Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions .agent/work-plans/issue-127/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Plan: CAMP running-task view P1 — read-only structured task tree

## Issue

https://github.com/rolker/camp/issues/127 (Part of #123)

## Context

CAMP's per-robot panel (`Platform`, `platform.ui`) shows `helmManager` +
`missionManager` in a vertical splitter. The `MissionManager` placeholder just
dumps a flattened `Heartbeat` into a text box. The boat now publishes structured
`marine_nav_interfaces/TaskFeedback` (current task + `TaskInformation[]`) on
`marine/status/mission_tasks` (unh_marine_autonomy#236). This adds a read-only
structured tree view sourced from that topic.

## Approach

1. **`RunningTasksModel`** (`QAbstractItemModel`) — builds a tree from the flat
`TaskInformation[]` using slash-delimited `id`s (`survey_a/line_1` → child of
`survey_a`; synthesize intermediate group rows when a path segment has no
explicit task). Columns: name / type / priority / status / done. Holds the
`current_navigation_task` id; `data()` returns a bold font + highlight
background for that row. `setTasks(current, rows)` rebuilds via
begin/endResetModel (small list, low rate — reset is fine for P1).
2. **`RunningTasksView`** (`QWidget`, node-injection — the `helm_manager` idiom,
NOT `camp_ros::ROSWidget`): `setNode()` + `updateRobotNamespace()`; owns a
`QTreeView` + `RunningTasksModel` built in the constructor (no `.ui` needed).
Exposes `taskSelected(QString id)` signal + `setSelectedTask(QString id)` slot
(wired to selection now; map glue consumes them in P2).
3. **Subscription** — `TaskFeedback` on `<ns>/marine/status/mission_tasks`,
created with the guarded Realtime callback group:
`if (auto ctx = camp_ros::RosContext::instance()) opts.callback_group =
ctx->group(camp_ros::RosContext::Group::Realtime);`. The executor-thread
callback converts the message to a GUI-friendly `QVector<TaskRow>` under a
mutex, then `QMetaObject::invokeMethod(this, "applyPendingTasks",
Qt::QueuedConnection)` marshals to the GUI thread (same pattern as
`MissionManager`). Never touch Qt from the ROS thread.
4. **Integration** — promote `RunningTasksView` in `platform.ui` (third widget in
the splitter); in `platform.cpp` call `runningTasksView->setNode(node_)`
(alongside the helm `setNode`) and `updateRobotNamespace(...)` (alongside the
others).
5. **Build wiring** — add the two `.cpp` to `SOURCES` (CMakeLists);
`find_package(marine_nav_interfaces REQUIRED)` + add to the executable's
`ament_target_dependencies`; `<depend>marine_nav_interfaces</depend>` in
`package.xml`.

## Files to Change

| File | Change |
|------|--------|
| `src/camp/running_tasks/running_tasks_model.{h,cpp}` | New tree model |
| `src/camp/running_tasks/running_tasks_view.{h,cpp}` | New node-injection widget |
| `src/camp/platform_manager/platform.ui` | Promote `RunningTasksView` in splitter |
| `src/camp/platform_manager/platform.cpp` | `setNode` + `updateRobotNamespace` |
| `CMakeLists.txt` | SOURCES + `marine_nav_interfaces` dep |
| `package.xml` | `<depend>marine_nav_interfaces</depend>` |

## Principles Self-Check

| Principle | Consideration |
|---|---|
| Thread safety | ROS callback never touches Qt; marshals via queued invoke + mutex, mirroring `MissionManager`. |
| Dual-use (no compromise) | Node-injection core + guarded Realtime group → CAMP gets the curated group, rqt (later) falls back to default. No `ROSWidget` coupling. |
| Additive | Placeholder `MissionManager` stays; this is a sibling view. |

## ADR Compliance

| ADR | Triggered | How addressed |
|---|---|---|
| (camp repo ADRs, if any) | TBD | Scan during implementation; this is additive UI, no message/contract changes. |

## Consequences

| If we change... | Also update... | In plan? |
|---|---|---|
| Add `marine_nav_interfaces` use | `package.xml` + CMake find_package + deps | Yes |
| New per-robot widget | `platform.ui` + `platform.cpp` node/namespace wiring | Yes |

## Open Questions

- Status column rendering: P1 shows the raw `status` YAML string (truncated). Full
parse/formatting deferred to P4 (mission progress).

## Estimated Scope

Single PR (new widget + model + wiring). Builds on the camp executable; rqt
wrapper and map linkage are separate later phases under #123.
38 changes: 38 additions & 0 deletions .agent/work-plans/issue-127/progress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
issue: 127
---

# Issue #127 — CAMP running-task view P1: read-only structured task tree

## Plan Authored
**Status**: complete
**When**: 2026-06-28 09:26 -04:00
**By**: Claude Code Agent (Claude Opus 4.8 (1M context))

**Plan**: `.agent/work-plans/issue-127/plan.md` at `2a1f7ce`
**Branch**: feature/issue-127 at `2a1f7ce`
**Phases**: single (P1 of camp#123)

### Open questions
- [ ] Status column shows raw status YAML first line (truncated) in P1; full parse deferred to P4.

## Local Review (Pre-Push)
**Status**: complete
**When**: 2026-06-28 09:33 -04:00
**By**: Claude Code Agent (Claude Opus 4.8 (1M context))
**Verdict**: approved (must-fix addressed before push)

**Branch**: feature/issue-127 at `e96eb34` (impl) — review fixes committed on top
**Mode**: pre-push
**Depth**: Deep (reason: custom QAbstractItemModel + ROS→GUI thread marshaling)
**Must-fix**: 1 (fixed) | **Suggestions**: 3 (2 fixed, 1 deferred)
**Round**: 1 | **Ship**: recommended — must-fix fixed + verified by rebuild

### Findings
- [x] (must-fix) ~RunningTasksView=default → UAF: pending_mutex_/rows_ destroyed before subscription_; callback in flight at teardown derefs freed members. FIXED: explicit dtor resets subscription_ first — `running_tasks_view.cpp`
- [x] (suggestion) Selection-restore re-emitted taskSelected() every republish → P2 feedback-loop risk. FIXED: QSignalBlocker around the restore — `running_tasks_view.cpp`
- [x] (suggestion) current_task_ stored raw vs normalized node fullId → highlight could miss on stray-slash id. FIXED: normalize current_task_ in setTasks — `running_tasks_model.cpp`
- [ ] (suggestion, deferred to P2) Synthetic group rows emit taskSelected(groupId) for a non-task; guard when map glue lands — `running_tasks_view.cpp`

### Model correctness (Lens A, verified)
- index/parent/rowCount/columnCount mutually consistent; rowInParent stable (append-only per rebuild); reset ordering correct (view restores by string id, not stale QModelIndex); null-safe. Walked rows [survey_a/line_2, survey_a, survey_a/line_1] — round-trips.
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ find_package(nav2_msgs REQUIRED)
find_package(pluginlib REQUIRED)
find_package(marine_autonomy REQUIRED)
find_package(marine_interfaces REQUIRED)
find_package(marine_nav_interfaces REQUIRED)

find_package(qt_gui_cpp REQUIRED)
find_package(rosgraph_msgs REQUIRED)
Expand Down Expand Up @@ -106,6 +107,8 @@ set(SOURCES
src/camp/ais/ais_contact.cpp
src/camp/ais/ais_manager.cpp
src/camp/helm_manager/helm_manager.cpp
src/camp/running_tasks/running_tasks_model.cpp
src/camp/running_tasks/running_tasks_view.cpp
src/camp/roslink.cpp
src/camp/nav_source.cpp
)
Expand Down Expand Up @@ -146,6 +149,7 @@ ament_target_dependencies(CCOMAutonomousMissionPlanner
nav2_msgs
marine_autonomy
marine_interfaces
marine_nav_interfaces

rclcpp
tf2
Expand Down
1 change: 1 addition & 0 deletions package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<depend>pluginlib</depend>
<depend>marine_autonomy</depend>
<depend>marine_interfaces</depend>
<depend>marine_nav_interfaces</depend>

<!-- <depend>sound_play</depend> -->
<depend>tf2</depend>
Expand Down
2 changes: 2 additions & 0 deletions src/camp/platform_manager/platform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ void Platform::update(const marine_interfaces::msg::Platform& platform)
platformNamespace = platform.platform_namespace;
m_ui->helmManager->updateRobotNamespace(platformNamespace.c_str());
m_ui->missionManager->updateRobotNamespace(platformNamespace.c_str());
m_ui->runningTasksView->updateRobotNamespace(platformNamespace.c_str());

path_topic_ = "/" + platformNamespace + "/received_global_plan";
subscribeToPathTopic();
Expand Down Expand Up @@ -227,6 +228,7 @@ void Platform::onNodeUpdated()
ns.second->nodeStarted(node_, transform_buffer_);
m_ui->helmManager->setNode(node_);
m_ui->missionManager->nodeStarted(node_, transform_buffer_);
m_ui->runningTasksView->setNode(node_);
subscribeToPathTopic();
}

Expand Down
7 changes: 7 additions & 0 deletions src/camp/platform_manager/platform.ui
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
</property>
<widget class="HelmManager" name="helmManager" native="true"/>
<widget class="MissionManager" name="missionManager" native="true"/>
<widget class="RunningTasksView" name="runningTasksView" native="true"/>
</widget>
</item>
<item>
Expand Down Expand Up @@ -49,6 +50,12 @@
<header>mission_manager/mission_manager.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>RunningTasksView</class>
<extends>QWidget</extends>
<header>running_tasks/running_tasks_view.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
Expand Down
Loading
Loading