action detailed view #62

Open
opened 2026-04-29 04:21:46 +00:00 by despiegk · 2 comments
Owner

mirror the approach used for the per-job (issue #58), per-service (issue #59), and per-run (issue #60) detailed views, but for a single action.

when we are looking at one action, we need a different view:

  • no longer show the grid
  • show the full action in a pane
  • right: live progress — list of jobs spawned by this action across recent runs, with phase chips
  • left: action metadata — name, description, command/script preview, retry policy (max attempts, backoff), tty flag, env preview, parent service if any, dependencies
  • header buttons: Run (schedule a job for this action), Edit, Delete, Logs, Jobs (open in Jobs tab filtered by this action), Terminal (only when tty)
  • link back to the parent service and to individual jobs
  • a clear "Back to actions" button
  • esc + browser back + url deep-link (#actions/) all work
  • responsive collapse below 900 px viewport

design a good ergonomic view for an action that mirrors the layout/feel of the new job/service/run detail views.

mirror the approach used for the per-job (issue #58), per-service (issue #59), and per-run (issue #60) detailed views, but for a single action. when we are looking at one action, we need a different view: - no longer show the grid - show the full action in a pane - right: live progress — list of jobs spawned by this action across recent runs, with phase chips - left: action metadata — name, description, command/script preview, retry policy (max attempts, backoff), tty flag, env preview, parent service if any, dependencies - header buttons: Run (schedule a job for this action), Edit, Delete, Logs, Jobs (open in Jobs tab filtered by this action), Terminal (only when tty) - link back to the parent service and to individual jobs - a clear "Back to actions" button - esc + browser back + url deep-link (#actions/<name>) all work - responsive collapse below 900 px viewport design a good ergonomic view for an action that mirrors the layout/feel of the new job/service/run detail views.
Author
Owner

Spec: Issue #62 — Action detailed view

Objective

Mirror the per-job (#58), per-service (#59), and per-run (#60) detailed views for a single action. Clicking a row in the Actions table (or deep-linking to #actions/<name>) hides the grid and opens a focused two-column workspace inside #tab-actions: action metadata on the left, a live sub-list of jobs the action has spawned across recent runs on the right. The legacy #actions-detail slideout is preserved exclusively for the New / Edit Action forms reached via showActionForm() / editAction().

Requirements

  • Hide the grid; show the action in a full pane.
  • Right pane — live sub-list of jobs spawned by this action across all recent runs, with phase chips. Includes a Run column (one action can produce jobs across many runs); each run cell is a chip-link to #runs/<id>. Each row clicks through to #jobs/<id>. 1.5 s polling.
  • Left pane — name, parent-service chip if dotted, description, command/script preview (or User prompt for interpreter === 'ai'), retry policy (max attempts, backoff, delay, max delay), TTY flag, env preview (collapsible <details>, closed by default), dependencies, interpreter, type (process / one-shot).
  • Header buttons — Run, Edit, Delete, Logs, Jobs, Terminal (only when a.tty === true), and a clear "Back to actions" button.
  • Cross-links to the parent service (#services/<svc>) and to individual jobs (#jobs/<id>).
  • Hash route #actions/<name> already deep-links via viewAction(); rewriting viewAction is sufficient.
  • Esc, browser back, and switching tabs all return to the grid and stop the poller cleanly. Esc must yield to active modals.
  • Layout collapses below 900 px to a vertical stack — handled automatically by extending the existing media query class lists.
  • Reuse --bg-* / --text-* / --border-color tokens.
  • Preserve the legacy #actions-detail slideout for showActionForm() and editAction(). closePanel('actions') symmetry: also call hideActionsDetailView().

Files to modify

  • crates/hero_proc_ui/templates/index.html
  • crates/hero_proc_ui/static/js/dashboard.js
  • crates/hero_proc_ui/static/css/dashboard.css

No backend / Rust changes.

Implementation plan

Step 1 — Restructure #tab-actions markup

Wrap the existing toolbar + bulk-bar + main-container (table + legacy #actions-detail slideout) in <div class="actions-list-view" id="actions-list-view">. Append a sibling <div class="actions-detail-view" id="actions-detail-view" hidden> with actions-detail-header (Back, title, actions) + actions-detail-split (left pane + right pane with toolbar + jobs container). Element IDs: actions-list-view, actions-detail-view, actions-detail-title, actions-detail-actions, actions-detail-back, actions-detail-left, actions-detail-right, actions-detail-jobs-toolbar, actions-detail-jobs-container, actions-detail-jobs-dot, actions-detail-jobs-status, actions-detail-jobs-count, actions-detail-jobs-pause, actions-detail-jobs-open.

Dependencies: none.

Step 2 — Extend the existing detail-view CSS

Append .actions-list-view, .actions-detail-view, .actions-detail-header, .actions-detail-title, .actions-detail-actions, .actions-detail-back, .actions-detail-split, .actions-detail-left, .actions-detail-right, .actions-detail-jobs-toolbar, .actions-detail-jobs to every existing grouped selector list (.jobs-detail-* / .services-detail-* / .runs-detail-*). Extend the 900 px media query similarly. Add a small .actions-detail-jobs { overflow-y: auto; padding: 0; } block plus the same > .data-table hover rules already present for runs/services.

Dependencies: Step 1.

Step 3 — State variables, show/hide helpers, ESC handler

Insert near the existing per-tab detail-view blocks:

let _actionsDetailOpen = false;
let _actionsDetailName = null;
let _actionsDetailJobsPaused = false;
let _actionsDetailJobsTimer = null;

function showActionsDetailView() { ... }
function stopActionsDetailJobsPolling() { ... }
function hideActionsDetailView() { ... }       // restores list, clears state, replaceRoute('actions') if hash starts with 'actions/'
function toggleActionsDetailJobsPause() { ... }
function refreshActionsDetailJobs() { fetchActionsDetailJobs(); }

document.addEventListener('keydown', function(e) { ... });   // dismiss on Esc, yield to modals

Dependencies: Step 1.

Step 4 — switchTab integration

Add if (tabName !== 'actions') hideActionsDetailView(); next to the existing jobs/services/runs equivalents.

Dependencies: Step 3.

Step 5 — Rewrite viewAction(name)

Fetch action.get and the initial job.list { filter: { action_id: name, limit: 200 } } in parallel. Populate title, header action buttons (Run / Edit / Logs / Jobs / Delete always; Terminal only when a.tty), and left-pane HTML via a helper renderActionsDetailLeft(a, svcName). Render the right-pane jobs table inline (helper renderActionsDetailJobsTable(jobs)). Init _actionsDetailName = a.name, reset toolbar state, wire actions-detail-jobs-open.onclick = navigateToActionJobs(a.name). Call showActionsDetailView(), setRoute('actions/' + encodeURIComponent(a.name)), mark the row selected, and start startActionsDetailJobsPolling(a.name).

Dependencies: Steps 1, 3.

Step 6 — Right-pane helpers + jobs polling loop

Add renderActionsDetailLeft(a, svcName), renderActionsDetailJobsTable(jobs) (columns: ID / Run / Phase / Attempt / Exit / Created — Run cell is chipLink('#'+j.run_id, 'runs', j.run_id) when present, else -). Add fetchActionsDetailJobs (1.5 s interval) and startActionsDetailJobsPolling(name). Polling re-fetches the job list scoped to _actionsDetailName (no action.get round-trip — action metadata is static while the view is open). Empty case: "No jobs for this action yet."

Dependencies: Step 5.

Step 7 — Action navigation helpers

Add navigateToActionJobs(name) (sets #jobs?action=<name> route, switches tabs, pre-fills #jobs-search with the action name so the existing client-side filter narrows to the right rows), navigateToActionLogs(name) (resolves the action's recent job IDs via job.list { action_id } then deep-links to #logs/jobs/<csv>), navigateToActionTerminal(name) (picks the newest running job and hands off to navigateToTerminalJob(id)).

Dependencies: none (used by Step 5).

Step 8 — Hashchange + closePanel symmetry

  • In the terminal else branch of the hashchange handler, mirror the jobs/services/runs lines: if (hash === 'actions' && _actionsDetailOpen) hideActionsDetailView();.
  • In closePanel, when tab === 'actions', call hideActionsDetailView() so legacy bulk/post-save callers drop the detail view too.

Dependencies: Step 3.

Step 9 — Smoke test

Manual verification through the dashboard via Hero Browser MCP. Restart via the standard service_proc start --reset flow.

Dependencies: Steps 1–8.

Acceptance criteria

  • Clicking a row in #actions-tbody hides the grid and shows #actions-detail-view.
  • Title shows the action name with a lightning-charge icon.
  • Left pane shows description (when present), parent-service chip (when dotted), TTY badge, Type, Interpreter, Retry Policy block (when present), env <details> (when non-empty), Dependencies (when non-empty), and the Script <pre> (or "User prompt" for AI).
  • Header buttons: Run / Edit / Logs / Jobs / Delete always; Terminal only when a.tty === true.
  • Run schedules a job via runAction(name) and toasts the new id.
  • Right pane lists jobs spawned by this action across runs (columns ID / Run / Phase / Attempt / Exit / Created). Empty state reads "No jobs for this action yet."
  • Run column is a chip-link to #runs/<id>; clicking a row opens #jobs/<id>.
  • Live dot pulses while polling; pause toggles icon + status text and stops fetches.
  • Polling interval is 1.5 s; the timer is cleared on hide / tab switch / browser back.
  • Open-in-Jobs-tab toolbar button calls navigateToActionJobs.
  • Hash route #actions/<name> deep-links into the detail view on initial load and on hashchange.
  • Browser back from #actions/<name> returns to #actions and closes the view.
  • Esc closes the view; Esc is ignored while a job-modal, confirm-modal, or any .modal.show is on top.
  • Switching tabs while detail is open also closes it.
  • Below 900 px viewport, the split collapses to a vertical stack.
  • The legacy #actions-detail slideout still opens for showActionForm() and editAction().
  • No console errors.

Notes

  • Polling vs. push. Same 1.5 s interval as runs/services. The poll is cheap: job.list { action_id, limit: 200 } hits a single indexed action_id = ? clause in list_jobs.
  • Static left pane. Action metadata is intrinsic to the spec and does not mutate while the user is looking at it. The poller refreshes only the right pane, avoiding a re-render of the script <pre> (which can be long).
  • action_id filter is the right one. job.create sets action_id = Some(spec.name.clone()) for every job, so server-side filtering is complete and exact.
  • ?action= hash for Jobs tab. The Jobs tab hashchange handler does not yet honour an action query param. The cheap path used here pre-fills #jobs-search so the existing client-side filter narrows to the right rows. A first-class server-side action filter for the Jobs tab list is a clean follow-up.
  • Terminal button. Terminals attach to a live PTY-bearing job, not to an action spec. navigateToActionTerminal picks the newest running job for that action; if none exists it toasts a hint to use Run first.
  • No parent service. When jobServiceName(a.name) === '' (top-level actions), the Parent Service form-group is omitted.
  • Legacy slideout coexistence. showActionForm() / editAction() continue to use #actions-detail (right-side slideout). closePanel('actions') is extended to also close the new detail view so post-save and bulk-delete paths don't leave a stale view open.
  • Dependency graph. The old viewAction rendered a D3 dependency graph via renderActionGraph. It's left in place and unreferenced for now, exactly as renderRunGraph was after #60. Reintroducing it is a clean follow-up.
# Spec: Issue #62 — Action detailed view ## Objective Mirror the per-job (#58), per-service (#59), and per-run (#60) detailed views for a single action. Clicking a row in the Actions table (or deep-linking to `#actions/<name>`) hides the grid and opens a focused two-column workspace inside `#tab-actions`: action metadata on the left, a live sub-list of jobs the action has spawned across recent runs on the right. The legacy `#actions-detail` slideout is preserved exclusively for the New / Edit Action forms reached via `showActionForm()` / `editAction()`. ## Requirements - Hide the grid; show the action in a full pane. - **Right pane** — live sub-list of jobs spawned by this action across all recent runs, with phase chips. Includes a Run column (one action can produce jobs across many runs); each run cell is a chip-link to `#runs/<id>`. Each row clicks through to `#jobs/<id>`. 1.5 s polling. - **Left pane** — name, parent-service chip if dotted, description, command/script preview (or User prompt for `interpreter === 'ai'`), retry policy (max attempts, backoff, delay, max delay), TTY flag, env preview (collapsible `<details>`, closed by default), dependencies, interpreter, type (process / one-shot). - **Header buttons** — Run, Edit, Delete, Logs, Jobs, Terminal (only when `a.tty === true`), and a clear "Back to actions" button. - Cross-links to the parent service (`#services/<svc>`) and to individual jobs (`#jobs/<id>`). - Hash route `#actions/<name>` already deep-links via `viewAction()`; rewriting `viewAction` is sufficient. - Esc, browser back, and switching tabs all return to the grid and stop the poller cleanly. Esc must yield to active modals. - Layout collapses below 900 px to a vertical stack — handled automatically by extending the existing media query class lists. - Reuse `--bg-*` / `--text-*` / `--border-color` tokens. - Preserve the legacy `#actions-detail` slideout for `showActionForm()` and `editAction()`. `closePanel('actions')` symmetry: also call `hideActionsDetailView()`. ## Files to modify - `crates/hero_proc_ui/templates/index.html` - `crates/hero_proc_ui/static/js/dashboard.js` - `crates/hero_proc_ui/static/css/dashboard.css` No backend / Rust changes. ## Implementation plan ### Step 1 — Restructure `#tab-actions` markup Wrap the existing toolbar + bulk-bar + main-container (table + legacy `#actions-detail` slideout) in `<div class="actions-list-view" id="actions-list-view">`. Append a sibling `<div class="actions-detail-view" id="actions-detail-view" hidden>` with `actions-detail-header` (Back, title, actions) + `actions-detail-split` (left pane + right pane with toolbar + jobs container). Element IDs: `actions-list-view`, `actions-detail-view`, `actions-detail-title`, `actions-detail-actions`, `actions-detail-back`, `actions-detail-left`, `actions-detail-right`, `actions-detail-jobs-toolbar`, `actions-detail-jobs-container`, `actions-detail-jobs-dot`, `actions-detail-jobs-status`, `actions-detail-jobs-count`, `actions-detail-jobs-pause`, `actions-detail-jobs-open`. Dependencies: none. ### Step 2 — Extend the existing detail-view CSS Append `.actions-list-view`, `.actions-detail-view`, `.actions-detail-header`, `.actions-detail-title`, `.actions-detail-actions`, `.actions-detail-back`, `.actions-detail-split`, `.actions-detail-left`, `.actions-detail-right`, `.actions-detail-jobs-toolbar`, `.actions-detail-jobs` to every existing grouped selector list (`.jobs-detail-* / .services-detail-* / .runs-detail-*`). Extend the 900 px media query similarly. Add a small `.actions-detail-jobs { overflow-y: auto; padding: 0; }` block plus the same `> .data-table` hover rules already present for runs/services. Dependencies: Step 1. ### Step 3 — State variables, show/hide helpers, ESC handler Insert near the existing per-tab detail-view blocks: ``` let _actionsDetailOpen = false; let _actionsDetailName = null; let _actionsDetailJobsPaused = false; let _actionsDetailJobsTimer = null; function showActionsDetailView() { ... } function stopActionsDetailJobsPolling() { ... } function hideActionsDetailView() { ... } // restores list, clears state, replaceRoute('actions') if hash starts with 'actions/' function toggleActionsDetailJobsPause() { ... } function refreshActionsDetailJobs() { fetchActionsDetailJobs(); } document.addEventListener('keydown', function(e) { ... }); // dismiss on Esc, yield to modals ``` Dependencies: Step 1. ### Step 4 — `switchTab` integration Add `if (tabName !== 'actions') hideActionsDetailView();` next to the existing jobs/services/runs equivalents. Dependencies: Step 3. ### Step 5 — Rewrite `viewAction(name)` Fetch `action.get` and the initial `job.list { filter: { action_id: name, limit: 200 } }` in parallel. Populate title, header action buttons (Run / Edit / Logs / Jobs / Delete always; Terminal only when `a.tty`), and left-pane HTML via a helper `renderActionsDetailLeft(a, svcName)`. Render the right-pane jobs table inline (helper `renderActionsDetailJobsTable(jobs)`). Init `_actionsDetailName = a.name`, reset toolbar state, wire `actions-detail-jobs-open.onclick = navigateToActionJobs(a.name)`. Call `showActionsDetailView()`, `setRoute('actions/' + encodeURIComponent(a.name))`, mark the row selected, and start `startActionsDetailJobsPolling(a.name)`. Dependencies: Steps 1, 3. ### Step 6 — Right-pane helpers + jobs polling loop Add `renderActionsDetailLeft(a, svcName)`, `renderActionsDetailJobsTable(jobs)` (columns: ID / Run / Phase / Attempt / Exit / Created — Run cell is `chipLink('#'+j.run_id, 'runs', j.run_id)` when present, else `-`). Add `fetchActionsDetailJobs` (1.5 s interval) and `startActionsDetailJobsPolling(name)`. Polling re-fetches the job list scoped to `_actionsDetailName` (no `action.get` round-trip — action metadata is static while the view is open). Empty case: "No jobs for this action yet." Dependencies: Step 5. ### Step 7 — Action navigation helpers Add `navigateToActionJobs(name)` (sets `#jobs?action=<name>` route, switches tabs, pre-fills `#jobs-search` with the action name so the existing client-side filter narrows to the right rows), `navigateToActionLogs(name)` (resolves the action's recent job IDs via `job.list { action_id }` then deep-links to `#logs/jobs/<csv>`), `navigateToActionTerminal(name)` (picks the newest running job and hands off to `navigateToTerminalJob(id)`). Dependencies: none (used by Step 5). ### Step 8 — Hashchange + closePanel symmetry - In the terminal `else` branch of the hashchange handler, mirror the jobs/services/runs lines: `if (hash === 'actions' && _actionsDetailOpen) hideActionsDetailView();`. - In `closePanel`, when `tab === 'actions'`, call `hideActionsDetailView()` so legacy bulk/post-save callers drop the detail view too. Dependencies: Step 3. ### Step 9 — Smoke test Manual verification through the dashboard via Hero Browser MCP. Restart via the standard `service_proc start --reset` flow. Dependencies: Steps 1–8. ## Acceptance criteria - [ ] Clicking a row in `#actions-tbody` hides the grid and shows `#actions-detail-view`. - [ ] Title shows the action name with a lightning-charge icon. - [ ] Left pane shows description (when present), parent-service chip (when dotted), TTY badge, Type, Interpreter, Retry Policy block (when present), env `<details>` (when non-empty), Dependencies (when non-empty), and the Script `<pre>` (or "User prompt" for AI). - [ ] Header buttons: Run / Edit / Logs / Jobs / Delete always; Terminal only when `a.tty === true`. - [ ] Run schedules a job via `runAction(name)` and toasts the new id. - [ ] Right pane lists jobs spawned by this action across runs (columns ID / Run / Phase / Attempt / Exit / Created). Empty state reads "No jobs for this action yet." - [ ] Run column is a chip-link to `#runs/<id>`; clicking a row opens `#jobs/<id>`. - [ ] Live dot pulses while polling; pause toggles icon + status text and stops fetches. - [ ] Polling interval is 1.5 s; the timer is cleared on hide / tab switch / browser back. - [ ] Open-in-Jobs-tab toolbar button calls `navigateToActionJobs`. - [ ] Hash route `#actions/<name>` deep-links into the detail view on initial load and on `hashchange`. - [ ] Browser back from `#actions/<name>` returns to `#actions` and closes the view. - [ ] Esc closes the view; Esc is ignored while a job-modal, confirm-modal, or any `.modal.show` is on top. - [ ] Switching tabs while detail is open also closes it. - [ ] Below 900 px viewport, the split collapses to a vertical stack. - [ ] The legacy `#actions-detail` slideout still opens for `showActionForm()` and `editAction()`. - [ ] No console errors. ## Notes - **Polling vs. push.** Same 1.5 s interval as runs/services. The poll is cheap: `job.list { action_id, limit: 200 }` hits a single indexed `action_id = ?` clause in `list_jobs`. - **Static left pane.** Action metadata is intrinsic to the spec and does not mutate while the user is looking at it. The poller refreshes only the right pane, avoiding a re-render of the script `<pre>` (which can be long). - **`action_id` filter is the right one.** `job.create` sets `action_id = Some(spec.name.clone())` for every job, so server-side filtering is complete and exact. - **`?action=` hash for Jobs tab.** The Jobs tab hashchange handler does not yet honour an `action` query param. The cheap path used here pre-fills `#jobs-search` so the existing client-side filter narrows to the right rows. A first-class server-side action filter for the Jobs tab list is a clean follow-up. - **Terminal button.** Terminals attach to a live PTY-bearing job, not to an action spec. `navigateToActionTerminal` picks the newest running job for that action; if none exists it toasts a hint to use Run first. - **No parent service.** When `jobServiceName(a.name) === ''` (top-level actions), the Parent Service form-group is omitted. - **Legacy slideout coexistence.** `showActionForm()` / `editAction()` continue to use `#actions-detail` (right-side slideout). `closePanel('actions')` is extended to also close the new detail view so post-save and bulk-delete paths don't leave a stale view open. - **Dependency graph.** The old `viewAction` rendered a D3 dependency graph via `renderActionGraph`. It's left in place and unreferenced for now, exactly as `renderRunGraph` was after #60. Reintroducing it is a clean follow-up.
Author
Owner

Implementation complete — browser test results

UI-only change, three files. cargo check passes for hero_proc_ui. Service restarted via service_proc start --reset; the dashboard was driven through Hero Browser MCP at the running ui.sock.

Files modified

File Change
crates/hero_proc_ui/templates/index.html actions tab restructured into list-view / detail-view with header + 2-column split + right-pane jobs toolbar
crates/hero_proc_ui/static/js/dashboard.js new state vars (_actionsDetailOpen, _actionsDetailName, _actionsDetailJobsPaused, _actionsDetailJobsTimer); show/hide helpers; ESC handler; switchTab / closePanel / hashchange symmetry; full rewrite of viewAction with helpers renderActionsDetailLeft, renderActionsDetailJobsTable, fetchActionsDetailJobs, startActionsDetailJobsPolling; new navigateToActionJobs, navigateToActionLogs, navigateToActionTerminal helpers
crates/hero_proc_ui/static/css/dashboard.css extended every existing .jobs-detail-* / .services-detail-* / .runs-detail-* selector group to also cover .actions-detail-*; added .actions-detail-jobs overflow + table hover rules

No backend / Rust changes. The legacy #actions-detail slideout is preserved — showActionForm() and editAction() still use it. The legacy renderActionGraph D3 helper is retained but unreachable; cleanup deferred (mirrors the renderRunGraph precedent from #60).

Test matrix (all pass)

Acceptance criterion Result
Click action row hides grid, shows two-pane detail pass
URL becomes #actions/<name> on open pass (#actions/hero_browser_server)
Title shows lightning-charge icon + action name pass
Header: Run / Edit / Logs / Jobs / Delete always; Terminal only when a.tty === true pass (TTY-disabled action: 5 buttons, no Terminal)
Left pane: TTY badge, Type, Interpreter, Retry Policy block, Env <details>, Script <pre> pass (all sections render)
Parent Service chip when name is dotted (verified independently against dotted action names) pass
Right pane lists jobs spawned by this action across runs (columns ID / Run / Phase / Attempt / Exit / Created) pass (1 job, run cell is chip-link to #runs/6)
Empty case shows "No jobs for this action yet." pass
Run column is a chip-link to #runs/<id>; clicking a row opens #jobs/<id> pass (lands at #jobs/14, _jobsDetailOpen=true, actions view + timer cleared)
Live dot pulses while polling; "Live" status text + count "N jobs" pass
Polling interval 1.5 s; timer cleared on hide / tab switch / browser back pass (_actionsDetailJobsTimer=null after each teardown)
Open-in-Jobs-tab toolbar button calls navigateToActionJobs pass
Hash route #actions/<name> deep-links into the detail view pass
Browser back from #actions/<name> returns to #actions and closes the view pass
Esc closes the view; yields to active modals pass
Switching tabs while detail is open also closes it pass
Below 900 px viewport, the split collapses to a vertical stack pass (flex-direction: column)
Legacy #actions-detail New Action form still opens pass
List filter (search) still works after Back pass (search "browser" matches 2 rows)
No console errors pass (empty console_messages)

Notes

  • Polling vs. push. 1.5 s matches runs/services. The poll is cheap: job.list { action_id, limit: 200 } hits an indexed action_id = ? clause in list_jobs.
  • Static left pane. Action metadata is intrinsic to the spec and does not mutate while the user is looking at it. The poller refreshes only the right pane, avoiding a re-render of the script <pre> (which can be long).
  • action_id filter is exact. job.create sets action_id = Some(spec.name.clone()) for every job, so server-side filtering is complete.
  • ?action= hash for Jobs tab. The Jobs tab hashchange handler does not yet honour an action query param. The cheap path used here pre-fills #jobs-search so the existing client-side filter narrows to the right rows. A first-class server-side action filter for the Jobs tab list is a clean follow-up.
  • Terminal button. Terminals attach to a live PTY-bearing job, not to an action spec. navigateToActionTerminal picks the newest running job for that action; if none exists it toasts a hint to use Run first.
  • No parent service. When the action name is not dotted (e.g. cleanup), the Parent Service chip is omitted.
  • Legacy slideout coexistence. #actions-detail (slideout) and #actions-detail-view (workspace) are siblings inside #tab-actions. closePanel('actions') is extended to also close the new detail view so post-save and bulk-delete paths don't leave a stale view open.
  • Class-name convention. actions-detail-view (plural prefix) intentionally mirrors jobs-detail-view, services-detail-view, and runs-detail-view. Legacy #actions-detail ID is unchanged.
  • Cleanup deferred. renderActionGraph (D3) is no longer called and stays in place for now — same precedent as renderRunGraph after #60.

Implementation complete; ready to merge once reviewed.

## Implementation complete — browser test results UI-only change, three files. cargo check passes for hero_proc_ui. Service restarted via `service_proc start --reset`; the dashboard was driven through Hero Browser MCP at the running ui.sock. ### Files modified | File | Change | |---|---| | `crates/hero_proc_ui/templates/index.html` | actions tab restructured into list-view / detail-view with header + 2-column split + right-pane jobs toolbar | | `crates/hero_proc_ui/static/js/dashboard.js` | new state vars (`_actionsDetailOpen`, `_actionsDetailName`, `_actionsDetailJobsPaused`, `_actionsDetailJobsTimer`); show/hide helpers; ESC handler; `switchTab` / `closePanel` / hashchange symmetry; full rewrite of `viewAction` with helpers `renderActionsDetailLeft`, `renderActionsDetailJobsTable`, `fetchActionsDetailJobs`, `startActionsDetailJobsPolling`; new `navigateToActionJobs`, `navigateToActionLogs`, `navigateToActionTerminal` helpers | | `crates/hero_proc_ui/static/css/dashboard.css` | extended every existing `.jobs-detail-* / .services-detail-* / .runs-detail-*` selector group to also cover `.actions-detail-*`; added `.actions-detail-jobs` overflow + table hover rules | No backend / Rust changes. The legacy `#actions-detail` slideout is preserved — `showActionForm()` and `editAction()` still use it. The legacy `renderActionGraph` D3 helper is retained but unreachable; cleanup deferred (mirrors the `renderRunGraph` precedent from #60). ### Test matrix (all pass) | Acceptance criterion | Result | |---|---| | Click action row hides grid, shows two-pane detail | pass | | URL becomes `#actions/<name>` on open | pass (`#actions/hero_browser_server`) | | Title shows lightning-charge icon + action name | pass | | Header: Run / Edit / Logs / Jobs / Delete always; Terminal only when `a.tty === true` | pass (TTY-disabled action: 5 buttons, no Terminal) | | Left pane: TTY badge, Type, Interpreter, Retry Policy block, Env `<details>`, Script `<pre>` | pass (all sections render) | | Parent Service chip when name is dotted (verified independently against dotted action names) | pass | | Right pane lists jobs spawned by this action across runs (columns ID / Run / Phase / Attempt / Exit / Created) | pass (1 job, run cell is chip-link to `#runs/6`) | | Empty case shows "No jobs for this action yet." | pass | | Run column is a chip-link to `#runs/<id>`; clicking a row opens `#jobs/<id>` | pass (lands at `#jobs/14`, `_jobsDetailOpen=true`, actions view + timer cleared) | | Live dot pulses while polling; "Live" status text + count "N jobs" | pass | | Polling interval 1.5 s; timer cleared on hide / tab switch / browser back | pass (`_actionsDetailJobsTimer=null` after each teardown) | | Open-in-Jobs-tab toolbar button calls `navigateToActionJobs` | pass | | Hash route `#actions/<name>` deep-links into the detail view | pass | | Browser back from `#actions/<name>` returns to `#actions` and closes the view | pass | | Esc closes the view; yields to active modals | pass | | Switching tabs while detail is open also closes it | pass | | Below 900 px viewport, the split collapses to a vertical stack | pass (`flex-direction: column`) | | Legacy `#actions-detail` New Action form still opens | pass | | List filter (search) still works after Back | pass (search "browser" matches 2 rows) | | No console errors | pass (empty `console_messages`) | ### Notes - **Polling vs. push.** 1.5 s matches runs/services. The poll is cheap: `job.list { action_id, limit: 200 }` hits an indexed `action_id = ?` clause in `list_jobs`. - **Static left pane.** Action metadata is intrinsic to the spec and does not mutate while the user is looking at it. The poller refreshes only the right pane, avoiding a re-render of the script `<pre>` (which can be long). - **`action_id` filter is exact.** `job.create` sets `action_id = Some(spec.name.clone())` for every job, so server-side filtering is complete. - **`?action=` hash for Jobs tab.** The Jobs tab hashchange handler does not yet honour an `action` query param. The cheap path used here pre-fills `#jobs-search` so the existing client-side filter narrows to the right rows. A first-class server-side action filter for the Jobs tab list is a clean follow-up. - **Terminal button.** Terminals attach to a live PTY-bearing job, not to an action spec. `navigateToActionTerminal` picks the newest running job for that action; if none exists it toasts a hint to use Run first. - **No parent service.** When the action name is not dotted (e.g. `cleanup`), the Parent Service chip is omitted. - **Legacy slideout coexistence.** `#actions-detail` (slideout) and `#actions-detail-view` (workspace) are siblings inside `#tab-actions`. `closePanel('actions')` is extended to also close the new detail view so post-save and bulk-delete paths don't leave a stale view open. - **Class-name convention.** `actions-detail-view` (plural prefix) intentionally mirrors `jobs-detail-view`, `services-detail-view`, and `runs-detail-view`. Legacy `#actions-detail` ID is unchanged. - **Cleanup deferred.** `renderActionGraph` (D3) is no longer called and stays in place for now — same precedent as `renderRunGraph` after #60. Implementation complete; ready to merge once reviewed.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_proc#62
No description provided.