run detailed view #60
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_proc#60
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
mirror the approach used for the per-job detailed view (issue #58), but for a single run.
when we are looking at one run, we need a different view:
design a good ergonomic view for a run that mirrors the layout/feel of the new job detail view.
Spec: Issue #60 — Run detailed view
Objective
Replace the side-panel run detail (
#runs-detailslideout) with a full-pane two-column workspace inside#tab-runs, mirroring the per-job (#58) and per-service (#59) detail views. When a single run is selected the runs grid is hidden and a dedicated workspace shows run metadata on the left and a live, polled jobs sub-list on the right. The legacy#runs-detailslideout stays in place because it is still used byshowRunForm()(New Run),showRunEditForm()(Edit Run), and theclosePanel('runs')callers.Requirements
viewRun(id)is invoked.#jobs?run=<id>), Kill All Jobs (only when active), Delete.<table class="data-table">with ID/Action/Phase/Attempt/Exit/Duration. Each row clicks through to the per-job detail. 1.5 s polling refetchesjob.list { filter: { run_id, limit: 200 } }and recomputes the jobs-summary on the left pane in place. Norun.getround-trip per tick. Empty case: "No jobs in this run yet."#runs/<id>from the URL viareplaceRoute('runs').#job-modal-overlay,.confirm-modal-overlay,.modal.show).viewRuncallssetRoute('runs/' + id)and the existinghashchangelands plain#runsonswitchTab('runs').#runs/<id>opens the new view directly (existing routing already callsviewRun(id)).closePanel('runs')dismisses the workspace (mirrors the symmetry shipped forclosePanel('jobs')/closePanel('services')).if (tabName !== 'jobs') hideJobsDetailView();/if (tabName !== 'services') hideServicesDetailView();).--bg-*/--text-*/--border-colortokens.Files to modify
crates/hero_proc_ui/templates/index.htmlcrates/hero_proc_ui/static/js/dashboard.jscrates/hero_proc_ui/static/css/dashboard.cssNo backend / Rust changes.
Implementation plan
Step 1 — Restructure
#tab-runsmarkupWrap the existing toolbar + bulk-bar + main-container (table + legacy
#runs-detailslideout) in<div class="runs-list-view" id="runs-list-view">. Append a sibling<div class="runs-detail-view" id="runs-detail-view" hidden>withruns-detail-header(Back, title, actions) +runs-detail-split(left pane + right pane with toolbar + jobs container). The legacy#runs-detailpanel stays inside#runs-list-view.Element IDs:
runs-list-view,runs-detail-view,runs-detail-title,runs-detail-actions,runs-detail-back,runs-detail-left,runs-detail-right,runs-detail-jobs-toolbar,runs-detail-jobs-container,runs-detail-jobs-dot,runs-detail-jobs-status,runs-detail-jobs-count,runs-detail-jobs-pause,runs-detail-jobs-open.Dependencies: none.
Step 2 — Extend the existing detail-view CSS
Append
.runs-list-view,.runs-detail-view,.runs-detail-header,.runs-detail-title,.runs-detail-actions,.runs-detail-back,.runs-detail-split,.runs-detail-left,.runs-detail-right,.runs-detail-jobs-toolbar,.runs-detail-jobsto every existing grouped selector list (the.jobs-detail-* / .services-detail-*rules at the bottom of the file). Extend the 900 px media query similarly. Add.runs-detail-jobs { overflow-y: auto; padding: 0; }plus the same> .data-tablehover rules already present for.services-detail-jobs.Dependencies: Step 1.
Step 3 — State variables, show/hide helpers, ESC handler
Insert just before the RUNS section (matching the placement used for the services detail view):
Dependencies: Step 1.
Step 4 —
switchTabintegrationAdd
if (tabName !== 'runs') hideRunsDetailView();next to the existing jobs/services equivalents.Dependencies: Step 3.
Step 5 — Rewrite
viewRun(id)Fetch
run.getand the initialjob.list { filter: { run_id, limit: 200 } }in parallel. Populate title, header action buttons (Edit, Open Jobs, Kill All Jobs gated by isActive, Delete), and left-pane HTML (status badge, id, name, parent-service chip, timestamps, duration, error from first failed job, dependencies, jobs summary). Render the right-pane jobs table inline (same template asfetchServicesDetailJobs). Init_runsDetailId = id, reset toolbar state, wireruns-detail-jobs-open.onclick = navigateToRunJobs(id). CallshowRunsDetailView(),setRoute('runs/' + id), mark the row selected, and startstartRunsDetailJobsPolling(id).Dependencies: Steps 1, 3.
Step 6 — Right-pane jobs polling loop
Add
fetchRunsDetailJobs(1.5 s interval) andstartRunsDetailJobsPolling(id). Each tick re-fetches the job list scoped to_runsDetailId(norun.getround-trip), repaints the table, and updates the jobs-summary on the left pane (target a stable#runs-detail-jobs-summarychild written by the helper that builds the left pane). Empty case message: "No jobs in this run yet."Dependencies: Step 5.
Step 7 —
navigateToRunJobs(id)helperThin wrapper around the existing
#jobs?run=<id>route: set_pendingRunFilter = id; _activeRunFilter = id; setRoute('jobs?run=' + id); switchTab('jobs'); loadJobs();(matching the hashchange handler's behaviour).Dependencies: none (used by Step 5).
Step 8 — Hashchange + closePanel symmetry
elsebranch of the hashchange handler, mirror the jobs/services line:if (hash === 'runs' && _runsDetailOpen) hideRunsDetailView();.closePanel, whentab === 'runs', callhideRunsDetailView().Dependencies: Step 3.
Step 9 — Smoke test
Manual verification through the dashboard via Hero Browser MCP (see acceptance criteria). Restart via the standard
service_proc start --resetflow.Dependencies: Steps 1–8.
Acceptance criteria
#runs/<id>directly opens the workspace.#services/<name>and opens the service detail view.#jobs/<id>and opens the per-job detail.#jobs?run=<id>), Kill All Jobs (only when active), Delete (returns to grid).#runs-detailslideout still works for New Run / Edit Run forms._runsDetailJobsTimeris null after closing).Notes
_runsDetailId, re-fetchesjob.list, repaints, and recomputes jobs-summary on the left.run.getis not re-fetched per tick — run lifecycle changes are picked up by the next view-open or by the existing tab refresh. Addingrun.getto each tick later is cheap if a sharper status refresh is wanted.r.status === 'error', the left pane shows the first failed job'serrorstring. If no jobs failed (run failed before scheduling), the field stays empty. The Run model has no top-level error field today.isActivefor Kill button. Coverscreated | starting | waiting_deps | running(matches the existing context-menu logic).service_idis null simply omits the parent service chip.#runs-detail(slideout) and#runs-detail-view(workspace) are siblings inside#tab-runs. The slideout's positioning is unchanged.closePanel('runs')is idempotent — callinghideRunsDetailView()when the workspace is already hidden is a no-op.renderRunGraph()is no longer called fromviewRunafter the rewrite. It stays in the file for now (out of scope to remove) but is unreachable.runs-detail-view(plural prefix) intentionally mirrorsjobs-detail-viewandservices-detail-view. The legacy#runs-detailID is unchanged.Implementation complete — browser test results
UI-only change, three files. cargo check passes for hero_proc_ui. Service was restarted via
service_proc start --reset; the dashboard was driven through the Hero Browser MCP at the running ui.sock.Files modified
crates/hero_proc_ui/templates/index.htmlcrates/hero_proc_ui/static/js/dashboard.js_runsDetailOpen,_runsDetailId,_runsDetailJobsPaused,_runsDetailJobsTimer), show/hide helpers, ESC handler,switchTabintegration, full rewrite ofviewRunwith helpersrenderRunsDetailLeft,renderRunsDetailJobsSummary,renderRunsDetailJobsTable;fetchRunsDetailJobs+startRunsDetailJobsPollingpoller;navigateToRunJobs(id)deep-link helper;closePanel('runs')+ hashchange symmetrycrates/hero_proc_ui/static/css/dashboard.css.jobs-detail-* / .services-detail-*selector group to also cover the.runs-detail-*counterparts; added.runs-detail-jobsoverflow + table hover rulesNo backend / Rust changes. The legacy
#runs-detailslideout is preserved —showRunForm()andeditRun()(showRunEditForm) still use it. The legacyrenderRunGraphD3 helper is retained but unreachable; cleanup deferred.Test matrix (all pass)
#runs/<id>on open#runs/7)#services/<name>and opens the service detail viewnavigateTo('services', name))#tab-jobs#jobs/15,_jobsDetailOpen=true, runs view + timer cleared)#runs-detail-jobs-summaryre-rendered without disturbing the rest of the pane)_runsDetailJobsTimer=null)if (tabName !== 'runs') hideRunsDetailView())flex-direction: column)#runs-detailNew Run form still opensconsole_messages)Notes
job.list { filter: { run_id, limit: 200 } }, repaints the table, and recomputes the jobs-summary on the left.run.getis not re-fetched per tick — run lifecycle changes are picked up the next time the view is opened or by the existing tab refresh.error/failedruns surface the first failed job'serrorstring on the left pane; runs with zero jobs render "No jobs in this run yet." withtotal = 0; runs with noservice_idsimply omit the Parent Service chip.#runs-detail(slideout) and#runs-detail-view(workspace) are siblings inside#tab-runs. The slideout's positioning is unchanged.closePanel('runs')now also callshideRunsDetailView()(idempotent), so destructive callers (bulk-delete, save-then-close) leave the user on a refreshed grid.--bg-*/--text-*/--border-colortokens.runs-detail-view(plural prefix) intentionally mirrorsjobs-detail-viewandservices-detail-view. Legacy#runs-detailID is unchanged.renderRunGraph(D3) is no longer called and stays in place for now; removing it is out of scope.Implementation complete; ready to merge once reviewed.