Skip to content

feat(OMN-12072): Swarm Control Plane widget — decomposition tree, wave view, live counters, savings bar - #125

Merged
jonahgabriel merged 2 commits into
devfrom
jonah/omn-12072-swarm-progression-ui
May 26, 2026
Merged

jonahgabriel merged 2 commits into
devfrom
jonah/omn-12072-swarm-progression-ui

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented May 25, 2026 •

Copy link
Copy Markdown
Contributor

OMN-12072 — Swarm Progression UI (B2-T1 through B2-T5)

Adds the Swarm Control Plane dashboard widget that renders swarm progression data from the swarm_runs projection.

Components

Task Component Description
B2-T1 SwarmDecompositionTree Parent→subtask tree with aggregate-derived per-subtask statuses, model hints cycled from models_used, graceful empty state when subtask_count=0
B2-T2 SwarmSubtaskCard Colored status badge, 3×3 metrics grid (subtasks, latency phases, cost/savings/speedup), models-used pill tags, registry hash footer
B2-T3 SwarmWaveVisualization Heuristic 60/40 wave split with dependency connector arrow; Wave 1 dimmed until run completes; empty state when subtask_count=0
B2-T4 SwarmLiveCounters 6 aggregate counter cards derived from swarm_runs (total runs, subtasks, avg speedup, wall time, cost, cumulative savings)
B2-T5 SwarmSavingsBar Stacked local+savings bar vs Opus-equivalent baseline, per-run mini bars (up to 10), graceful empty state for zero-cost rows

Supporting infrastructure

  • SwarmRunTable — 8-column run list (Status, Run ID, Subtasks, Speedup, Wall time, Cost, Savings, Created) with row selection
  • SwarmControlPlane — tabbed host widget (Overview, Decomposition, Run Detail, Wave View, Savings) with ComponentWrapper
  • shared/types/topics.ts — added swarmRuns: 'onex.snapshot.projection.swarm.runs.v1'
  • server/postgres-projection-reader.ts — added swarm_runs SELECT case
  • server/routes.ts — added GET /api/swarm-runs
  • src/services/swarm-api.ts — typed fetch client

Graceful degradation

The swarm_runs table currently has 12 rows with subtask_count=0, zero costs, and empty models_used — node_swarm_subtask_state_reducer isn't deployed yet. All components degrade with informative placeholder text explaining which projection is needed. Live counters and the savings bar will populate automatically once the projection materializes.

Tests

10 tests passing — all tabs, empty states, edge cases (zero subtasks, zero costs), and run selection.

dod_evidence

  • TypeScript: clean (npx tsc --noEmit)
  • ESLint: clean (all local/no-typography-inline and local/no-projection-fallback issues fixed)
  • Tests: 10/10 passing

Summary by CodeRabbit

  • New Features
    • Swarm Control Plane dashboard with tabs: live counters, decomposition tree, run/subtask detail, wave visualization, and savings breakdown
    • Live counters and a selectable swarm runs table with metrics and timestamps
    • New API endpoint to fetch swarm runs and a client-side adapter to consume it
    • Formatting helpers and types for swarm run data
  • Tests
    • Unit/UI tests covering dashboard views and subtask derivation logic

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 25, 2026 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 24aa23f6-724a-469a-8079-d47b20546639

📥 Commits

Reviewing files that changed from the base of the PR and between 6313fd7 and 0d42769.

📒 Files selected for processing (17)
  • server/postgres-projection-reader.ts
  • server/routes.ts
  • shared/types/topics.ts
  • src/components/dashboard/swarm-control-plane/SwarmControlPlane.test.tsx
  • src/components/dashboard/swarm-control-plane/SwarmControlPlane.tsx
  • src/components/dashboard/swarm-control-plane/SwarmDecompositionTree.tsx
  • src/components/dashboard/swarm-control-plane/SwarmLiveCounters.tsx
  • src/components/dashboard/swarm-control-plane/SwarmRunTable.tsx
  • src/components/dashboard/swarm-control-plane/SwarmSavingsBar.tsx
  • src/components/dashboard/swarm-control-plane/SwarmSubtaskCard.tsx
  • src/components/dashboard/swarm-control-plane/SwarmSubtaskDetailPanel.test.tsx
  • src/components/dashboard/swarm-control-plane/SwarmSubtaskDetailPanel.tsx
  • src/components/dashboard/swarm-control-plane/SwarmWaveVisualization.tsx
  • src/components/dashboard/swarm-control-plane/format.ts
  • src/components/dashboard/swarm-control-plane/swarm-control-plane.types.ts
  • src/components/dashboard/swarm-control-plane/useSwarmSnapshot.ts
  • src/services/swarm-api.ts

📝 Walkthrough

Walkthrough

This PR adds backend and frontend integration for monitoring swarm run executions. It includes PostgreSQL projection reader support, a REST API endpoint, typed service adapters, React data hooks, and a comprehensive tabbed dashboard UI with run tables, live metrics aggregation, subtask synthesis, visual decompositions, wave-based execution views, and cost/savings analysis.

Changes

Swarm Control Plane

Layer / File(s) Summary
Backend API support
server/postgres-projection-reader.ts, server/routes.ts, shared/types/topics.ts
Postgres projection reader now supports onex.snapshot.projection.swarm.runs.v1, selects from swarm_runs (ordered by created_at, limited), and Express exposes GET /api/swarm-runs with a 503 fallback when Postgres mode is unavailable.
Frontend service layer & data fetching
src/services/swarm-api.ts, src/components/dashboard/swarm-control-plane/useSwarmSnapshot.ts
New typed fetchSwarmRuns adapter and useSwarmSnapshot hook that queries TOPICS.swarmRuns with a 5s refetch, normalizes rows into runs[], and computes capturedAt plus normalized error/loading flags.
Shared types and formatting utilities
src/components/dashboard/swarm-control-plane/swarm-control-plane.types.ts, src/components/dashboard/swarm-control-plane/format.ts
Adds SwarmRunStatus/normalizeStatus, SwarmControlPlaneConfig, and formatters: fmtMs, fmtUsd, fmtDate, fmtRatio, fmtPct.
Main control plane component and tab orchestration
src/components/dashboard/swarm-control-plane/SwarmControlPlane.tsx, src/components/dashboard/swarm-control-plane/SwarmControlPlane.test.tsx
SwarmControlPlane wires snapshot hook and data-source mode, manages selected-run state, renders SwarmRunTable and SwarmEvidenceTabs (Live Counters, Decomposition, Run Detail, Subtask Detail, Wave, Savings); tests cover empty states, tab switching, edge cases, and interactions.
Run table with row selection
src/components/dashboard/swarm-control-plane/SwarmRunTable.tsx
SwarmRunTable renders a horizontally-scrollable table of runs with status dot, truncated ID, counts, speedup, wall time, cost, savings, created date; supports selection highlighting and maxRuns limiting.
Live counters aggregation panel
src/components/dashboard/swarm-control-plane/SwarmLiveCounters.tsx
SwarmLiveCounters computes aggregated metrics (runs, subtasks, avg speedup, wall time, cost, savings) and displays them as counter cards with conditional highlights and subtitles.
Subtask detail panel with synthesis logic
src/components/dashboard/swarm-control-plane/SwarmSubtaskDetailPanel.tsx, src/components/dashboard/swarm-control-plane/SwarmSubtaskDetailPanel.test.tsx
deriveSubtaskRows synthesizes per-subtask rows from aggregates (status sequencing, model cycling, cost distribution, latency estimate); SwarmSubtaskDetailPanel renders derived rows with status badges and a projection notice; tests validate derivation and rendering.
Decomposition tree visualization
src/components/dashboard/swarm-control-plane/SwarmDecompositionTree.tsx
SwarmDecompositionTree renders a RootNode and connected SubtaskNode entries with status colors, indices, model hints, and pending projection messaging when subtasks are absent.
Wave-based parallel execution visualization
src/components/dashboard/swarm-control-plane/SwarmWaveVisualization.tsx
SwarmWaveVisualization heuristically partitions subtasks into waves (~60% / remainder), renders WaveBlock/SubtaskSlot with status dots, latency labels, and waiting/completion indicators.
Individual subtask run detail card
src/components/dashboard/swarm-control-plane/SwarmSubtaskCard.tsx
SwarmSubtaskCard shows run id, status badge, metrics grid (subtasks/succeeded/failed, latencies, cost/savings, speedup), optional models-used list and registry info.
Cost and savings visualization
src/components/dashboard/swarm-control-plane/SwarmSavingsBar.tsx
SwarmSavingsBar aggregates local/cloud/savings costs, renders a stacked bar and legend, per-run breakdown (up to 10), and handles empty/zero-cost early-return states.

Sequence Diagram

sequenceDiagram
  participant Client
  participant Backend as Backend /api/swarm-runs
  participant Pg as Postgres
  participant ReactQuery as React Query
  participant useSwarmSnapshot as useSwarmSnapshot Hook
  participant SwarmControlPlane as SwarmControlPlane Component

  Client->>ReactQuery: Initial page load
  ReactQuery->>Backend: GET /api/swarm-runs
  Backend->>Pg: SELECT ... FROM swarm_runs ORDER BY created_at DESC LIMIT 200
  Pg-->>Backend: rows[]
  Backend-->>ReactQuery: { rows: [...] }
  ReactQuery-->>useSwarmSnapshot: data update
  useSwarmSnapshot->>useSwarmSnapshot: normalize runs, compute capturedAt
  useSwarmSnapshot-->>SwarmControlPlane: SwarmSnapshot { runs, isLoading, capturedAt }
  SwarmControlPlane->>SwarmControlPlane: select active run, render table & tabs
  SwarmControlPlane-->>Client: Render SwarmRunTable + EvidenceTabs
  Client->>SwarmControlPlane: Click run row
  SwarmControlPlane->>SwarmControlPlane: selectedRunId = clicked run
  SwarmControlPlane-->>Client: Re-render active tab with selected run
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • OmniNode-ai/omnidash#125: Adds the same swarm_runs projection reader case, /api/swarm-runs route, TOPICS.swarmRuns, and Swarm Control Plane UI components.

Poem

🐰 I hopped through rows and API lanes,
I counted runs and traced the chains,
Tabs and waves in tidy rows,
Savings bloom where insight grows,
A carrot for each tested case!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a Swarm Control Plane widget with five key visualization components (decomposition tree, wave view, live counters, savings bar, and run detail) for displaying swarm progression data.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jonah/omn-12072-swarm-progression-ui

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
src/components/dashboard/swarm-control-plane/SwarmControlPlane.test.tsx (1)

87-155: ⚡ Quick win

Add explicit coverage for the Subtask Detail tab path.

The suite validates overview, decomposition, run detail, wave, and savings, but it doesn’t exercise the subtask-rows (Subtask Detail) branch currently wired in SwarmControlPlane.tsx. A small test here would catch tab-routing regressions early.

Proposed test addition
@@
   it('shows wave visualization in wave tab', () => {
@@
   });
+
+  it('shows subtask detail panel in subtask detail tab', () => {
+    mockUseSwarmSnapshot.mockReturnValue({
+      ...EMPTY_SNAPSHOT,
+      runs: [makeRun()],
+      hasAnyData: true,
+    });
+    render(<SwarmControlPlane config={{}} />);
+    fireEvent.click(screen.getByRole('tab', { name: /Subtask Detail/i }));
+    expect(screen.getByText(/derived from aggregates/i)).toBeTruthy();
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/dashboard/swarm-control-plane/SwarmControlPlane.test.tsx`
around lines 87 - 155, Add a new test in SwarmControlPlane.test.tsx that
exercises the "Subtask Detail" tab path: mockUseSwarmSnapshot to return a
snapshot with runs (use makeRun()) that include at least one subtask entry,
render(<SwarmControlPlane config={{}} />), then fireEvent.click the tab labeled
"Subtask Detail" (or the element that triggers the subtask-rows branch), and
assert that elements expected from the subtask-rows branch (e.g., subtask IDs,
statuses, or specific text present in the UI) are rendered; target the
SwarmControlPlane component and the subtask-rows branch so the test fails if tab
routing or rendering for Subtask Detail regresses.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/dashboard/swarm-control-plane/format.ts`:
- Around line 7-11: fmtUsd currently treats negative numbers as if they were
tiny positives (e.g., -12.34 hits the <0.001 branch); fix by extracting the sign
and using the absolute value for threshold checks and toFixed formatting inside
fmtUsd: compute sign = usd < 0 ? '-' : '' and abs = Math.abs(usd), keep the zero
special-case (compare abs === 0), then apply thresholds against abs and return
the formatted string with sign placed before the dollar sign (e.g.,
`${sign}$${abs.toFixed(...)}`).

In `@src/components/dashboard/swarm-control-plane/SwarmDecompositionTree.tsx`:
- Around line 119-128: The per-subtask status derivation in
SwarmDecompositionTree.tsx (the loop building perSubtaskStatus) currently
ignores run.skipped_count; update the logic in that loop to account for skipped
subtasks by allocating indices after succeeded_count and failed_count for
skipped_count, and only mark remaining ones as 'pending' (or 'skipped'/'pending'
based on isComplete) accordingly; use the existing symbols run.subtask_count,
run.succeeded_count, run.failed_count, run.skipped_count and isComplete to
compute thresholds (succeeded -> failed -> skipped -> pending) so skipped
subtasks are assigned 'skipped' rather than misclassified.

In `@src/components/dashboard/swarm-control-plane/SwarmLiveCounters.tsx`:
- Line 52: The succeededRuns calculation in SwarmLiveCounters uses a
case-sensitive check (const succeededRuns = runs.filter((r) => r.status ===
'succeeded').length) and may miss variants like 'Succeeded' or 'SUCCEEDED';
update the filter to normalize r.status (e.g.,
r.status?.toString().toLowerCase()) before comparison and guard for undefined
statuses so succeededRuns correctly counts all case variants of 'succeeded'.

In `@src/components/dashboard/swarm-control-plane/SwarmRunTable.tsx`:
- Around line 64-71: The table row in SwarmRunTable is only mouse-clickable;
update the <tr> that uses run.run_id and onSelectRun to be keyboard-accessible
by adding tabIndex={0}, a keyDown handler that calls onSelectRun(run.run_id)
when Enter or Space is pressed (preventDefault on Space to avoid scrolling), and
appropriate ARIA semantics such as role="row" (or role="button" if more
appropriate) and aria-selected={isSelected} so screen readers and keyboard users
can activate/select rows the same way as mouse users.

In `@src/components/dashboard/swarm-control-plane/SwarmSavingsBar.tsx`:
- Around line 25-37: The code treats runs[].savings_usd as always non-negative,
which makes the "saved" label and percentages misleading when some runs have
negative savings_usd; update the UI logic in SwarmSavingsBar (references: runs,
totalSavings, totalBaseline, localPct, savingsPct, fmtPct, fmtUsd) to compute
two values: totalPositiveSavings = sum of max(0, r.savings_usd) and
totalNegativeSavings = sum of min(0, r.savings_usd), derive savingsPct from
totalPositiveSavings (and/or show negative percent separately), and change the
displayed label to conditionally render "saved (…)" when totalPositiveSavings >
0 or "over baseline (…)" when totalNegativeSavings < 0, ensuring fmtPct and
fmtUsd are passed the appropriate non-negative or negative totals so percentages
and text match the sign.

In `@src/components/dashboard/swarm-control-plane/SwarmSubtaskDetailPanel.tsx`:
- Around line 24-65: The deriveSubtaskRows function in SwarmSubtaskDetailPanel
synthesizes authoritative per-subtask state (status, costUsd, latencyMs, model)
from aggregate fields on SwarmRunRow; move this logic out of the widget so the
component only renders API/projection data. Replace deriveSubtaskRows with a
thin adapter that expects an explicit array of SwarmSubtaskRow-like objects
provided by upstream (or add a new run.subtasks: SwarmSubtaskRow[] projection on
the API/selector), remove in-component inference of perSubtaskCost,
estimatedSubtaskLatency, status derivation, and model selection, and update all
callers to supply the authoritative subtask array (or call the new projection
helper) instead of relying on deriveSubtaskRows.

In `@src/services/swarm-api.ts`:
- Around line 52-63: The fetchJson/fetchSwarmRuns implementation currently calls
fetch() directly and bypasses the project's data-source abstraction; replace the
direct HTTP call with the approved data-source/projection-query path by
importing and using the snapshot/data-source client (the abstraction selected by
VITE_DATA_SOURCE) instead of fetch and resolveBase. Specifically, update
fetchSwarmRuns (and fetchJson helper) to call the appropriate data-source API or
projection query method (the module exported by src/data-source/* that returns
SwarmRunsResponse) via SwarmApiOptions/resolveBase-aware plumbing, ensure no
hardcoded localhost:3002 remains, and preserve the
SwarmRunsResponse/SwarmApiOptions types when mapping the returned value.

---

Nitpick comments:
In `@src/components/dashboard/swarm-control-plane/SwarmControlPlane.test.tsx`:
- Around line 87-155: Add a new test in SwarmControlPlane.test.tsx that
exercises the "Subtask Detail" tab path: mockUseSwarmSnapshot to return a
snapshot with runs (use makeRun()) that include at least one subtask entry,
render(<SwarmControlPlane config={{}} />), then fireEvent.click the tab labeled
"Subtask Detail" (or the element that triggers the subtask-rows branch), and
assert that elements expected from the subtask-rows branch (e.g., subtask IDs,
statuses, or specific text present in the UI) are rendered; target the
SwarmControlPlane component and the subtask-rows branch so the test fails if tab
routing or rendering for Subtask Detail regresses.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 212972b4-ebb9-42ae-8938-811101b98ffd

📥 Commits

Reviewing files that changed from the base of the PR and between 1404040 and 6313fd7.

📒 Files selected for processing (17)
  • server/postgres-projection-reader.ts
  • server/routes.ts
  • shared/types/topics.ts
  • src/components/dashboard/swarm-control-plane/SwarmControlPlane.test.tsx
  • src/components/dashboard/swarm-control-plane/SwarmControlPlane.tsx
  • src/components/dashboard/swarm-control-plane/SwarmDecompositionTree.tsx
  • src/components/dashboard/swarm-control-plane/SwarmLiveCounters.tsx
  • src/components/dashboard/swarm-control-plane/SwarmRunTable.tsx
  • src/components/dashboard/swarm-control-plane/SwarmSavingsBar.tsx
  • src/components/dashboard/swarm-control-plane/SwarmSubtaskCard.tsx
  • src/components/dashboard/swarm-control-plane/SwarmSubtaskDetailPanel.test.tsx
  • src/components/dashboard/swarm-control-plane/SwarmSubtaskDetailPanel.tsx
  • src/components/dashboard/swarm-control-plane/SwarmWaveVisualization.tsx
  • src/components/dashboard/swarm-control-plane/format.ts
  • src/components/dashboard/swarm-control-plane/swarm-control-plane.types.ts
  • src/components/dashboard/swarm-control-plane/useSwarmSnapshot.ts
  • src/services/swarm-api.ts

Comment on lines +7 to +11
export function fmtUsd(usd: number): string {
if (usd === 0) return '$0.00';
if (usd < 0.001) return `$${usd.toFixed(5)}`;
if (usd < 0.01) return `$${usd.toFixed(4)}`;
return `$${usd.toFixed(2)}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle negative USD values before tiny-value precision branches.

Negative amounts currently hit the < 0.001 branch, which over-formats values like -12.34 as five decimals. Use absolute value for thresholds and apply sign separately.

💡 Suggested fix
 export function fmtUsd(usd: number): string {
-  if (usd === 0) return '$0.00';
-  if (usd < 0.001) return `$${usd.toFixed(5)}`;
-  if (usd < 0.01) return `$${usd.toFixed(4)}`;
-  return `$${usd.toFixed(2)}`;
+  if (usd === 0) return '$0.00';
+  const sign = usd < 0 ? '-' : '';
+  const abs = Math.abs(usd);
+  if (abs < 0.001) return `-${'$'}${abs.toFixed(5)}`.replace('-$', `${sign}$`);
+  if (abs < 0.01) return `-${'$'}${abs.toFixed(4)}`.replace('-$', `${sign}$`);
+  return `${sign}$${abs.toFixed(2)}`;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function fmtUsd(usd: number): string {
if (usd === 0) return '$0.00';
if (usd < 0.001) return `$${usd.toFixed(5)}`;
if (usd < 0.01) return `$${usd.toFixed(4)}`;
return `$${usd.toFixed(2)}`;
export function fmtUsd(usd: number): string {
if (usd === 0) return '$0.00';
const sign = usd < 0 ? '-' : '';
const abs = Math.abs(usd);
if (abs < 0.001) return `-${'$'}${abs.toFixed(5)}`.replace('-$', `${sign}$`);
if (abs < 0.01) return `-${'$'}${abs.toFixed(4)}`.replace('-$', `${sign}$`);
return `${sign}$${abs.toFixed(2)}`;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/dashboard/swarm-control-plane/format.ts` around lines 7 - 11,
fmtUsd currently treats negative numbers as if they were tiny positives (e.g.,
-12.34 hits the <0.001 branch); fix by extracting the sign and using the
absolute value for threshold checks and toFixed formatting inside fmtUsd:
compute sign = usd < 0 ? '-' : '' and abs = Math.abs(usd), keep the zero
special-case (compare abs === 0), then apply thresholds against abs and return
the formatted string with sign placed before the dollar sign (e.g.,
`${sign}$${abs.toFixed(...)}`).

Comment on lines +119 to +128
// Derive per-subtask statuses from aggregate counts
const perSubtaskStatus: string[] = [];
for (let i = 0; i < run.subtask_count; i++) {
if (i < run.succeeded_count) {
perSubtaskStatus.push('succeeded');
} else if (i < run.succeeded_count + run.failed_count) {
perSubtaskStatus.push('failed');
} else {
perSubtaskStatus.push(isComplete ? 'skipped' : 'pending');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Respect skipped_count when deriving per-subtask statuses.

The derivation ignores run.skipped_count, so skipped subtasks can be rendered as pending/skipped incorrectly depending on completion state.

Proposed fix
   for (let i = 0; i < run.subtask_count; i++) {
     if (i < run.succeeded_count) {
       perSubtaskStatus.push('succeeded');
     } else if (i < run.succeeded_count + run.failed_count) {
       perSubtaskStatus.push('failed');
+    } else if (i < run.succeeded_count + run.failed_count + run.skipped_count) {
+      perSubtaskStatus.push('skipped');
     } else {
       perSubtaskStatus.push(isComplete ? 'skipped' : 'pending');
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Derive per-subtask statuses from aggregate counts
const perSubtaskStatus: string[] = [];
for (let i = 0; i < run.subtask_count; i++) {
if (i < run.succeeded_count) {
perSubtaskStatus.push('succeeded');
} else if (i < run.succeeded_count + run.failed_count) {
perSubtaskStatus.push('failed');
} else {
perSubtaskStatus.push(isComplete ? 'skipped' : 'pending');
}
// Derive per-subtask statuses from aggregate counts
const perSubtaskStatus: string[] = [];
for (let i = 0; i < run.subtask_count; i++) {
if (i < run.succeeded_count) {
perSubtaskStatus.push('succeeded');
} else if (i < run.succeeded_count + run.failed_count) {
perSubtaskStatus.push('failed');
} else if (i < run.succeeded_count + run.failed_count + run.skipped_count) {
perSubtaskStatus.push('skipped');
} else {
perSubtaskStatus.push(isComplete ? 'skipped' : 'pending');
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/dashboard/swarm-control-plane/SwarmDecompositionTree.tsx`
around lines 119 - 128, The per-subtask status derivation in
SwarmDecompositionTree.tsx (the loop building perSubtaskStatus) currently
ignores run.skipped_count; update the logic in that loop to account for skipped
subtasks by allocating indices after succeeded_count and failed_count for
skipped_count, and only mark remaining ones as 'pending' (or 'skipped'/'pending'
based on isComplete) accordingly; use the existing symbols run.subtask_count,
run.succeeded_count, run.failed_count, run.skipped_count and isComplete to
compute thresholds (succeeded -> failed -> skipped -> pending) so skipped
subtasks are assigned 'skipped' rather than misclassified.

? runs.reduce((s, r) => s + r.parallelism_speedup_ratio, 0) / runs.length
: 1;

const succeededRuns = runs.filter((r) => r.status === 'succeeded').length;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize status before counting succeeded runs.

Line 52 uses a case-sensitive raw string check, which can undercount succeeded runs if status casing varies.

🔧 Suggested fix
+import { normalizeStatus } from './swarm-control-plane.types';
...
-  const succeededRuns = runs.filter((r) => r.status === 'succeeded').length;
+  const succeededRuns = runs.filter((r) => normalizeStatus(r.status) === 'succeeded').length;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const succeededRuns = runs.filter((r) => r.status === 'succeeded').length;
import { normalizeStatus } from './swarm-control-plane.types';
const succeededRuns = runs.filter((r) => normalizeStatus(r.status) === 'succeeded').length;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/dashboard/swarm-control-plane/SwarmLiveCounters.tsx` at line
52, The succeededRuns calculation in SwarmLiveCounters uses a case-sensitive
check (const succeededRuns = runs.filter((r) => r.status ===
'succeeded').length) and may miss variants like 'Succeeded' or 'SUCCEEDED';
update the filter to normalize r.status (e.g.,
r.status?.toString().toLowerCase()) before comparison and guard for undefined
statuses so succeededRuns correctly counts all case variants of 'succeeded'.

Comment on lines +64 to +71
<tr
key={run.run_id}
onClick={() => onSelectRun(run.run_id)}
style={{
cursor: 'pointer',
background: isSelected ? 'var(--panel-2)' : 'transparent',
borderBottom: '1px solid var(--line-2)',
}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add keyboard-accessible row selection for interactive table rows.

Line 66 makes <tr> mouse-clickable only; keyboard users cannot select a run. Add tabIndex, keyboard activation (Enter/Space), and selection semantics.

♿ Suggested fix
               <tr
                 key={run.run_id}
                 onClick={() => onSelectRun(run.run_id)}
+                onKeyDown={(e) => {
+                  if (e.key === 'Enter' || e.key === ' ') {
+                    e.preventDefault();
+                    onSelectRun(run.run_id);
+                  }
+                }}
+                tabIndex={0}
+                role="button"
+                aria-pressed={isSelected}
                 style={{
                   cursor: 'pointer',
                   background: isSelected ? 'var(--panel-2)' : 'transparent',
                   borderBottom: '1px solid var(--line-2)',
                 }}
               >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<tr
key={run.run_id}
onClick={() => onSelectRun(run.run_id)}
style={{
cursor: 'pointer',
background: isSelected ? 'var(--panel-2)' : 'transparent',
borderBottom: '1px solid var(--line-2)',
}}
<tr
key={run.run_id}
onClick={() => onSelectRun(run.run_id)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onSelectRun(run.run_id);
}
}}
tabIndex={0}
role="button"
aria-pressed={isSelected}
style={{
cursor: 'pointer',
background: isSelected ? 'var(--panel-2)' : 'transparent',
borderBottom: '1px solid var(--line-2)',
}}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/dashboard/swarm-control-plane/SwarmRunTable.tsx` around lines
64 - 71, The table row in SwarmRunTable is only mouse-clickable; update the <tr>
that uses run.run_id and onSelectRun to be keyboard-accessible by adding
tabIndex={0}, a keyDown handler that calls onSelectRun(run.run_id) when Enter or
Space is pressed (preventDefault on Space to avoid scrolling), and appropriate
ARIA semantics such as role="row" (or role="button" if more appropriate) and
aria-selected={isSelected} so screen readers and keyboard users can
activate/select rows the same way as mouse users.

Comment on lines +25 to +37
const totalSavings = runs.reduce((s, r) => s + r.savings_usd, 0);
const totalBaseline = Math.max(totalLocal + totalSavings, totalCloud);

const localPct = totalBaseline > 0 ? (totalLocal / totalBaseline) * 100 : 0;
const savingsPct = totalBaseline > 0 ? (totalSavings / totalBaseline) * 100 : 0;

return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
<Text as="span" size="sm" weight="semibold" color="primary">Actual vs Opus-equivalent cost</Text>
<Text as="span" size="xs" family="mono" color="ok">
{fmtPct(totalSavings, totalBaseline)} saved ({fmtUsd(totalSavings)})
</Text>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle negative savings_usd explicitly (over-baseline runs).

Current logic assumes savings is non-negative, so negative values can render misleading “saved” messaging and percentages.

Proposed fix
-  const totalSavings = runs.reduce((s, r) => s + r.savings_usd, 0);
+  const totalSavings = runs.reduce((s, r) => s + r.savings_usd, 0);
+  const hasNetSavings = totalSavings >= 0;
+  const savingsAbs = Math.max(totalSavings, 0);
+  const overrunAbs = Math.max(-totalSavings, 0);
   const totalBaseline = Math.max(totalLocal + totalSavings, totalCloud);

   const localPct = totalBaseline > 0 ? (totalLocal / totalBaseline) * 100 : 0;
-  const savingsPct = totalBaseline > 0 ? (totalSavings / totalBaseline) * 100 : 0;
+  const savingsPct = totalBaseline > 0 ? (savingsAbs / totalBaseline) * 100 : 0;
@@
-        <Text as="span" size="xs" family="mono" color="ok">
-          {fmtPct(totalSavings, totalBaseline)} saved ({fmtUsd(totalSavings)})
+        <Text as="span" size="xs" family="mono" color={hasNetSavings ? 'ok' : 'bad'}>
+          {hasNetSavings
+            ? `${fmtPct(savingsAbs, totalBaseline)} saved (${fmtUsd(savingsAbs)})`
+            : `${fmtPct(overrunAbs, totalBaseline)} over baseline (${fmtUsd(overrunAbs)})`}
         </Text>
@@
-        {savingsPct > 0 && (
+        {hasNetSavings && savingsPct > 0 && (
@@
-            title={`Savings: ${fmtUsd(totalSavings)}`}
+            title={`Savings: ${fmtUsd(savingsAbs)}`}
@@
-              <Text as="span" size="xs" family="mono" color={run.savings_usd > 0 ? 'ok' : 'tertiary'} style={{ flex: '0 0 60px', textAlign: 'right' }}>
-                {run.savings_usd > 0 ? `−${fmtUsd(run.savings_usd)}` : run.total_cost_usd > 0 ? fmtUsd(run.total_cost_usd) : '-'}
+              <Text
+                as="span"
+                size="xs"
+                family="mono"
+                color={run.savings_usd > 0 ? 'ok' : run.savings_usd < 0 ? 'bad' : 'tertiary'}
+                style={{ flex: '0 0 60px', textAlign: 'right' }}
+              >
+                {run.savings_usd > 0
+                  ? `−${fmtUsd(run.savings_usd)}`
+                  : run.savings_usd < 0
+                  ? `+${fmtUsd(Math.abs(run.savings_usd))}`
+                  : run.total_cost_usd > 0
+                  ? fmtUsd(run.total_cost_usd)
+                  : '-'}
               </Text>

Also applies to: 67-83, 95-116

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/dashboard/swarm-control-plane/SwarmSavingsBar.tsx` around
lines 25 - 37, The code treats runs[].savings_usd as always non-negative, which
makes the "saved" label and percentages misleading when some runs have negative
savings_usd; update the UI logic in SwarmSavingsBar (references: runs,
totalSavings, totalBaseline, localPct, savingsPct, fmtPct, fmtUsd) to compute
two values: totalPositiveSavings = sum of max(0, r.savings_usd) and
totalNegativeSavings = sum of min(0, r.savings_usd), derive savingsPct from
totalPositiveSavings (and/or show negative percent separately), and change the
displayed label to conditionally render "saved (…)" when totalPositiveSavings >
0 or "over baseline (…)" when totalNegativeSavings < 0, ensuring fmtPct and
fmtUsd are passed the appropriate non-negative or negative totals so percentages
and text match the sign.

Comment on lines +24 to +65
export function deriveSubtaskRows(run: SwarmRunRow): SwarmSubtaskRow[] {
if (run.subtask_count === 0) return [];

const rows: SwarmSubtaskRow[] = [];
const runStatus = normalizeStatus(run.status);
const isComplete = runStatus === 'succeeded' || runStatus === 'failed';

// Distribute aggregate costs/latency evenly across subtasks as estimates
const perSubtaskCost = run.total_cost_usd > 0
? run.total_cost_usd / run.subtask_count
: null;
// Estimate individual latency: dispatch time split evenly, plus aggregation overhead
const estimatedSubtaskLatency = run.dispatch_wall_latency_ms > 0
? Math.round(run.dispatch_wall_latency_ms / Math.max(1, run.subtask_count / (run.parallelism_speedup_ratio || 1)))
: null;

for (let i = 0; i < run.subtask_count; i++) {
let status: SwarmSubtaskRow['status'];
if (i < run.succeeded_count) {
status = 'succeeded';
} else if (i < run.succeeded_count + run.failed_count) {
status = 'failed';
} else if (i < run.succeeded_count + run.failed_count + run.skipped_count) {
status = 'skipped';
} else {
status = isComplete ? 'skipped' : 'pending';
}

const model = run.models_used.length > 0
? run.models_used[i % run.models_used.length]
: 'unknown';

rows.push({
subtaskId: `${run.run_id.slice(0, 8)}-st${String(i + 1).padStart(2, '0')}`,
description: `Subtask ${String(i + 1).padStart(2, '0')}`,
model,
status,
tokensInput: null,
tokensOutput: null,
costUsd: perSubtaskCost,
latencyMs: status === 'succeeded' || status === 'failed' ? estimatedSubtaskLatency : null,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Move authoritative per-subtask derivation out of the widget layer.

deriveSubtaskRows synthesizes status/cost/latency/model rows from aggregates. That makes the component own inferred state instead of rendering projection/API truth, so displayed subtask details can drift from backend reality.

As per coding guidelines, "Widgets may render projection/API data and presentation state only. They must not ... implement reducers, or infer authoritative state ... Add upstream projection/API surface before adding component-side reducer or backend client."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/dashboard/swarm-control-plane/SwarmSubtaskDetailPanel.tsx`
around lines 24 - 65, The deriveSubtaskRows function in SwarmSubtaskDetailPanel
synthesizes authoritative per-subtask state (status, costUsd, latencyMs, model)
from aggregate fields on SwarmRunRow; move this logic out of the widget so the
component only renders API/projection data. Replace deriveSubtaskRows with a
thin adapter that expects an explicit array of SwarmSubtaskRow-like objects
provided by upstream (or add a new run.subtasks: SwarmSubtaskRow[] projection on
the API/selector), remove in-component inference of perSubtaskCost,
estimatedSubtaskLatency, status derivation, and model selection, and update all
callers to supply the authoritative subtask array (or call the new projection
helper) instead of relying on deriveSubtaskRows.

Comment thread src/services/swarm-api.ts
Comment on lines +52 to +63
async function fetchJson<T>(path: string): Promise<T> {
const res = await fetch(path);
if (!res.ok) return { rows: [] } as T;
const body = (await res.json()) as unknown;
return body as T;
}

// ── Public API ───────────────────────────────────────────────────────────────

export function fetchSwarmRuns(opts?: SwarmApiOptions): Promise<SwarmRunsResponse> {
return fetchJson<SwarmRunsResponse>(`${resolveBase(opts)}/swarm-runs`);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Route swarm data access through src/data-source abstractions.

This adapter calls fetch('/api/swarm-runs') directly, which bypasses the configured snapshot-source path and can diverge from VITE_DATA_SOURCE behavior. Please move this through the approved src/data-source/ abstraction flow (or consume the existing projection-query path).

As per coding guidelines **/*.{ts,tsx}: Do NOT hardcode localhost:3002 anywhere. All data access must go through src/data-source/ abstractions (FileSnapshotSource or HttpSnapshotSource), selected by VITE_DATA_SOURCE environment variable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/swarm-api.ts` around lines 52 - 63, The fetchJson/fetchSwarmRuns
implementation currently calls fetch() directly and bypasses the project's
data-source abstraction; replace the direct HTTP call with the approved
data-source/projection-query path by importing and using the
snapshot/data-source client (the abstraction selected by VITE_DATA_SOURCE)
instead of fetch and resolveBase. Specifically, update fetchSwarmRuns (and
fetchJson helper) to call the appropriate data-source API or projection query
method (the module exported by src/data-source/* that returns SwarmRunsResponse)
via SwarmApiOptions/resolveBase-aware plumbing, ensure no hardcoded
localhost:3002 remains, and preserve the SwarmRunsResponse/SwarmApiOptions types
when mapping the returned value.

@jonahgabriel

Copy link
Copy Markdown
Contributor Author

Phase 0 quarantine (OMN-12238): Deferred from the May 25 integration release. Merge conflict + 7 unresolved CR threads (including 2 coding guideline violations). Widget shows placeholder data regardless — no integration dependency. Will re-queue after dev-target cutover.

Implements the full swarm progression UI against the swarm_runs projection:
- B2-T1: SwarmDecompositionTree — parent→subtask tree with aggregate-derived
  per-subtask statuses and model hints; graceful empty state when subtask_count=0
- B2-T2: SwarmSubtaskCard — colored status badge, 3×3 metrics grid, models-used
  pill tags, registry hash footer
- B2-T3: SwarmWaveVisualization — heuristic 60/40 wave split with dependency
  connector; Wave 1 dimmed until run complete; empty state when subtask_count=0
- B2-T4: SwarmLiveCounters — 6 aggregate cards (total runs, subtasks, avg speedup,
  wall time, cost, savings) derived from swarm_runs projection
- B2-T5: SwarmSavingsBar — stacked local+savings bar vs cloud equivalent baseline,
  per-run mini bars (up to 10), zero-cost graceful empty state
- SwarmRunTable: 8-column run list with row selection, speedup/savings coloring
- SwarmControlPlane: tabbed widget (Overview, Decomposition, Run Detail, Wave View,
  Savings) wired to swarm.runs.v1 projection topic via useProjectionQuery
- Adds TOPICS.swarmRuns, swarm-runs.v1 postgres case, GET /api/swarm-runs route
- 10 tests passing
…ns/cost/latency table (B5-T3 gap)

Adds SwarmSubtaskDetailPanel showing per-subtask rows with:
- subtask_id (derived: run_id prefix + padded index)
- description (Subtask 01, 02, ...)
- model assigned (cycled from run.models_used)
- status (succeeded/failed/skipped/pending, derived from aggregate counts)
- tokens_input / tokens_output (null — requires node_swarm_subtask_state_reducer)
- cost_usd (evenly distributed from total_cost_usd)
- latency_ms (estimated from dispatch_wall_latency_ms / parallelism)

Panel is clearly marked as aggregate-derived with a fixture notice.
Shows "No subtask data" with reducer name when subtask_count=0.

Wired as new "Subtask Detail" tab in SwarmControlPlane alongside existing
"Run Detail" aggregate card. Uses List icon from lucide-react.

Schema confirmed: swarm_runs has no jsonb subtask column and no separate
swarm_subtasks table exists yet in stability-test. All per-subtask data
requires node_swarm_subtask_state_reducer projection.

15 new tests + 10 existing passing (25 total).
@jonahgabriel
jonahgabriel force-pushed the jonah/omn-12072-swarm-progression-ui branch from 6313fd7 to 0d42769 Compare May 26, 2026 12:47
@jonahgabriel
jonahgabriel merged commit eecd8fa into dev May 26, 2026
12 of 17 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-12072-swarm-progression-ui branch May 26, 2026 12:47
jonahgabriel added a commit that referenced this pull request Jun 6, 2026
* feat(OMN-11979): redesign delegation dashboard — dense metrics, narrative labels, stale .js cleanup (#119)

- Replace donut chart with terminal-style KPI tiles + stacked model bar + task breakdown
- Rename Quality Gate → Escalation Detail, humanize failure categories
- Fix postgres-projection-reader: correct column names, JSONB string coercion
- Add /projection Vite proxy for Express bridge
- Remove 8 stale compiled .js files that shadowed .tsx source via Vite resolution
- "Quality Gate" → "Local Success", "pass/fail" → "local/escalated" throughout

* feat(OMN-12069): delegation correlation trace view

Adds the delegation correlation trace view, trigger panel, and dashboard assertion updates needed for CI.

* feat(OMN-12133): add projection query endpoints for log entries and traces (#122)

Adds GET /api/projections/log-entries and GET /api/projections/traces
to the Express server, backed by new queryLogEntries and queryTraces
methods on PostgresProjectionReader. Adds traceExplorer topic constant
to shared/types/topics.ts. All query params are validated; limits are
capped (1000 for entries, 500 for traces). is_running = true when
last_event_at < 60s ago. 17 new unit tests + 2 route-level tests.

* feat(OMN-12089): B6 delegation savings proof pack — printable audit artifact (#123)

Adds DelegationSavingsProofPackPanel as a new 'Savings Proof Pack' tab in
the delegation control plane evidence tabs. Renders an exportable audit
report from live projection data: correlation IDs for every run, routing
decisions (model, rule, confidence), quality gate results (pass/fail +
detail), pricing manifest version, per-run savings, cumulative savings
summary, and a pass-rate totals row.

Print/Save as PDF is handled via window.print() with an injected @media
print stylesheet that isolates the printable region to the proof pack table.
No server-side PDF generation or new backend endpoints required — all data
sourced from DelegationEvidenceSnapshot already in context.

7 tests covering empty state, header rendering, run table, quality gate
labels, routing display, totals row, and print button.

* feat(OMN-12085): add swarm-runs projection topic to PostgresProjectionReader (#124)

Adds onex.snapshot.projection.swarm-runs.v1 case to the Postgres projection
reader. Queries omnidash_analytics.swarm_runs and returns all T4 display
fields: runId, status, subtask/succeeded/failed counts, modelsUsed,
totalCostUsd, cloudEquivalentCostUsd, savingsUsd, parallelismSpeedupRatio,
all latency fields, and createdAt. The swarm_runs table already exists on
stability-test (15436) with 11 rows.

* feat(OMN-12135): add TraceExplorerWidget — two-panel event bus trace explorer (#126)

Adds TraceExplorerWidget to omnidash for event bus observability Phase 2.
The widget consumes onex.snapshot.projection.traces.v1 to render a
left-panel trace list (with running/error indicators) and right-panel
chronological timeline of log entries for the selected trace.

- TraceExplorerWidget.tsx: two-panel layout, trace cards, timeline, search
- TraceExplorerWidget.stories.tsx: Empty, Populated, WithRunningTrace, ErrorTraces, Loading
- TraceExplorerWidget.test.tsx: 5 unit tests (loading, empty, render, filter, select prompt)
- storybook/fixtures/trace-explorer.ts: buildTraceGroups fixture factory
- generate-registry.ts: trace-explorer manifest with projectionSchema + displayContract
- index.ts: lazy import wired under trace-explorer/TraceExplorerWidget
- integration.part2/3: component count bumped 28→29 with trace-explorer

Closes OMN-12135. References OMN-12136, OMN-12137, OMN-12138, OMN-12139.

* feat(OMN-12082): add Context Effectiveness Heatmap widget (B4) (#127)

Implements the context factor effectiveness visualization showing which
context segments help/hurt which models across A3 ablation experiments:

- Segment × model matrix with color-coded cells:
  green (≥75% pass rate), amber (26–74%), red (≤25%)
- Clickable cells open an examples panel showing specific token impact,
  pass/fail counts, and qualitative findings per segment×model combination
- Fixture mode: when no live data exists, shows OMN-11241 research results
  (golden_chain green across all models, claude_md/architecture_patterns
  red for local models, exemplar green for exact-interface tasks,
  local_failures neutral)
- Fixture badge clearly marks when live projection is absent
- Reads from context_experiment_scores table via new
  onex.snapshot.projection.context.experiment-scores.v1 topic
- Adds postgres reader case for context_experiment_scores SELECT
- 10 tests passing (empty state, matrix render, cell selection, fixture
  badge, toggle deselect, footer count)

* feat(OMN-12145): add Express dispatch bridge and Kafka command topic (#128)

- POST /api/dispatch validates body, builds envelope, publishes to
  onex.cmd.omnidash.dispatch-request.v1 via kafkajs producer
- Returns 400 on missing fields, 503 when broker unavailable
- Kafka producer connects on server start, graceful disconnect on SIGTERM/SIGINT
- COMMAND_TOPICS registry in shared/types/command-topics.ts (separate from
  TOPICS to preserve golden-chain onex.snapshot.projection.* invariant)
- 9 unit tests with mocked producer covering validation, 503, and happy path

Closes OMN-12145, OMN-12149

* feat(OMN-12147): add command dispatch UI — trace action buttons + Cmd+K palette (#129)

- New `command-dispatch/` directory with:
  - `useDispatch.ts` hook: POST /api/dispatch, availability probe, error/loading state
  - `DispatchButton.tsx`: icon button (RotateCw/Square) with loading/success/error states
  - `CommandPalette.tsx`: Cmd+K/Ctrl+K overlay with node search, JSON payload editor, dispatch result; includes `useCommandPalette` hook for global keyboard listener
  - `CommandPalette.stories.tsx`: Open, WithResults, Dispatching, Error story exports
- `TraceExplorerWidget.tsx`: adds Stop button on running traces, Re-run button on completed traces
- `App.tsx`: mounts `CommandPalette` at app root via `useCommandPalette` hook

Closes OMN-12147, relates OMN-12148

* fix(OMN-12149): align dispatch command topic to omnimarket namespace (#131)

The node_dispatch_request_handler subscribes to
onex.cmd.omnimarket.dispatch-request.v1 (topic naming lint requires
consumer service namespace). Aligns the Express bridge publisher topic.

* chore(OMN-12171): delete orphaned .js files and add CI enforcement gate (#132)

* chore(OMN-12171): delete orphaned .js files and add CI enforcement gate

Confirms 298 .js artifacts in src/ and shared/ are stale (all have .ts/.tsx
counterparts; tsconfig emits nothing to src/; build output goes to dist/).
Deletes them from working tree.

Adds scripts/check-no-orphaned-js.sh — fails if any .js file in src/ or shared/
shadows a .ts/.tsx source — and wires it as a CI gate in ci.yml.

Also fixes broken @storybook/test import in CommandPalette.stories.tsx
(package moved to storybook/test in v10), restoring tsc --noEmit clean pass.

* fix(OMN-12171): include command topic in server typecheck

* refactor(OMN-12187): Consolidate DRY violations in omnidash

* refactor(OMN-12187): Consolidate DRY violations in omnidash

Extract shared pure-data helpers (timestampValue, mergeDelegationSessions,
buildCostSavingsOverviewResult) into server/projection-reader-shared.ts.
Both sqlite-projection-reader.ts and postgres-projection-reader.ts delegate
to the shared module, eliminating ~120 lines of duplicated logic.

Add 13 unit tests for the shared module covering edge cases in timestamp
parsing, session merge semantics, and cost overview aggregation.

The integration test files (part2/part3/part4) are distinct suites with
different test content — they are not duplicates and are left intact.

* fix(OMN-12187): remove unused cost overview input

* feat(OMN-12072): Swarm Control Plane widget — decomposition tree, wave view, live counters, savings bar (#125)

* feat(OMN-12072): add Swarm Control Plane widget (B2-T1 through B2-T5)

Implements the full swarm progression UI against the swarm_runs projection:
- B2-T1: SwarmDecompositionTree — parent→subtask tree with aggregate-derived
  per-subtask statuses and model hints; graceful empty state when subtask_count=0
- B2-T2: SwarmSubtaskCard — colored status badge, 3×3 metrics grid, models-used
  pill tags, registry hash footer
- B2-T3: SwarmWaveVisualization — heuristic 60/40 wave split with dependency
  connector; Wave 1 dimmed until run complete; empty state when subtask_count=0
- B2-T4: SwarmLiveCounters — 6 aggregate cards (total runs, subtasks, avg speedup,
  wall time, cost, savings) derived from swarm_runs projection
- B2-T5: SwarmSavingsBar — stacked local+savings bar vs cloud equivalent baseline,
  per-run mini bars (up to 10), zero-cost graceful empty state
- SwarmRunTable: 8-column run list with row selection, speedup/savings coloring
- SwarmControlPlane: tabbed widget (Overview, Decomposition, Run Detail, Wave View,
  Savings) wired to swarm.runs.v1 projection topic via useProjectionQuery
- Adds TOPICS.swarmRuns, swarm-runs.v1 postgres case, GET /api/swarm-runs route
- 10 tests passing

* feat(OMN-12072): add SwarmSubtaskDetailPanel — per-subtask model/tokens/cost/latency table (B5-T3 gap)

Adds SwarmSubtaskDetailPanel showing per-subtask rows with:
- subtask_id (derived: run_id prefix + padded index)
- description (Subtask 01, 02, ...)
- model assigned (cycled from run.models_used)
- status (succeeded/failed/skipped/pending, derived from aggregate counts)
- tokens_input / tokens_output (null — requires node_swarm_subtask_state_reducer)
- cost_usd (evenly distributed from total_cost_usd)
- latency_ms (estimated from dispatch_wall_latency_ms / parallelism)

Panel is clearly marked as aggregate-derived with a fixture notice.
Shows "No subtask data" with reducer name when subtask_count=0.

Wired as new "Subtask Detail" tab in SwarmControlPlane alongside existing
"Run Detail" aggregate card. Uses List icon from lucide-react.

Schema confirmed: swarm_runs has no jsonb subtask column and no separate
swarm_subtasks table exists yet in stability-test. All per-subtask data
requires node_swarm_subtask_state_reducer projection.

15 new tests + 10 existing passing (25 total).

* refactor(OMN-11578/OMN-11596): reduce cyclomatic complexity in ThreeCanvas and parseDashboardDefinition (#137)

OMN-11578: Extract advanceFocusProgress, applyFocusedOpacities,
applyIdleOpacities, and projectAxisLabels from the ThreeCanvas
useEffect god-function (CCN=83). The tick loop is now a thin
orchestrator calling four named helpers via a TickRefs context struct.

OMN-11596: Extract validateTopLevelFields and validateLayoutItem from
parseDashboardDefinition (CCN=31). The parse function is reduced to
an 8-line orchestrator; all conditional branches live in the helpers.

* fix(OMN-12245): silence unused swarm route request (#139)

* feat(OMN-11658): wire SEA demo dashboard projections (#140)

* feat(OMN-11658): wire SEA demo dashboard projections

* fix(OMN-11658): make trace rows toggle independently

* fix(OMN-11658): polish SEA dashboard rendering

* feat(OMN-11658): expose delegation trigger in SEA control plane (#141)

* fix(OMN-11658): improve SEA prompt feedback (#142)

* fix(OMN-11658): let dashboard fill wide viewports (#143)

* fix(OMN-11658): balance dashboard gutters (#144)

* fix(OMN-11658): keep SEA stream newest first (#145)

* fix(OMN-11658): refresh SEA traces after prompt submit (#146)

* fix(OMN-11658): center dashboard shell (#147)

* fix(OMN-11658): extend dashboard body to bottom (#148)

* fix(OMN-11658): repair empty SEA dashboard layout (#149)

* chore(DELEG-002): annotate pricing fallbacks, align fixture model names with bifrost config (#150)

* feat(OMN-12070): wire delegation trigger through omnimarket runtime (#151)

- Add COMMAND_TOPICS.delegateSkill ('onex.cmd.omnimarket.delegate-skill.v1')
  to shared/types/command-topics.ts so the topic is contract-declared rather
  than hardcoded in server/routes.ts
- Declare the delegation trigger command topic in contract.yaml with handler
  node, ingress path, payload shape, and OMNIDASH_RUNTIME_EFFECTS_URL env note
- server/routes.ts uses COMMAND_TOPICS.delegateSkill constant; runtime targets
  stability-test :18085/v1/effects via OMNIDASH_RUNTIME_EFFECTS_URL env var
- DelegationRunContext exposes pendingCorrelationId + setPendingCorrelationId
  so a freshly-dispatched correlation_id propagates to all child panels before
  projection rows materialize
- DelegationRunHeader wires onCorrelationId callback to DelegationTriggerPanel
- DelegationEvidenceTabs auto-navigates to correlation-trace tab on dispatch
  and displays a pending indicator with the correlation_id
- DelegationCorrelationTracePanel falls back to pendingCorrelationId when no
  run is selected and shows appropriate pending vs awaiting-events messages
- Test: setPendingCorrelationId updates context value (DelegationRunContext.test)

* fix(OMN-12243): add 'delegation' short-topic alias to postgres + sqlite projection readers (#152)

GET /projection/delegation was returning rows:[] because the query() switch
had no 'delegation' case — only the full versioned topic name. Adds a
fall-through alias in both PostgresProjectionReader and SqliteProjectionReader
so the short topic name routes to the same decisions.v1 query logic.

Test: new routes.test.ts case seeds a SQLite DB and asserts the short alias
returns the same rows as the full topic name.

* fix(OMN-12287): close fixture gaps and add file-mode banners to dashboard widgets (#153)

- Add fileMode prop to ComponentWrapper: renders a [File Mode] badge in the
  widget header when active, analogous to the [Live] badge, using Text component
  to comply with the no-typography-inline ESLint rule
- Wire fileMode={!isLiveDataSource(mode)} on all 13 widgets that use
  useDataSourceMode: TraceExplorer, LiveEventStream, ContextHeatmap,
  DelegationControlPlane, EventStream, ReadinessGate, DelegationMetrics2D,
  DelegationSavingsWidget, DelegationTokenUsageWidget,
  DelegationModelRoutingWidget, DelegationQualityGateWidget, AbCompareWidget
- Improve emptyHint messages on all updated widgets to include file-mode
  fixture directory paths so developers know exactly where to add test data
- 1124/1124 tests pass

* feat(OMN-12288): deep-link delegation runs to trace explorer (#154)

- Add traceFilter + setTraceFilter to UISlice so a correlation_id can be
  passed to TraceExplorer before navigating there (no URL routing required)
- DelegationRunTable adds a GitCommitHorizontal icon button per row; clicking
  it calls setTraceFilter(correlationId) + setActivePage('trace') and stops
  event propagation so the row-select handler is not also triggered
- TraceExplorerWidget initializes its query state from traceFilter on mount
  and immediately clears the store value so subsequent navigations start fresh
- Tests: DelegationRunTable.test.tsx (7 cases), TraceExplorerWidget deep-link
  test confirms filter pre-fill + store clear (1132 total tests pass)

* feat(T22): add DelegationCostComparisonWidget — per-run cost proof surface (#155)

- New widget DelegationCostComparisonWidget in delegation/ directory reads from
  TOPICS.delegationSavings (same source as DelegationSavingsWidget) and surfaces
  a per-session cost comparison table with columns: Session, Task, Model, Tokens,
  Latency, Local $, Cloud $, Saved, Provenance
- Provenance badges (measured/estimated) on each row per the § 1.3 spec — green
  for measured (llm_call_metrics), amber for estimated (routing-rule inferred)
- Summary KPI bar shows cumulative savings, local/cloud cost, session count,
  total tokens, measured vs estimated breakdown
- Sort controls: date (default), savings descending, tokens descending
- maxRows config cap with overflow notice; showProvenance=false hides source column
- Registered as 'delegation-cost-comparison' in generate-registry.ts and index.ts
- Integration tests updated for count 29 → 30
- 1132/1132 tests pass

* fix(OMN-12402): unify CommandPalette node-type badges and Dispatch button contrast (#156)

Issue 1: kindColor() mixed design tokens (var(--accent), var(--text-tertiary))
with hardcoded hex (#d97706, #7c3aed), so ORCHESTRATOR/COMPUTE badges rendered
as weak plain text while EFFECT/REDUCER read as vivid chips. Drive all four
badges from the design-system node-type role tokens (--<kind>{,-soft,-ink}),
which exist for both themes, so every badge is a consistent filled chip.

Issue 2: the Dispatch button used var(--accent) as its fill (which resolves to
--brand-soft, a pale hover tint) plus literal color:'white' text — invisible in
light mode. Use the brand fill + on-brand text token (--brand /
--primary-foreground) so the CTA stays legible in both themes.

Adds CommandPalette.test.tsx asserting both fixes. Visual proof:
docs/evidence/OMN-12402-{badges,dispatch-light}.png

* fix(OMN-12367): render Correlation Trace tab gracefully in file mode (#159)

In VITE_DATA_SOURCE=file mode the /api/delegation/* proxy is not registered
(it is wired only when VITE_PROJECTION_API_URL is set), so fetchCorrelationTrace
hit the Vite SPA fallback, received index.html, and surfaced a raw
'JSON.parse: unexpected character at line 1 column 1' error.

Detect file mode up front in fetchCorrelationTrace and read a per-correlation
fixture under fixtures/onex.snapshot.projection.delegation.correlation-trace.v1/
so the surface renders a representative event chain without postgres. In live
mode, parse defensively (res.json().catch) so any non-JSON body degrades to an
empty trace and the panel shows its graceful no-events message rather than a raw
parse error.

Adds the delegationCorrelationTrace topic, a representative 2-event fixture for
the file-mode decisions correlation (069e55b5...), and tests covering file-mode
fixture fetch and non-JSON hardening.

* fix(OMN-12399): register Context Effectiveness Heatmap widget in palette (#158)

The widget source (component, hook, fixtures, types, test) existed but was
registered in zero of the three Track A surfaces, so it never appeared in the
widget palette and was unreachable through the supported user flow.

- Add lazy import to componentImports in src/components/dashboard/index.ts
- Add context-effectiveness-heatmap manifest to MVP_COMPONENTS in
  scripts/generate-registry.ts (quality category, contextExperimentScores
  projection, OMN-11241 fixture fallback)
- Regenerate src/registry/component-registry.json (30 -> 31 components)
- Update integration.part2/part3 component-count + name-list assertions

Visual proof (OMN-7093): palette entry + widget rendered on dashboard in
file mode using the OMN-11241 research fixture.

* fix(OMN-12366): give Delegation Control Plane active tab a clear visual indicator (#157)

The 10-tab evidence strip distinguished active from inactive tabs only by
background (var(--panel-2) vs transparent); border, text color, and weight
were identical, so the strip did not read as tabs.

Active tab now uses the --brand accent token: brand border, a 2px brand
bottom-border underline, --brand-soft background, and brand-colored
semibold label text. All via existing design tokens (no hardcoded hex),
verified to read clearly in both light and dark themes.

Visual evidence (OMN-7093):
- docs/evidence/OMN-12366-tab-strip-dark.png
- docs/evidence/OMN-12366-tab-strip-light.png

* feat(OMN-12139): add Trace Explorer fixture for onex.snapshot.projection.traces.v1 (#161)

Wires a _trace_groups() factory into scripts/generate_fixtures.py and emits
five representative TraceGroup records under
fixtures/onex.snapshot.projection.traces.v1/, covering every TraceExplorer
UI state: multi-node completed, single-node completed, running, error, and a
longer multi-node trace. The widget reads each file as one TraceGroup via the
legacy useProjectionQuery readAll path; the file-source middleware
auto-generates index.json.

Shapes mirror the widget's TraceGroup interface exactly (no entity_id); each
record is keyed by its correlation_id so the fixture filename matches.

DoD: VITE_DATA_SOURCE=file dev server renders 5 traces (1 running, 1 error)
in the Trace Explorer in File Mode; Playwright screenshot under
docs/evidence/OMN-12139-trace-explorer-file-mode.png proves populated/running/
error states (OMN-7093). npm run build, lint, and full vitest suite (1172
tests) green.

* fix(OMN-12397): make Savings Proof Pack source line dynamic by data mode (#160)

The printable proof pack hardcoded "Source: OmniNode delegation_events
projection" even in file mode, so a PDF exported from fixture data was
misleading once removed from the parent Fixture banner. Derive the
"Source:" line and reproducibility sentence from useDataSourceMode():

  file     -> Local fixture file (fixtures/.../demo-delegation-snapshot.json)
  sqlite   -> Local SQLite projection
  http     -> OmniNode projection API via Express bridge
  postgres -> OmniNode delegation_events projection

Non-live modes (file/sqlite) state the report is from a local
development data source and is not a live projection.

Renamed fixture live-delegation-demo.json -> demo-delegation-snapshot.json;
the word 'live' inside fixtures/ was second-order misleading. The file is
discovered by directory glob (vite fixturesMiddleware readdirSync), so no
code references the old name.

dod_evidence: rendered_output screenshot
docs/evidence/OMN-12397-proof-pack-file-mode-source.png shows the
file-mode footer with the fixture source string and 15 fixture rows.

* fix(OMN-12392): repoint run-generate-fixtures.sh to local script and correct CLAUDE.md fixture claim (#162)

OMN-12392: `run-generate-fixtures.sh` was looking for `generate_fixtures.py` at
`$OMNIBASE_INFRA_PATH/scripts/generate_fixtures.py`, but that path does not exist in
omnibase_infra. The script lives in this repo at `scripts/generate_fixtures.py`.
Fix: set SCRIPT_PATH to `$REPO/scripts/generate_fixtures.py` while preserving
`--project "$OMNIBASE_INFRA_PATH"` for uv dependency resolution (the script imports
from omnibase_infra). `npm run generate:fixtures` now works without a non-standard
OMNIBASE_INFRA_PATH.

Also fix CLAUDE.md hard rule #6 which claimed "Fixtures and layouts are not committed"
— fixtures/ IS tracked via the OMN-10305 gitignore carve-out (!fixtures/, !fixtures/**).
Correct the claim and add the carve-out explanation.

OMN-12393: add a lineage classification table to CLAUDE.md documenting that
omnidash/ (dev) is CANONICAL_ACTIVE, omnidash-v2/ is SUPERSEDED, and
omnidash-v2-new/ is a TEMP_STAGING clone. Cite verification evidence.

* test(OMN-10947): pin dashboard e2e fixture proof (#163)

* test(OMN-10947): scope dashboard e2e locators (#164)

* fix(OMN-12394): add .env to omnidash .gitignore to prevent credential leak (#165)

The .env file holds OMNIDASH_ANALYTICS_DB_URL with a live Postgres password.
The existing rule only covered .env.local* — .env itself was unprotected.

Adds an explicit .env entry so git never tracks the credential file.

* fix: move omnidash event bus config to contract overlay (#166)

* chore(OMN-12459): add universal managed gitignore block to omnidash (#168)

Appends the onex-managed universal block to .gitignore, adding
.env.*, !.env.example, *.log, .idea/, .vscode/, test-results/, and
playwright-report/ coverage. .env and .DS_Store were already present
individually; the block makes the policy explicit and machine-readable.

No tracked files required untracking.

* ci(OMN-12477): add dev-only main-target-guard (forward-port from main) (#167)

Forward-ports the main-target-guard workflow to dev (the file existed on
main but was absent on dev — main<->dev drift). Adds the dev-only variant
WITHOUT the hotfix/* bypass: main accepts only dev promotion PRs carrying
promotion-receipt: OCC-N. The guard only triggers on base==main, so it is
inert on dev-targeting PRs and takes effect at the next dev->main promotion.

OMN-12477

* feat(OMN-12674): remove reviewdog workflows and configs (#170)

Removes the reviewdog caller workflow from omnidash. No reviewdog
required-status-check bindings existed on main protection, dev
protection, or any repo ruleset, so this is the only active reviewdog
surface in the repo.

dod_evidence: OMN-12674

* fix(OMN-12739): tighten delegation dashboard formatting (#171)

* fix(OMN-12740): add browser-safe dashboard id fallback (#172)

* ci: preserve hotfix main-target allowance for release
jonahgabriel added a commit that referenced this pull request Jun 7, 2026
* feat(OMN-11979): redesign delegation dashboard — dense metrics, narrative labels, stale .js cleanup (#119)

- Replace donut chart with terminal-style KPI tiles + stacked model bar + task breakdown
- Rename Quality Gate → Escalation Detail, humanize failure categories
- Fix postgres-projection-reader: correct column names, JSONB string coercion
- Add /projection Vite proxy for Express bridge
- Remove 8 stale compiled .js files that shadowed .tsx source via Vite resolution
- "Quality Gate" → "Local Success", "pass/fail" → "local/escalated" throughout

* feat(OMN-12069): delegation correlation trace view

Adds the delegation correlation trace view, trigger panel, and dashboard assertion updates needed for CI.

* feat(OMN-12133): add projection query endpoints for log entries and traces (#122)

Adds GET /api/projections/log-entries and GET /api/projections/traces
to the Express server, backed by new queryLogEntries and queryTraces
methods on PostgresProjectionReader. Adds traceExplorer topic constant
to shared/types/topics.ts. All query params are validated; limits are
capped (1000 for entries, 500 for traces). is_running = true when
last_event_at < 60s ago. 17 new unit tests + 2 route-level tests.

* feat(OMN-12089): B6 delegation savings proof pack — printable audit artifact (#123)

Adds DelegationSavingsProofPackPanel as a new 'Savings Proof Pack' tab in
the delegation control plane evidence tabs. Renders an exportable audit
report from live projection data: correlation IDs for every run, routing
decisions (model, rule, confidence), quality gate results (pass/fail +
detail), pricing manifest version, per-run savings, cumulative savings
summary, and a pass-rate totals row.

Print/Save as PDF is handled via window.print() with an injected @media
print stylesheet that isolates the printable region to the proof pack table.
No server-side PDF generation or new backend endpoints required — all data
sourced from DelegationEvidenceSnapshot already in context.

7 tests covering empty state, header rendering, run table, quality gate
labels, routing display, totals row, and print button.

* feat(OMN-12085): add swarm-runs projection topic to PostgresProjectionReader (#124)

Adds onex.snapshot.projection.swarm-runs.v1 case to the Postgres projection
reader. Queries omnidash_analytics.swarm_runs and returns all T4 display
fields: runId, status, subtask/succeeded/failed counts, modelsUsed,
totalCostUsd, cloudEquivalentCostUsd, savingsUsd, parallelismSpeedupRatio,
all latency fields, and createdAt. The swarm_runs table already exists on
stability-test (15436) with 11 rows.

* feat(OMN-12135): add TraceExplorerWidget — two-panel event bus trace explorer (#126)

Adds TraceExplorerWidget to omnidash for event bus observability Phase 2.
The widget consumes onex.snapshot.projection.traces.v1 to render a
left-panel trace list (with running/error indicators) and right-panel
chronological timeline of log entries for the selected trace.

- TraceExplorerWidget.tsx: two-panel layout, trace cards, timeline, search
- TraceExplorerWidget.stories.tsx: Empty, Populated, WithRunningTrace, ErrorTraces, Loading
- TraceExplorerWidget.test.tsx: 5 unit tests (loading, empty, render, filter, select prompt)
- storybook/fixtures/trace-explorer.ts: buildTraceGroups fixture factory
- generate-registry.ts: trace-explorer manifest with projectionSchema + displayContract
- index.ts: lazy import wired under trace-explorer/TraceExplorerWidget
- integration.part2/3: component count bumped 28→29 with trace-explorer

Closes OMN-12135. References OMN-12136, OMN-12137, OMN-12138, OMN-12139.

* feat(OMN-12082): add Context Effectiveness Heatmap widget (B4) (#127)

Implements the context factor effectiveness visualization showing which
context segments help/hurt which models across A3 ablation experiments:

- Segment × model matrix with color-coded cells:
  green (≥75% pass rate), amber (26–74%), red (≤25%)
- Clickable cells open an examples panel showing specific token impact,
  pass/fail counts, and qualitative findings per segment×model combination
- Fixture mode: when no live data exists, shows OMN-11241 research results
  (golden_chain green across all models, claude_md/architecture_patterns
  red for local models, exemplar green for exact-interface tasks,
  local_failures neutral)
- Fixture badge clearly marks when live projection is absent
- Reads from context_experiment_scores table via new
  onex.snapshot.projection.context.experiment-scores.v1 topic
- Adds postgres reader case for context_experiment_scores SELECT
- 10 tests passing (empty state, matrix render, cell selection, fixture
  badge, toggle deselect, footer count)

* feat(OMN-12145): add Express dispatch bridge and Kafka command topic (#128)

- POST /api/dispatch validates body, builds envelope, publishes to
  onex.cmd.omnidash.dispatch-request.v1 via kafkajs producer
- Returns 400 on missing fields, 503 when broker unavailable
- Kafka producer connects on server start, graceful disconnect on SIGTERM/SIGINT
- COMMAND_TOPICS registry in shared/types/command-topics.ts (separate from
  TOPICS to preserve golden-chain onex.snapshot.projection.* invariant)
- 9 unit tests with mocked producer covering validation, 503, and happy path

Closes OMN-12145, OMN-12149

* feat(OMN-12147): add command dispatch UI — trace action buttons + Cmd+K palette (#129)

- New `command-dispatch/` directory with:
  - `useDispatch.ts` hook: POST /api/dispatch, availability probe, error/loading state
  - `DispatchButton.tsx`: icon button (RotateCw/Square) with loading/success/error states
  - `CommandPalette.tsx`: Cmd+K/Ctrl+K overlay with node search, JSON payload editor, dispatch result; includes `useCommandPalette` hook for global keyboard listener
  - `CommandPalette.stories.tsx`: Open, WithResults, Dispatching, Error story exports
- `TraceExplorerWidget.tsx`: adds Stop button on running traces, Re-run button on completed traces
- `App.tsx`: mounts `CommandPalette` at app root via `useCommandPalette` hook

Closes OMN-12147, relates OMN-12148

* fix(OMN-12149): align dispatch command topic to omnimarket namespace (#131)

The node_dispatch_request_handler subscribes to
onex.cmd.omnimarket.dispatch-request.v1 (topic naming lint requires
consumer service namespace). Aligns the Express bridge publisher topic.

* chore(OMN-12171): delete orphaned .js files and add CI enforcement gate (#132)

* chore(OMN-12171): delete orphaned .js files and add CI enforcement gate

Confirms 298 .js artifacts in src/ and shared/ are stale (all have .ts/.tsx
counterparts; tsconfig emits nothing to src/; build output goes to dist/).
Deletes them from working tree.

Adds scripts/check-no-orphaned-js.sh — fails if any .js file in src/ or shared/
shadows a .ts/.tsx source — and wires it as a CI gate in ci.yml.

Also fixes broken @storybook/test import in CommandPalette.stories.tsx
(package moved to storybook/test in v10), restoring tsc --noEmit clean pass.

* fix(OMN-12171): include command topic in server typecheck

* refactor(OMN-12187): Consolidate DRY violations in omnidash

* refactor(OMN-12187): Consolidate DRY violations in omnidash

Extract shared pure-data helpers (timestampValue, mergeDelegationSessions,
buildCostSavingsOverviewResult) into server/projection-reader-shared.ts.
Both sqlite-projection-reader.ts and postgres-projection-reader.ts delegate
to the shared module, eliminating ~120 lines of duplicated logic.

Add 13 unit tests for the shared module covering edge cases in timestamp
parsing, session merge semantics, and cost overview aggregation.

The integration test files (part2/part3/part4) are distinct suites with
different test content — they are not duplicates and are left intact.

* fix(OMN-12187): remove unused cost overview input

* feat(OMN-12072): Swarm Control Plane widget — decomposition tree, wave view, live counters, savings bar (#125)

* feat(OMN-12072): add Swarm Control Plane widget (B2-T1 through B2-T5)

Implements the full swarm progression UI against the swarm_runs projection:
- B2-T1: SwarmDecompositionTree — parent→subtask tree with aggregate-derived
  per-subtask statuses and model hints; graceful empty state when subtask_count=0
- B2-T2: SwarmSubtaskCard — colored status badge, 3×3 metrics grid, models-used
  pill tags, registry hash footer
- B2-T3: SwarmWaveVisualization — heuristic 60/40 wave split with dependency
  connector; Wave 1 dimmed until run complete; empty state when subtask_count=0
- B2-T4: SwarmLiveCounters — 6 aggregate cards (total runs, subtasks, avg speedup,
  wall time, cost, savings) derived from swarm_runs projection
- B2-T5: SwarmSavingsBar — stacked local+savings bar vs cloud equivalent baseline,
  per-run mini bars (up to 10), zero-cost graceful empty state
- SwarmRunTable: 8-column run list with row selection, speedup/savings coloring
- SwarmControlPlane: tabbed widget (Overview, Decomposition, Run Detail, Wave View,
  Savings) wired to swarm.runs.v1 projection topic via useProjectionQuery
- Adds TOPICS.swarmRuns, swarm-runs.v1 postgres case, GET /api/swarm-runs route
- 10 tests passing

* feat(OMN-12072): add SwarmSubtaskDetailPanel — per-subtask model/tokens/cost/latency table (B5-T3 gap)

Adds SwarmSubtaskDetailPanel showing per-subtask rows with:
- subtask_id (derived: run_id prefix + padded index)
- description (Subtask 01, 02, ...)
- model assigned (cycled from run.models_used)
- status (succeeded/failed/skipped/pending, derived from aggregate counts)
- tokens_input / tokens_output (null — requires node_swarm_subtask_state_reducer)
- cost_usd (evenly distributed from total_cost_usd)
- latency_ms (estimated from dispatch_wall_latency_ms / parallelism)

Panel is clearly marked as aggregate-derived with a fixture notice.
Shows "No subtask data" with reducer name when subtask_count=0.

Wired as new "Subtask Detail" tab in SwarmControlPlane alongside existing
"Run Detail" aggregate card. Uses List icon from lucide-react.

Schema confirmed: swarm_runs has no jsonb subtask column and no separate
swarm_subtasks table exists yet in stability-test. All per-subtask data
requires node_swarm_subtask_state_reducer projection.

15 new tests + 10 existing passing (25 total).

* refactor(OMN-11578/OMN-11596): reduce cyclomatic complexity in ThreeCanvas and parseDashboardDefinition (#137)

OMN-11578: Extract advanceFocusProgress, applyFocusedOpacities,
applyIdleOpacities, and projectAxisLabels from the ThreeCanvas
useEffect god-function (CCN=83). The tick loop is now a thin
orchestrator calling four named helpers via a TickRefs context struct.

OMN-11596: Extract validateTopLevelFields and validateLayoutItem from
parseDashboardDefinition (CCN=31). The parse function is reduced to
an 8-line orchestrator; all conditional branches live in the helpers.

* fix(OMN-12245): silence unused swarm route request (#139)

* feat(OMN-11658): wire SEA demo dashboard projections (#140)

* feat(OMN-11658): wire SEA demo dashboard projections

* fix(OMN-11658): make trace rows toggle independently

* fix(OMN-11658): polish SEA dashboard rendering

* feat(OMN-11658): expose delegation trigger in SEA control plane (#141)

* fix(OMN-11658): improve SEA prompt feedback (#142)

* fix(OMN-11658): let dashboard fill wide viewports (#143)

* fix(OMN-11658): balance dashboard gutters (#144)

* fix(OMN-11658): keep SEA stream newest first (#145)

* fix(OMN-11658): refresh SEA traces after prompt submit (#146)

* fix(OMN-11658): center dashboard shell (#147)

* fix(OMN-11658): extend dashboard body to bottom (#148)

* fix(OMN-11658): repair empty SEA dashboard layout (#149)

* chore(DELEG-002): annotate pricing fallbacks, align fixture model names with bifrost config (#150)

* feat(OMN-12070): wire delegation trigger through omnimarket runtime (#151)

- Add COMMAND_TOPICS.delegateSkill ('onex.cmd.omnimarket.delegate-skill.v1')
  to shared/types/command-topics.ts so the topic is contract-declared rather
  than hardcoded in server/routes.ts
- Declare the delegation trigger command topic in contract.yaml with handler
  node, ingress path, payload shape, and OMNIDASH_RUNTIME_EFFECTS_URL env note
- server/routes.ts uses COMMAND_TOPICS.delegateSkill constant; runtime targets
  stability-test :18085/v1/effects via OMNIDASH_RUNTIME_EFFECTS_URL env var
- DelegationRunContext exposes pendingCorrelationId + setPendingCorrelationId
  so a freshly-dispatched correlation_id propagates to all child panels before
  projection rows materialize
- DelegationRunHeader wires onCorrelationId callback to DelegationTriggerPanel
- DelegationEvidenceTabs auto-navigates to correlation-trace tab on dispatch
  and displays a pending indicator with the correlation_id
- DelegationCorrelationTracePanel falls back to pendingCorrelationId when no
  run is selected and shows appropriate pending vs awaiting-events messages
- Test: setPendingCorrelationId updates context value (DelegationRunContext.test)

* fix(OMN-12243): add 'delegation' short-topic alias to postgres + sqlite projection readers (#152)

GET /projection/delegation was returning rows:[] because the query() switch
had no 'delegation' case — only the full versioned topic name. Adds a
fall-through alias in both PostgresProjectionReader and SqliteProjectionReader
so the short topic name routes to the same decisions.v1 query logic.

Test: new routes.test.ts case seeds a SQLite DB and asserts the short alias
returns the same rows as the full topic name.

* fix(OMN-12287): close fixture gaps and add file-mode banners to dashboard widgets (#153)

- Add fileMode prop to ComponentWrapper: renders a [File Mode] badge in the
  widget header when active, analogous to the [Live] badge, using Text component
  to comply with the no-typography-inline ESLint rule
- Wire fileMode={!isLiveDataSource(mode)} on all 13 widgets that use
  useDataSourceMode: TraceExplorer, LiveEventStream, ContextHeatmap,
  DelegationControlPlane, EventStream, ReadinessGate, DelegationMetrics2D,
  DelegationSavingsWidget, DelegationTokenUsageWidget,
  DelegationModelRoutingWidget, DelegationQualityGateWidget, AbCompareWidget
- Improve emptyHint messages on all updated widgets to include file-mode
  fixture directory paths so developers know exactly where to add test data
- 1124/1124 tests pass

* feat(OMN-12288): deep-link delegation runs to trace explorer (#154)

- Add traceFilter + setTraceFilter to UISlice so a correlation_id can be
  passed to TraceExplorer before navigating there (no URL routing required)
- DelegationRunTable adds a GitCommitHorizontal icon button per row; clicking
  it calls setTraceFilter(correlationId) + setActivePage('trace') and stops
  event propagation so the row-select handler is not also triggered
- TraceExplorerWidget initializes its query state from traceFilter on mount
  and immediately clears the store value so subsequent navigations start fresh
- Tests: DelegationRunTable.test.tsx (7 cases), TraceExplorerWidget deep-link
  test confirms filter pre-fill + store clear (1132 total tests pass)

* feat(T22): add DelegationCostComparisonWidget — per-run cost proof surface (#155)

- New widget DelegationCostComparisonWidget in delegation/ directory reads from
  TOPICS.delegationSavings (same source as DelegationSavingsWidget) and surfaces
  a per-session cost comparison table with columns: Session, Task, Model, Tokens,
  Latency, Local $, Cloud $, Saved, Provenance
- Provenance badges (measured/estimated) on each row per the § 1.3 spec — green
  for measured (llm_call_metrics), amber for estimated (routing-rule inferred)
- Summary KPI bar shows cumulative savings, local/cloud cost, session count,
  total tokens, measured vs estimated breakdown
- Sort controls: date (default), savings descending, tokens descending
- maxRows config cap with overflow notice; showProvenance=false hides source column
- Registered as 'delegation-cost-comparison' in generate-registry.ts and index.ts
- Integration tests updated for count 29 → 30
- 1132/1132 tests pass

* fix(OMN-12402): unify CommandPalette node-type badges and Dispatch button contrast (#156)

Issue 1: kindColor() mixed design tokens (var(--accent), var(--text-tertiary))
with hardcoded hex (#d97706, #7c3aed), so ORCHESTRATOR/COMPUTE badges rendered
as weak plain text while EFFECT/REDUCER read as vivid chips. Drive all four
badges from the design-system node-type role tokens (--<kind>{,-soft,-ink}),
which exist for both themes, so every badge is a consistent filled chip.

Issue 2: the Dispatch button used var(--accent) as its fill (which resolves to
--brand-soft, a pale hover tint) plus literal color:'white' text — invisible in
light mode. Use the brand fill + on-brand text token (--brand /
--primary-foreground) so the CTA stays legible in both themes.

Adds CommandPalette.test.tsx asserting both fixes. Visual proof:
docs/evidence/OMN-12402-{badges,dispatch-light}.png

* fix(OMN-12367): render Correlation Trace tab gracefully in file mode (#159)

In VITE_DATA_SOURCE=file mode the /api/delegation/* proxy is not registered
(it is wired only when VITE_PROJECTION_API_URL is set), so fetchCorrelationTrace
hit the Vite SPA fallback, received index.html, and surfaced a raw
'JSON.parse: unexpected character at line 1 column 1' error.

Detect file mode up front in fetchCorrelationTrace and read a per-correlation
fixture under fixtures/onex.snapshot.projection.delegation.correlation-trace.v1/
so the surface renders a representative event chain without postgres. In live
mode, parse defensively (res.json().catch) so any non-JSON body degrades to an
empty trace and the panel shows its graceful no-events message rather than a raw
parse error.

Adds the delegationCorrelationTrace topic, a representative 2-event fixture for
the file-mode decisions correlation (069e55b5...), and tests covering file-mode
fixture fetch and non-JSON hardening.

* fix(OMN-12399): register Context Effectiveness Heatmap widget in palette (#158)

The widget source (component, hook, fixtures, types, test) existed but was
registered in zero of the three Track A surfaces, so it never appeared in the
widget palette and was unreachable through the supported user flow.

- Add lazy import to componentImports in src/components/dashboard/index.ts
- Add context-effectiveness-heatmap manifest to MVP_COMPONENTS in
  scripts/generate-registry.ts (quality category, contextExperimentScores
  projection, OMN-11241 fixture fallback)
- Regenerate src/registry/component-registry.json (30 -> 31 components)
- Update integration.part2/part3 component-count + name-list assertions

Visual proof (OMN-7093): palette entry + widget rendered on dashboard in
file mode using the OMN-11241 research fixture.

* fix(OMN-12366): give Delegation Control Plane active tab a clear visual indicator (#157)

The 10-tab evidence strip distinguished active from inactive tabs only by
background (var(--panel-2) vs transparent); border, text color, and weight
were identical, so the strip did not read as tabs.

Active tab now uses the --brand accent token: brand border, a 2px brand
bottom-border underline, --brand-soft background, and brand-colored
semibold label text. All via existing design tokens (no hardcoded hex),
verified to read clearly in both light and dark themes.

Visual evidence (OMN-7093):
- docs/evidence/OMN-12366-tab-strip-dark.png
- docs/evidence/OMN-12366-tab-strip-light.png

* feat(OMN-12139): add Trace Explorer fixture for onex.snapshot.projection.traces.v1 (#161)

Wires a _trace_groups() factory into scripts/generate_fixtures.py and emits
five representative TraceGroup records under
fixtures/onex.snapshot.projection.traces.v1/, covering every TraceExplorer
UI state: multi-node completed, single-node completed, running, error, and a
longer multi-node trace. The widget reads each file as one TraceGroup via the
legacy useProjectionQuery readAll path; the file-source middleware
auto-generates index.json.

Shapes mirror the widget's TraceGroup interface exactly (no entity_id); each
record is keyed by its correlation_id so the fixture filename matches.

DoD: VITE_DATA_SOURCE=file dev server renders 5 traces (1 running, 1 error)
in the Trace Explorer in File Mode; Playwright screenshot under
docs/evidence/OMN-12139-trace-explorer-file-mode.png proves populated/running/
error states (OMN-7093). npm run build, lint, and full vitest suite (1172
tests) green.

* fix(OMN-12397): make Savings Proof Pack source line dynamic by data mode (#160)

The printable proof pack hardcoded "Source: OmniNode delegation_events
projection" even in file mode, so a PDF exported from fixture data was
misleading once removed from the parent Fixture banner. Derive the
"Source:" line and reproducibility sentence from useDataSourceMode():

  file     -> Local fixture file (fixtures/.../demo-delegation-snapshot.json)
  sqlite   -> Local SQLite projection
  http     -> OmniNode projection API via Express bridge
  postgres -> OmniNode delegation_events projection

Non-live modes (file/sqlite) state the report is from a local
development data source and is not a live projection.

Renamed fixture live-delegation-demo.json -> demo-delegation-snapshot.json;
the word 'live' inside fixtures/ was second-order misleading. The file is
discovered by directory glob (vite fixturesMiddleware readdirSync), so no
code references the old name.

dod_evidence: rendered_output screenshot
docs/evidence/OMN-12397-proof-pack-file-mode-source.png shows the
file-mode footer with the fixture source string and 15 fixture rows.

* fix(OMN-12392): repoint run-generate-fixtures.sh to local script and correct CLAUDE.md fixture claim (#162)

OMN-12392: `run-generate-fixtures.sh` was looking for `generate_fixtures.py` at
`$OMNIBASE_INFRA_PATH/scripts/generate_fixtures.py`, but that path does not exist in
omnibase_infra. The script lives in this repo at `scripts/generate_fixtures.py`.
Fix: set SCRIPT_PATH to `$REPO/scripts/generate_fixtures.py` while preserving
`--project "$OMNIBASE_INFRA_PATH"` for uv dependency resolution (the script imports
from omnibase_infra). `npm run generate:fixtures` now works without a non-standard
OMNIBASE_INFRA_PATH.

Also fix CLAUDE.md hard rule #6 which claimed "Fixtures and layouts are not committed"
— fixtures/ IS tracked via the OMN-10305 gitignore carve-out (!fixtures/, !fixtures/**).
Correct the claim and add the carve-out explanation.

OMN-12393: add a lineage classification table to CLAUDE.md documenting that
omnidash/ (dev) is CANONICAL_ACTIVE, omnidash-v2/ is SUPERSEDED, and
omnidash-v2-new/ is a TEMP_STAGING clone. Cite verification evidence.

* test(OMN-10947): pin dashboard e2e fixture proof (#163)

* test(OMN-10947): scope dashboard e2e locators (#164)

* fix(OMN-12394): add .env to omnidash .gitignore to prevent credential leak (#165)

The .env file holds OMNIDASH_ANALYTICS_DB_URL with a live Postgres password.
The existing rule only covered .env.local* — .env itself was unprotected.

Adds an explicit .env entry so git never tracks the credential file.

* fix: move omnidash event bus config to contract overlay (#166)

* chore(OMN-12459): add universal managed gitignore block to omnidash (#168)

Appends the onex-managed universal block to .gitignore, adding
.env.*, !.env.example, *.log, .idea/, .vscode/, test-results/, and
playwright-report/ coverage. .env and .DS_Store were already present
individually; the block makes the policy explicit and machine-readable.

No tracked files required untracking.

* ci(OMN-12477): add dev-only main-target-guard (forward-port from main) (#167)

Forward-ports the main-target-guard workflow to dev (the file existed on
main but was absent on dev — main<->dev drift). Adds the dev-only variant
WITHOUT the hotfix/* bypass: main accepts only dev promotion PRs carrying
promotion-receipt: OCC-N. The guard only triggers on base==main, so it is
inert on dev-targeting PRs and takes effect at the next dev->main promotion.

OMN-12477

* feat(OMN-12674): remove reviewdog workflows and configs (#170)

Removes the reviewdog caller workflow from omnidash. No reviewdog
required-status-check bindings existed on main protection, dev
protection, or any repo ruleset, so this is the only active reviewdog
surface in the repo.

dod_evidence: OMN-12674

* fix(OMN-12739): tighten delegation dashboard formatting (#171)

* fix(OMN-12740): add browser-safe dashboard id fallback (#172)

* feat(OMN-12745): add DelegationModelOutputWidget rendering inference generated_text (#177)

Implements the demo-blocking deliverable for OMN-12745: DelegationModelOutputWidget
renders ModelLlmInferenceResponse.generated_text sourced from bus topic
onex.evt.omnibase-infra.inference-response.v1 in fixture mode (offline, no .201
dependency). Live surfacing is gated on the OMN-12746 demo redeploy.

Changes:
- src/components/dashboard/delegation/DelegationModelOutputWidget.tsx — new widget
  rendering latest generated_text, model identity, task type, token count, latency,
  and a recent response history list
- src/components/dashboard/delegation/DelegationModelOutputWidget.test.tsx — 8 unit
  tests (TDD-first: loading, empty, generated_text, model identity, topic label,
  upstream notice, provisioned, token count)
- src/components/dashboard/delegation/DelegationModelOutputWidget.stories.tsx — 6
  stories: Empty, Loading, Populated, FixtureMode, Provisioned, SingleResponse
- shared/types/topics.ts — adds inferenceResponseText topic key
- src/storybook/fixtures/delegation-routing.ts — adds buildInferenceResponseProjection
- src/storybook/fixtures/index.ts — exports new fixture builder
- scripts/generate-registry.ts — adds delegation-model-output MVP manifest entry
- src/registry/component-registry.json — regenerated (32 components)
- src/components/dashboard/index.ts — lazy-import registration
- src/templates/sea-demo.ts — delegation-model-output added to SEA Demo template
- src/templates/templates.test.ts — updated SEA Demo widget order assertions
- src/integration.part2.test.tsx — component count 31 to 32 and name list
- src/integration.part3.test.tsx — component count 31 to 32
- src/storybook-coverage-compliance.test.ts — adds story file to compliance list
- fixtures/onex.snapshot.projection.delegation.inference-response-text.v1/snapshot.json
  — fixture data for file mode (offline demo)

Evidence-Source OCC SHA: c872f8edbabda4306e3dd290dbb8995031da3d7c

* feat(OMN-12748): render delegated prompt/response from contract-declared projection (#178)

The Correlation Trace panel already renders prompt_text/response_text per run,
but fetchCorrelationTrace called a bespoke /api/delegation/correlation-trace/<id>
REST route that the deployed contract-driven projection API does not implement
(404), so the panel always showed 'No events found' and the dashboard could not
surface the actual delegated work.

Read the per-correlation trace from the contract-declared projection instead:
/projection/onex.snapshot.projection.delegation.correlation-trace.v1?correlation_id=<id>
(a read path the projection API already supports). Selecting a run now renders
its prompt + response. File mode still reads the per-correlation fixture.

Pairs with omnimarket OMN-12748, which exposes that projection over
delegation_events. Canonical: client renders a contract-declared projection; no
bespoke REST route.

* chore(release)(OMN-12245): backmerge omnidash v1.1.1 to dev (#176)

* fix(OMN-12756): publish delegation trigger through command bus (#179)

* feat(OMN-12748): render delegated prompt/response from contract-declared projection

The Correlation Trace panel already renders prompt_text/response_text per run,
but fetchCorrelationTrace called a bespoke /api/delegation/correlation-trace/<id>
REST route that the deployed contract-driven projection API does not implement
(404), so the panel always showed 'No events found' and the dashboard could not
surface the actual delegated work.

Read the per-correlation trace from the contract-declared projection instead:
/projection/onex.snapshot.projection.delegation.correlation-trace.v1?correlation_id=<id>
(a read path the projection API already supports). Selecting a run now renders
its prompt + response. File mode still reads the per-correlation fixture.

Pairs with omnimarket OMN-12748, which exposes that projection over
delegation_events. Canonical: client renders a contract-declared projection; no
bespoke REST route.

* fix(OMN-12756): route delegation API to dashboard server

* chore(OMN-12765): backmerge omnidash main to dev (#181)

* feat(OMN-11614, OMN-11617): deduplicate projection readers and integration test utilities (#120)

* feat(OMN-11979): redesign delegation dashboard — dense metrics, narrative labels, stale .js cleanup (#119)

- Replace donut chart with terminal-style KPI tiles + stacked model bar + task breakdown
- Rename Quality Gate → Escalation Detail, humanize failure categories
- Fix postgres-projection-reader: correct column names, JSONB string coercion
- Add /projection Vite proxy for Express bridge
- Remove 8 stale compiled .js files that shadowed .tsx source via Vite resolution
- "Quality Gate" → "Local Success", "pass/fail" → "local/escalated" throughout

* feat(OMN-11614, OMN-11617): deduplicate projection readers and integration test utilities

OMN-11614: Extract shared session-merge and cost-savings-overview aggregation
logic from postgres-projection-reader.ts and sqlite-projection-reader.ts into
server/projection-utils.ts. Removes ~160 lines of duplicated code addressed
by the TODO(OMN-10976) comment. Fixes pre-existing test failures in
postgres-projection-reader.test.ts where string-typed token assertions
contradicted the numeric coercion the reader actually applies.

OMN-11617: Extract manifest loading and QueryClient factory into
src/test-utils/integrationHelpers.ts. Updates integration.part2/3/4.test.tsx
to import TEST_MANIFEST and createTestQueryClient instead of duplicating the
readFileSync + JSON.parse pattern and bare QueryClient constructor.

17 new unit tests for projection-utils.ts cover timestampValue, sessionKey,
mergeDelegationSessions, sessionTokens, and buildCostSavingsOverview.

* fix(OMN-11614, OMN-11617): update delegation widget tests to match OMN-11979 redesign

Tests were written against the old design (KPI tiles with "Pass rate",
"Passed", "Failed" labels; donut panel 420px wide; "QG Pass" column;
"Failure categories" heading; "Recent checks" label). OMN-11979 shipped
a dense terminal-style redesign that changed these rendered strings.

Update assertions to match the current component output:
- DelegationMetrics3D: maxWidth 420→240, canvas height 200→140
- DelegationMetrics2D: "N/M pass" → "N/M local"
- DelegationModelRoutingWidget: column "QG Pass" → "Local Rate"
- DelegationQualityGateWidget: empty msg, check-type labels, section
  headings, recent-checks label all updated to current rendered text

All 1044 tests pass (0 new failures introduced).

* chore: release v1.1.0 (OMN-12245) (#135)

* feat(OMN-11979): redesign delegation dashboard — dense metrics, narrative labels, stale .js cleanup (#119)

- Replace donut chart with terminal-style KPI tiles + stacked model bar + task breakdown
- Rename Quality Gate → Escalation Detail, humanize failure categories
- Fix postgres-projection-reader: correct column names, JSONB string coercion
- Add /projection Vite proxy for Express bridge
- Remove 8 stale compiled .js files that shadowed .tsx source via Vite resolution
- "Quality Gate" → "Local Success", "pass/fail" → "local/escalated" throughout

* feat(OMN-12069): delegation correlation trace view

Adds the delegation correlation trace view, trigger panel, and dashboard assertion updates needed for CI.

* feat(OMN-12133): add projection query endpoints for log entries and traces (#122)

Adds GET /api/projections/log-entries and GET /api/projections/traces
to the Express server, backed by new queryLogEntries and queryTraces
methods on PostgresProjectionReader. Adds traceExplorer topic constant
to shared/types/topics.ts. All query params are validated; limits are
capped (1000 for entries, 500 for traces). is_running = true when
last_event_at < 60s ago. 17 new unit tests + 2 route-level tests.

* feat(OMN-12089): B6 delegation savings proof pack — printable audit artifact (#123)

Adds DelegationSavingsProofPackPanel as a new 'Savings Proof Pack' tab in
the delegation control plane evidence tabs. Renders an exportable audit
report from live projection data: correlation IDs for every run, routing
decisions (model, rule, confidence), quality gate results (pass/fail +
detail), pricing manifest version, per-run savings, cumulative savings
summary, and a pass-rate totals row.

Print/Save as PDF is handled via window.print() with an injected @media
print stylesheet that isolates the printable region to the proof pack table.
No server-side PDF generation or new backend endpoints required — all data
sourced from DelegationEvidenceSnapshot already in context.

7 tests covering empty state, header rendering, run table, quality gate
labels, routing display, totals row, and print button.

* feat(OMN-12085): add swarm-runs projection topic to PostgresProjectionReader (#124)

Adds onex.snapshot.projection.swarm-runs.v1 case to the Postgres projection
reader. Queries omnidash_analytics.swarm_runs and returns all T4 display
fields: runId, status, subtask/succeeded/failed counts, modelsUsed,
totalCostUsd, cloudEquivalentCostUsd, savingsUsd, parallelismSpeedupRatio,
all latency fields, and createdAt. The swarm_runs table already exists on
stability-test (15436) with 11 rows.

* feat(OMN-12135): add TraceExplorerWidget — two-panel event bus trace explorer (#126)

Adds TraceExplorerWidget to omnidash for event bus observability Phase 2.
The widget consumes onex.snapshot.projection.traces.v1 to render a
left-panel trace list (with running/error indicators) and right-panel
chronological timeline of log entries for the selected trace.

- TraceExplorerWidget.tsx: two-panel layout, trace cards, timeline, search
- TraceExplorerWidget.stories.tsx: Empty, Populated, WithRunningTrace, ErrorTraces, Loading
- TraceExplorerWidget.test.tsx: 5 unit tests (loading, empty, render, filter, select prompt)
- storybook/fixtures/trace-explorer.ts: buildTraceGroups fixture factory
- generate-registry.ts: trace-explorer manifest with projectionSchema + displayContract
- index.ts: lazy import wired under trace-explorer/TraceExplorerWidget
- integration.part2/3: component count bumped 28→29 with trace-explorer

Closes OMN-12135. References OMN-12136, OMN-12137, OMN-12138, OMN-12139.

* feat(OMN-12082): add Context Effectiveness Heatmap widget (B4) (#127)

Implements the context factor effectiveness visualization showing which
context segments help/hurt which models across A3 ablation experiments:

- Segment × model matrix with color-coded cells:
  green (≥75% pass rate), amber (26–74%), red (≤25%)
- Clickable cells open an examples panel showing specific token impact,
  pass/fail counts, and qualitative findings per segment×model combination
- Fixture mode: when no live data exists, shows OMN-11241 research results
  (golden_chain green across all models, claude_md/architecture_patterns
  red for local models, exemplar green for exact-interface tasks,
  local_failures neutral)
- Fixture badge clearly marks when live projection is absent
- Reads from context_experiment_scores table via new
  onex.snapshot.projection.context.experiment-scores.v1 topic
- Adds postgres reader case for context_experiment_scores SELECT
- 10 tests passing (empty state, matrix render, cell selection, fixture
  badge, toggle deselect, footer count)

* feat(OMN-12145): add Express dispatch bridge and Kafka command topic (#128)

- POST /api/dispatch validates body, builds envelope, publishes to
  onex.cmd.omnidash.dispatch-request.v1 via kafkajs producer
- Returns 400 on missing fields, 503 when broker unavailable
- Kafka producer connects on server start, graceful disconnect on SIGTERM/SIGINT
- COMMAND_TOPICS registry in shared/types/command-topics.ts (separate from
  TOPICS to preserve golden-chain onex.snapshot.projection.* invariant)
- 9 unit tests with mocked producer covering validation, 503, and happy path

Closes OMN-12145, OMN-12149

* feat(OMN-12147): add command dispatch UI — trace action buttons + Cmd+K palette (#129)

- New `command-dispatch/` directory with:
  - `useDispatch.ts` hook: POST /api/dispatch, availability probe, error/loading state
  - `DispatchButton.tsx`: icon button (RotateCw/Square) with loading/success/error states
  - `CommandPalette.tsx`: Cmd+K/Ctrl+K overlay with node search, JSON payload editor, dispatch result; includes `useCommandPalette` hook for global keyboard listener
  - `CommandPalette.stories.tsx`: Open, WithResults, Dispatching, Error story exports
- `TraceExplorerWidget.tsx`: adds Stop button on running traces, Re-run button on completed traces
- `App.tsx`: mounts `CommandPalette` at app root via `useCommandPalette` hook

Closes OMN-12147, relates OMN-12148

* fix(OMN-12149): align dispatch command topic to omnimarket namespace (#131)

The node_dispatch_request_handler subscribes to
onex.cmd.omnimarket.dispatch-request.v1 (topic naming lint requires
consumer service namespace). Aligns the Express bridge publisher topic.

* chore(OMN-12171): delete orphaned .js files and add CI enforcement gate (#132)

* chore(OMN-12171): delete orphaned .js files and add CI enforcement gate

Confirms 298 .js artifacts in src/ and shared/ are stale (all have .ts/.tsx
counterparts; tsconfig emits nothing to src/; build output goes to dist/).
Deletes them from working tree.

Adds scripts/check-no-orphaned-js.sh — fails if any .js file in src/ or shared/
shadows a .ts/.tsx source — and wires it as a CI gate in ci.yml.

Also fixes broken @storybook/test import in CommandPalette.stories.tsx
(package moved to storybook/test in v10), restoring tsc --noEmit clean pass.

* fix(OMN-12171): include command topic in server typecheck

* refactor(OMN-12187): Consolidate DRY violations in omnidash

* refactor(OMN-12187): Consolidate DRY violations in omnidash

Extract shared pure-data helpers (timestampValue, mergeDelegationSessions,
buildCostSavingsOverviewResult) into server/projection-reader-shared.ts.
Both sqlite-projection-reader.ts and postgres-projection-reader.ts delegate
to the shared module, eliminating ~120 lines of duplicated logic.

Add 13 unit tests for the shared module covering edge cases in timestamp
parsing, session merge semantics, and cost overview aggregation.

The integration test files (part2/part3/part4) are distinct suites with
different test content — they are not duplicates and are left intact.

* fix(OMN-12187): remove unused cost overview input

* chore: release v1.1.0

* ci: re-trigger CI for release promotion PR

* feat(OMN-6877): add 4 observability pages — trace explorer, session replay, sandbox monitor, voice sessions (#136)

- TraceExplorerPage: wraps existing TraceExplorerWidget as a full-page view
- SessionReplayPage: step/play/rewind through recorded sessions with state reconstruction and progress bar
- SandboxMonitorPage: real-time allow/deny stream, deny-rate bar, per-policy breakdown table
- VoiceSessionPage: audio timeline, transcript overlay, turn-taking visualization
- Extends AppPage type and App.tsx routing switch for all 4 new pages
- Adds Observability section to Sidebar with GitBranch/RotateCcw/Shield/Mic icons
- Adds sandboxDecisions, voiceSessions, sessionReplay topics to TOPICS constant

* ci(OMN-12243): add main target guard (#138)

* release: promote omnidash dev to main (2026-06-06) (#174)

* feat(OMN-11979): redesign delegation dashboard — dense metrics, narrative labels, stale .js cleanup (#119)

- Replace donut chart with terminal-style KPI tiles + stacked model bar + task breakdown
- Rename Quality Gate → Escalation Detail, humanize failure categories
- Fix postgres-projection-reader: correct column names, JSONB string coercion
- Add /projection Vite proxy for Express bridge
- Remove 8 stale compiled .js files that shadowed .tsx source via Vite resolution
- "Quality Gate" → "Local Success", "pass/fail" → "local/escalated" throughout

* feat(OMN-12069): delegation correlation trace view

Adds the delegation correlation trace view, trigger panel, and dashboard assertion updates needed for CI.

* feat(OMN-12133): add projection query endpoints for log entries and traces (#122)

Adds GET /api/projections/log-entries and GET /api/projections/traces
to the Express server, backed by new queryLogEntries and queryTraces
methods on PostgresProjectionReader. Adds traceExplorer topic constant
to shared/types/topics.ts. All query params are validated; limits are
capped (1000 for entries, 500 for traces). is_running = true when
last_event_at < 60s ago. 17 new unit tests + 2 route-level tests.

* feat(OMN-12089): B6 delegation savings proof pack — printable audit artifact (#123)

Adds DelegationSavingsProofPackPanel as a new 'Savings Proof Pack' tab in
the delegation control plane evidence tabs. Renders an exportable audit
report from live projection data: correlation IDs for every run, routing
decisions (model, rule, confidence), quality gate results (pass/fail +
detail), pricing manifest version, per-run savings, cumulative savings
summary, and a pass-rate totals row.

Print/Save as PDF is handled via window.print() with an injected @media
print stylesheet that isolates the printable region to the proof pack table.
No server-side PDF generation or new backend endpoints required — all data
sourced from DelegationEvidenceSnapshot already in context.

7 tests covering empty state, header rendering, run table, quality gate
labels, routing display, totals row, and print button.

* feat(OMN-12085): add swarm-runs projection topic to PostgresProjectionReader (#124)

Adds onex.snapshot.projection.swarm-runs.v1 case to the Postgres projection
reader. Queries omnidash_analytics.swarm_runs and returns all T4 display
fields: runId, status, subtask/succeeded/failed counts, modelsUsed,
totalCostUsd, cloudEquivalentCostUsd, savingsUsd, parallelismSpeedupRatio,
all latency fields, and createdAt. The swarm_runs table already exists on
stability-test (15436) with 11 rows.

* feat(OMN-12135): add TraceExplorerWidget — two-panel event bus trace explorer (#126)

Adds TraceExplorerWidget to omnidash for event bus observability Phase 2.
The widget consumes onex.snapshot.projection.traces.v1 to render a
left-panel trace list (with running/error indicators) and right-panel
chronological timeline of log entries for the selected trace.

- TraceExplorerWidget.tsx: two-panel layout, trace cards, timeline, search
- TraceExplorerWidget.stories.tsx: Empty, Populated, WithRunningTrace, ErrorTraces, Loading
- TraceExplorerWidget.test.tsx: 5 unit tests (loading, empty, render, filter, select prompt)
- storybook/fixtures/trace-explorer.ts: buildTraceGroups fixture factory
- generate-registry.ts: trace-explorer manifest with projectionSchema + displayContract
- index.ts: lazy import wired under trace-explorer/TraceExplorerWidget
- integration.part2/3: component count bumped 28→29 with trace-explorer

Closes OMN-12135. References OMN-12136, OMN-12137, OMN-12138, OMN-12139.

* feat(OMN-12082): add Context Effectiveness Heatmap widget (B4) (#127)

Implements the context factor effectiveness visualization showing which
context segments help/hurt which models across A3 ablation experiments:

- Segment × model matrix with color-coded cells:
  green (≥75% pass rate), amber (26–74%), red (≤25%)
- Clickable cells open an examples panel showing specific token impact,
  pass/fail counts, and qualitative findings per segment×model combination
- Fixture mode: when no live data exists, shows OMN-11241 research results
  (golden_chain green across all models, claude_md/architecture_patterns
  red for local models, exemplar green for exact-interface tasks,
  local_failures neutral)
- Fixture badge clearly marks when live projection is absent
- Reads from context_experiment_scores table via new
  onex.snapshot.projection.context.experiment-scores.v1 topic
- Adds postgres reader case for context_experiment_scores SELECT
- 10 tests passing (empty state, matrix render, cell selection, fixture
  badge, toggle deselect, footer count)

* feat(OMN-12145): add Express dispatch bridge and Kafka command topic (#128)

- POST /api/dispatch validates body, builds envelope, publishes to
  onex.cmd.omnidash.dispatch-request.v1 via kafkajs producer
- Returns 400 on missing fields, 503 when broker unavailable
- Kafka producer connects on server start, graceful disconnect on SIGTERM/SIGINT
- COMMAND_TOPICS registry in shared/types/command-topics.ts (separate from
  TOPICS to preserve golden-chain onex.snapshot.projection.* invariant)
- 9 unit tests with mocked producer covering validation, 503, and happy path

Closes OMN-12145, OMN-12149

* feat(OMN-12147): add command dispatch UI — trace action buttons + Cmd+K palette (#129)

- New `command-dispatch/` directory with:
  - `useDispatch.ts` hook: POST /api/dispatch, availability probe, error/loading state
  - `DispatchButton.tsx`: icon button (RotateCw/Square) with loading/success/error states
  - `CommandPalette.tsx`: Cmd+K/Ctrl+K overlay with node search, JSON payload editor, dispatch result; includes `useCommandPalette` hook for global keyboard listener
  - `CommandPalette.stories.tsx`: Open, WithResults, Dispatching, Error story exports
- `TraceExplorerWidget.tsx`: adds Stop button on running traces, Re-run button on completed traces
- `App.tsx`: mounts `CommandPalette` at app root via `useCommandPalette` hook

Closes OMN-12147, relates OMN-12148

* fix(OMN-12149): align dispatch command topic to omnimarket namespace (#131)

The node_dispatch_request_handler subscribes to
onex.cmd.omnimarket.dispatch-request.v1 (topic naming lint requires
consumer service namespace). Aligns the Express bridge publisher topic.

* chore(OMN-12171): delete orphaned .js files and add CI enforcement gate (#132)

* chore(OMN-12171): delete orphaned .js files and add CI enforcement gate

Confirms 298 .js artifacts in src/ and shared/ are stale (all have .ts/.tsx
counterparts; tsconfig emits nothing to src/; build output goes to dist/).
Deletes them from working tree.

Adds scripts/check-no-orphaned-js.sh — fails if any .js file in src/ or shared/
shadows a .ts/.tsx source — and wires it as a CI gate in ci.yml.

Also fixes broken @storybook/test import in CommandPalette.stories.tsx
(package moved to storybook/test in v10), restoring tsc --noEmit clean pass.

* fix(OMN-12171): include command topic in server typecheck

* refactor(OMN-12187): Consolidate DRY violations in omnidash

* refactor(OMN-12187): Consolidate DRY violations in omnidash

Extract shared pure-data helpers (timestampValue, mergeDelegationSessions,
buildCostSavingsOverviewResult) into server/projection-reader-shared.ts.
Both sqlite-projection-reader.ts and postgres-projection-reader.ts delegate
to the shared module, eliminating ~120 lines of duplicated logic.

Add 13 unit tests for the shared module covering edge cases in timestamp
parsing, session merge semantics, and cost overview aggregation.

The integration test files (part2/part3/part4) are distinct suites with
different test content — they are not duplicates and are left intact.

* fix(OMN-12187): remove unused cost overview input

* feat(OMN-12072): Swarm Control Plane widget — decomposition tree, wave view, live counters, savings bar (#125)

* feat(OMN-12072): add Swarm Control Plane widget (B2-T1 through B2-T5)

Implements the full swarm progression UI against the swarm_runs projection:
- B2-T1: SwarmDecompositionTree — parent→subtask tree with aggregate-derived
  per-subtask statuses and model hints; graceful empty state when subtask_count=0
- B2-T2: SwarmSubtaskCard — colored status badge, 3×3 metrics grid, models-used
  pill tags, registry hash footer
- B2-T3: SwarmWaveVisualization — heuristic 60/40 wave split with dependency
  connector; Wave 1 dimmed until run complete; empty state when subtask_count=0
- B2-T4: SwarmLiveCounters — 6 aggregate cards (total runs, subtasks, avg speedup,
  wall time, cost, savings) derived from swarm_runs projection
- B2-T5: SwarmSavingsBar — stacked local+savings bar vs cloud equivalent baseline,
  per-run mini bars (up to 10), zero-cost graceful empty state
- SwarmRunTable: 8-column run list with row selection, speedup/savings coloring
- SwarmControlPlane: tabbed widget (Overview, Decomposition, Run Detail, Wave View,
  Savings) wired to swarm.runs.v1 projection topic via useProjectionQuery
- Adds TOPICS.swarmRuns, swarm-runs.v1 postgres case, GET /api/swarm-runs route
- 10 tests passing

* feat(OMN-12072): add SwarmSubtaskDetailPanel — per-subtask model/tokens/cost/latency table (B5-T3 gap)

Adds SwarmSubtaskDetailPanel showing per-subtask rows with:
- subtask_id (derived: run_id prefix + padded index)
- description (Subtask 01, 02, ...)
- model assigned (cycled from run.models_used)
- status (succeeded/failed/skipped/pending, derived from aggregate counts)
- tokens_input / tokens_output (null — requires node_swarm_subtask_state_reducer)
- cost_usd (evenly distributed from total_cost_usd)
- latency_ms (estimated from dispatch_wall_latency_ms / parallelism)

Panel is clearly marked as aggregate-derived with a fixture notice.
Shows "No subtask data" with reducer name when subtask_count=0.

Wired as new "Subtask Detail" tab in SwarmControlPlane alongside existing
"Run Detail" aggregate card. Uses List icon from lucide-react.

Schema confirmed: swarm_runs has no jsonb subtask column and no separate
swarm_subtasks table exists yet in stability-test. All per-subtask data
requires node_swarm_subtask_state_reducer projection.

15 new tests + 10 existing passing (25 total).

* refactor(OMN-11578/OMN-11596): reduce cyclomatic complexity in ThreeCanvas and parseDashboardDefinition (#137)

OMN-11578: Extract advanceFocusProgress, applyFocusedOpacities,
applyIdleOpacities, and projectAxisLabels from the ThreeCanvas
useEffect god-function (CCN=83). The tick loop is now a thin
orchestrator calling four named helpers via a TickRefs context struct.

OMN-11596: Extract validateTopLevelFields and validateLayoutItem from
parseDashboardDefinition (CCN=31). The parse function is reduced to
an 8-line orchestrator; all conditional branches live in the helpers.

* fix(OMN-12245): silence unused swarm route request (#139)

* feat(OMN-11658): wire SEA demo dashboard projections (#140)

* feat(OMN-11658): wire SEA demo dashboard projections

* fix(OMN-11658): make trace rows toggle independently

* fix(OMN-11658): polish SEA dashboard rendering

* feat(OMN-11658): expose delegation trigger in SEA control plane (#141)

* fix(OMN-11658): improve SEA prompt feedback (#142)

* fix(OMN-11658): let dashboard fill wide viewports (#143)

* fix(OMN-11658): balance dashboard gutters (#144)

* fix(OMN-11658): keep SEA stream newest first (#145)

* fix(OMN-11658): refresh SEA traces after prompt submit (#146)

* fix(OMN-11658): center dashboard shell (#147)

* fix(OMN-11658): extend dashboard body to bottom (#148)

* fix(OMN-11658): repair empty SEA dashboard layout (#149)

* chore(DELEG-002): annotate pricing fallbacks, align fixture model names with bifrost config (#150)

* feat(OMN-12070): wire delegation trigger through omnimarket runtime (#151)

- Add COMMAND_TOPICS.delegateSkill ('onex.cmd.omnimarket.delegate-skill.v1')
  to shared/types/command-topics.ts so the topic is contract-declared rather
  than hardcoded in server/routes.ts
- Declare the delegation trigger command topic in contract.yaml with handler
  node, ingress path, payload shape, and OMNIDASH_RUNTIME_EFFECTS_URL env note
- server/routes.ts uses COMMAND_TOPICS.delegateSkill constant; runtime targets
  stability-test :18085/v1/effects via OMNIDASH_RUNTIME_EFFECTS_URL env var
- DelegationRunContext exposes pendingCorrelationId + setPendingCorrelationId
  so a freshly-dispatched correlation_id propagates to all child panels before
  projection rows materialize
- DelegationRunHeader wires onCorrelationId callback to DelegationTriggerPanel
- DelegationEvidenceTabs auto-navigates to correlation-trace tab on dispatch
  and displays a pending indicator with the correlation_id
- DelegationCorrelationTracePanel falls back to pendingCorrelationId when no
  run is selected and shows appropriate pending vs awaiting-events messages
- Test: setPendingCorrelationId updates context value (DelegationRunContext.test)

* fix(OMN-12243): add 'delegation' short-topic alias to postgres + sqlite projection readers (#152)

GET /projection/delegation was returning rows:[] because the query() switch
had no 'delegation' case — only the full versioned topic name. Adds a
fall-through alias in both PostgresProjectionReader and SqliteProjectionReader
so the short topic name routes to the same decisions.v1 query logic.

Test: new routes.test.ts case seeds a SQLite DB and asserts the short alias
returns the same rows as the full topic name.

* fix(OMN-12287): close fixture gaps and add file-mode banners to dashboard widgets (#153)

- Add fileMode prop to ComponentWrapper: renders a [File Mode] badge in the
  widget header when active, analogous to the [Live] badge, using Text component
  to comply with the no-typography-inline ESLint rule
- Wire fileMode={!isLiveDataSource(mode)} on all 13 widgets that use
  useDataSourceMode: TraceExplorer, LiveEventStream, ContextHeatmap,
  DelegationControlPlane, EventStream, ReadinessGate, DelegationMetrics2D,
  DelegationSavingsWidget, DelegationTokenUsageWidget,
  DelegationModelRoutingWidget, DelegationQualityGateWidget, AbCompareWidget
- Improve emptyHint messages on all updated widgets to include file-mode
  fixture directory paths so developers know exactly where to add test data
- 1124/1124 tests pass

* feat(OMN-12288): deep-link delegation runs to trace explorer (#154)

- Add traceFilter + setTraceFilter to UISlice so a correlation_id can be
  passed to TraceExplorer before navigating there (no URL routing required)
- DelegationRunTable adds a GitCommitHorizontal icon button per row; clicking
  it calls setTraceFilter(correlationId) + setActivePage('trace') and stops
  event propagation so the row-select handler is not also triggered
- TraceExplorerWidget initializes its query state from traceFilter on mount
  and immediately clears the store value so subsequent navigations start fresh
- Tests: DelegationRunTable.test.tsx (7 cases), TraceExplorerWidget deep-link
  test confirms filter pre-fill + store clear (1132 total tests pass)

* feat(T22): add DelegationCostComparisonWidget — per-run cost proof surface (#155)

- New widget DelegationCostComparisonWidget in delegation/ directory reads from
  TOPICS.delegationSavings (same source as DelegationSavingsWidget) and surfaces
  a per-session cost comparison table with columns: Session, Task, Model, Tokens,
  Latency, Local $, Cloud $, Saved, Provenance
- Provenance badges (measured/estimated) on each row per the § 1.3 spec — green
  for measured (llm_call_metrics), amber for estimated (routing-rule inferred)
- Summary KPI bar shows cumulative savings, local/cloud cost, session count,
  total tokens, measured vs estimated breakdown
- Sort controls: date (default), savings descending, tokens descending
- maxRows config cap with overflow notice; showProvenance=false hides source column
- Registered as 'delegation-cost-comparison' in generate-registry.ts and index.ts
- Integration tests updated for count 29 → 30
- 1132/1132 tests pass

* fix(OMN-12402): unify CommandPalette node-type badges and Dispatch button contrast (#156)

Issue 1: kindColor() mixed design tokens (var(--accent), var(--text-tertiary))
with hardcoded hex (#d97706, #7c3aed), so ORCHESTRATOR/COMPUTE badges rendered
as weak plain text while EFFECT/REDUCER read as vivid chips. Drive all four
badges from the design-system node-type role tokens (--<kind>{,-soft,-ink}),
which exist for both themes, so every badge is a consistent filled chip.

Issue 2: the Dispatch button used var(--accent) as its fill (which resolves to
--brand-soft, a pale hover tint) plus literal color:'white' text — invisible in
light mode. Use the brand fill + on-brand text token (--brand /
--primary-foreground) so the CTA stays legible in both themes.

Adds CommandPalette.test.tsx asserting both fixes. Visual proof:
docs/evidence/OMN-12402-{badges,dispatch-light}.png

* fix(OMN-12367): render Correlation Trace tab gracefully in file mode (#159)

In VITE_DATA_SOURCE=file mode the /api/delegation/* proxy is not registered
(it is wired only when VITE_PROJECTION_API_URL is set), so fetchCorrelationTrace
hit the Vite SPA fallback, received index.html, and surfaced a raw
'JSON.parse: unexpected character at line 1 column 1' error.

Detect file mode up front in fetchCorrelationTrace and read a per-correlation
fixture under fixtures/onex.snapshot.projection.delegation.correlation-trace.v1/
so the surface renders a representative event chain without postgres. In live
mode, parse defensively (res.json().catch) so any non-JSON body degrades to an
empty trace and the panel shows its graceful no-events message rather than a raw
parse error.

Adds the delegationCorrelationTrace topic, a representative 2-event fixture for
the file-mode decisions correlation (069e55b5...), and tests covering file-mode
fixture fetch and non-JSON hardening.

* fix(OMN-12399): register Context Effectiveness Heatmap widget in palette (#158)

The widget source (component, hook, fixtures, types, test) existed but was
registered in zero of the three Track A surfaces, so it never appeared in the
widget palette and was unreachable through the supported user flow.

- Add lazy import to componentImports in src/components/dashboard/index.ts
- Add context-effectiveness-heatmap manifest to MVP_COMPONENTS in
  scripts/generate-registry.ts (quality category, contextExperimentScores
  projection, OMN-11241 fixture fallback)
- Regenerate src/registry/component-registry.json (30 -> 31 components)
- Update integration.part2/part3 component-count + name-list assertions

Visual proof (OMN-7093): palette entry + widget rendered on dashboard in
file mode using the OMN-11241 research fixture.

* fix(OMN-12366): give Delegation Control Plane active tab a clear visual indicator (#157)

The 10-tab evidence strip distinguished active from inactive tabs only by
background (var(--panel-2) vs transparent); border, text color, and weight
were identical, so the strip did not read as tabs.

Active tab now uses the --brand accent token: brand border, a 2px brand
bottom-border underline, --brand-soft background, and brand-colored
semibold label text. All via existing design tokens (no hardcoded hex),
verified to read clearly in both light and dark themes.

Visual evidence (OMN-7093):
- docs/evidence/OMN-12366-tab-strip-dark.png
- docs/evidence/OMN-12366-tab-strip-light.png

* feat(OMN-12139): add Trace Explorer fixture for onex.snapshot.projection.traces.v1 (#161)

Wires a _trace_groups() factory into scripts/generate_fixtures.py and emits
five representative TraceGroup records under
fixtures/onex.snapshot.projection.traces.v1/, covering every TraceExplorer
UI state: multi-node completed, single-node completed, running, error, and a
longer multi-node trace. The widget reads each file as one TraceGroup via the
legacy useProjectionQuery readAll path; the file-source middleware
auto-generates index.json.

Shapes mirror the widget's TraceGroup interface exactly (no entity_id); each
record is keyed by its correlation_id so the fixture filename matches.

DoD: VITE_DATA_SOURCE=file dev server renders 5 traces (1 running, 1 error)
in the Trace Explorer in File Mode; Playwright screenshot under
docs/evidence/OMN-12139-trace-explorer-file-mode.png proves populated/running/
error states (OMN-7093). npm run build, lint, and full vitest suite (1172
tests) green.

* fix(OMN-12397): make Savings Proof Pack source line dynamic by data mode (#160)

The printable proof pack hardcoded "Source: OmniNode delegation_events
projection" even in file mode, so a PDF exported from fixture data was
misleading once removed from the parent Fixture banner. Derive the
"Source:" line and reproducibility sentence from useDataSourceMode():

  file     -> Local fixture file (fixtures/.../demo-delegation-snapshot.json)
  sqlite   -> Local SQLite projection
  http     -> OmniNode projection API via Express bridge
  postgres -> OmniNode delegation_events projection

Non-live modes (file/sqlite) state the report is from a local
development data source and is not a live projection.

Renamed fixture live-delegation-demo.json -> demo-delegation-snapshot.json;
the word 'live' inside fixtures/ was second-order misleading. The file is
discovered by directory glob (vite fixturesMiddleware readdirSync), so no
code references the old name.

dod_evidence: rendered_output screenshot
docs/evidence/OMN-12397-proof-pack-file-mode-source.png shows the
file-mode footer with the fixture source string and 15 fixture rows.

* fix(OMN-12392): repoint run-generate-fixtures.sh to local script and correct CLAUDE.md fixture claim (#162)

OMN-12392: `run-generate-fixtures.sh` was looking for `generate_fixtures.py` at
`$OMNIBASE_INFRA_PATH/scripts/generate_fixtures.py`, but that path does not exist in
omnibase_infra. The script lives in this repo at `scripts/generate_fixtures.py`.
Fix: set SCRIPT_PATH to `$REPO/scripts/generate_fixtures.py` while preserving
`--project "$OMNIBASE_INFRA_PATH"` for uv dependency resolution (the script imports
from omnibase_infra). `npm run generate:fixtures` now works without a non-standard
OMNIBASE_INFRA_PATH.

Also fix CLAUDE.md hard rule #6 which claimed "Fixtures and layouts are not committed"
— fixtures/ IS tracked via the OMN-10305 gitignore carve-out (!fixtures/, !fixtures/**).
Correct the claim and add the carve-out explanation.

OMN-12393: add a lineage classification table to CLAUDE.md documenting that
omnidash/ (dev) is CANONICAL_ACTIVE, omnidash-v2/ is SUPERSEDED, and
omnidash-v2-new/ is a TEMP_STAGING clone. Cite verification evidence.

* test(OMN-10947): pin dashboard e2e fixture proof (#163)

* test(OMN-10947): scope dashboard e2e locators (#164)

* fix(OMN-12394): add .env to omnidash .gitignore to prevent credential leak (#165)

The .env file holds OMNIDASH_ANALYTICS_DB_URL with a live Postgres password.
The existing rule only covered .env.local* — .env itself was unprotected.

Adds an explicit .env entry so git never tracks the credential file.

* fix: move omnidash event bus config to contract overlay (#166)

* chore(OMN-12459): add universal managed gitignore block to omnidash (#168)

Appends the onex-managed universal block to .gitignore, adding
.env.*, !.env.example, *.log, .idea/, .vscode/, test-results/, and
playwright-report/ coverage. .env and .DS_Store were already present
individually; the block makes the policy explicit and machine-readable.

No tracked files required untracking.

* ci(OMN-12477): add dev-only main-target-guard (forward-port from main) (#167)

Forward-ports the main-target-guard workflow to dev (the file existed on
main but was absent on dev — main<->dev drift). Adds the dev-only variant
WITHOUT the hotfix/* bypass: main accepts only dev promotion PRs carrying
promotion-receipt: OCC-N. The guard only triggers on base==main, so it is
inert on dev-targeting PRs and takes effect at the next dev->main promotion.

OMN-12477

* feat(OMN-12674): remove reviewdog workflows and configs (#170)

Removes the reviewdog caller workflow from omnidash. No reviewdog
required-status-check bindings existed on main protection, dev
protection, or any repo ruleset, so this is the only active reviewdog
surface in the repo.

dod_evidence: OMN-12674

* fix(OMN-12739): tighten delegation dashboard formatting (#171)

* fix(OMN-12740): add browser-safe dashboard id fallback (#172)

* ci: preserve hotfix main-target allowance for release

* chore(release)(OMN-12245): bump omnidash to v1.1.1

* chore(OMN-12765): backmerge current main into dash dev lane (#183)

* feat(OMN-11614, OMN-11617): deduplicate projection readers and integration test utilities (#120)

* feat(OMN-11979): redesign delegation dashboard — dense metrics, narrative labels, stale .js cleanup (#119)

- Replace donut chart with terminal-style KPI tiles + stacked model bar + task breakdown
- Rename Quality Gate → Escalation Detail, humanize failure categories
- Fix postgres-projection-reader: correct column names, JSONB string coercion
- Add /projection Vite proxy for Express bridge
- Remove 8 stale compiled .js files that shadowed .tsx source via Vite resolution
- "Quality Gate" → "Local Success", "pass/fail" → "local/escalated" throughout

* feat(OMN-11614, OMN-11617): deduplicate projection readers and integration test utilities

OMN-11614: Extract shared session-merge and cost-savings-overview aggregation
logic from postgres-projection-reader.ts and sqlite-projection-reader.ts into
server/projection-utils.ts. Removes ~160 lines of duplicated code addressed
by the TODO(OMN-10976) comment. Fixes pre-existing test failures in
postgres-projection-reader.test.ts where string-typed token assertions
contradicted the numeric coercion the reader actually applies.

OMN-11617: Extrac…
andywu42 pushed a commit to andywu42/omnidash that referenced this pull request Jul 16, 2026
…mniNode-ai#201) (OmniNode-ai#202)

* feat(OMN-11614, OMN-11617): deduplicate projection readers and integration test utilities (#120)

* feat(OMN-11979): redesign delegation dashboard — dense metrics, narrative labels, stale .js cleanup (#119)

- Replace donut chart with terminal-style KPI tiles + stacked model bar + task breakdown
- Rename Quality Gate → Escalation Detail, humanize failure categories
- Fix postgres-projection-reader: correct column names, JSONB string coercion
- Add /projection Vite proxy for Express bridge
- Remove 8 stale compiled .js files that shadowed .tsx source via Vite resolution
- "Quality Gate" → "Local Success", "pass/fail" → "local/escalated" throughout

* feat(OMN-11614, OMN-11617): deduplicate projection readers and integration test utilities

OMN-11614: Extract shared session-merge and cost-savings-overview aggregation
logic from postgres-projection-reader.ts and sqlite-projection-reader.ts into
server/projection-utils.ts. Removes ~160 lines of duplicated code addressed
by the TODO(OMN-10976) comment. Fixes pre-existing test failures in
postgres-projection-reader.test.ts where string-typed token assertions
contradicted the numeric coercion the reader actually applies.

OMN-11617: Extract manifest loading and QueryClient factory into
src/test-utils/integrationHelpers.ts. Updates integration.part2/3/4.test.tsx
to import TEST_MANIFEST and createTestQueryClient instead of duplicating the
readFileSync + JSON.parse pattern and bare QueryClient constructor.

17 new unit tests for projection-utils.ts cover timestampValue, sessionKey,
mergeDelegationSessions, sessionTokens, and buildCostSavingsOverview.

* fix(OMN-11614, OMN-11617): update delegation widget tests to match OMN-11979 redesign

Tests were written against the old design (KPI tiles with "Pass rate",
"Passed", "Failed" labels; donut panel 420px wide; "QG Pass" column;
"Failure categories" heading; "Recent checks" label). OMN-11979 shipped
a dense terminal-style redesign that changed these rendered strings.

Update assertions to match the current component output:
- DelegationMetrics3D: maxWidth 420→240, canvas height 200→140
- DelegationMetrics2D: "N/M pass" → "N/M local"
- DelegationModelRoutingWidget: column "QG Pass" → "Local Rate"
- DelegationQualityGateWidget: empty msg, check-type labels, section
  headings, recent-checks label all updated to current rendered text

All 1044 tests pass (0 new failures introduced).

* chore: release v1.1.0 (OMN-12245) (#135)

* feat(OMN-11979): redesign delegation dashboard — dense metrics, narrative labels, stale .js cleanup (#119)

- Replace donut chart with terminal-style KPI tiles + stacked model bar + task breakdown
- Rename Quality Gate → Escalation Detail, humanize failure categories
- Fix postgres-projection-reader: correct column names, JSONB string coercion
- Add /projection Vite proxy for Express bridge
- Remove 8 stale compiled .js files that shadowed .tsx source via Vite resolution
- "Quality Gate" → "Local Success", "pass/fail" → "local/escalated" throughout

* feat(OMN-12069): delegation correlation trace view

Adds the delegation correlation trace view, trigger panel, and dashboard assertion updates needed for CI.

* feat(OMN-12133): add projection query endpoints for log entries and traces (#122)

Adds GET /api/projections/log-entries and GET /api/projections/traces
to the Express server, backed by new queryLogEntries and queryTraces
methods on PostgresProjectionReader. Adds traceExplorer topic constant
to shared/types/topics.ts. All query params are validated; limits are
capped (1000 for entries, 500 for traces). is_running = true when
last_event_at < 60s ago. 17 new unit tests + 2 route-level tests.

* feat(OMN-12089): B6 delegation savings proof pack — printable audit artifact (#123)

Adds DelegationSavingsProofPackPanel as a new 'Savings Proof Pack' tab in
the delegation control plane evidence tabs. Renders an exportable audit
report from live projection data: correlation IDs for every run, routing
decisions (model, rule, confidence), quality gate results (pass/fail +
detail), pricing manifest version, per-run savings, cumulative savings
summary, and a pass-rate totals row.

Print/Save as PDF is handled via window.print() with an injected @media
print stylesheet that isolates the printable region to the proof pack table.
No server-side PDF generation or new backend endpoints required — all data
sourced from DelegationEvidenceSnapshot already in context.

7 tests covering empty state, header rendering, run table, quality gate
labels, routing display, totals row, and print button.

* feat(OMN-12085): add swarm-runs projection topic to PostgresProjectionReader (#124)

Adds onex.snapshot.projection.swarm-runs.v1 case to the Postgres projection
reader. Queries omnidash_analytics.swarm_runs and returns all T4 display
fields: runId, status, subtask/succeeded/failed counts, modelsUsed,
totalCostUsd, cloudEquivalentCostUsd, savingsUsd, parallelismSpeedupRatio,
all latency fields, and createdAt. The swarm_runs table already exists on
stability-test (15436) with 11 rows.

* feat(OMN-12135): add TraceExplorerWidget — two-panel event bus trace explorer (#126)

Adds TraceExplorerWidget to omnidash for event bus observability Phase 2.
The widget consumes onex.snapshot.projection.traces.v1 to render a
left-panel trace list (with running/error indicators) and right-panel
chronological timeline of log entries for the selected trace.

- TraceExplorerWidget.tsx: two-panel layout, trace cards, timeline, search
- TraceExplorerWidget.stories.tsx: Empty, Populated, WithRunningTrace, ErrorTraces, Loading
- TraceExplorerWidget.test.tsx: 5 unit tests (loading, empty, render, filter, select prompt)
- storybook/fixtures/trace-explorer.ts: buildTraceGroups fixture factory
- generate-registry.ts: trace-explorer manifest with projectionSchema + displayContract
- index.ts: lazy import wired under trace-explorer/TraceExplorerWidget
- integration.part2/3: component count bumped 28→29 with trace-explorer

Closes OMN-12135. References OMN-12136, OMN-12137, OMN-12138, OMN-12139.

* feat(OMN-12082): add Context Effectiveness Heatmap widget (B4) (#127)

Implements the context factor effectiveness visualization showing which
context segments help/hurt which models across A3 ablation experiments:

- Segment × model matrix with color-coded cells:
  green (≥75% pass rate), amber (26–74%), red (≤25%)
- Clickable cells open an examples panel showing specific token impact,
  pass/fail counts, and qualitative findings per segment×model combination
- Fixture mode: when no live data exists, shows OMN-11241 research results
  (golden_chain green across all models, claude_md/architecture_patterns
  red for local models, exemplar green for exact-interface tasks,
  local_failures neutral)
- Fixture badge clearly marks when live projection is absent
- Reads from context_experiment_scores table via new
  onex.snapshot.projection.context.experiment-scores.v1 topic
- Adds postgres reader case for context_experiment_scores SELECT
- 10 tests passing (empty state, matrix render, cell selection, fixture
  badge, toggle deselect, footer count)

* feat(OMN-12145): add Express dispatch bridge and Kafka command topic (#128)

- POST /api/dispatch validates body, builds envelope, publishes to
  onex.cmd.omnidash.dispatch-request.v1 via kafkajs producer
- Returns 400 on missing fields, 503 when broker unavailable
- Kafka producer connects on server start, graceful disconnect on SIGTERM/SIGINT
- COMMAND_TOPICS registry in shared/types/command-topics.ts (separate from
  TOPICS to preserve golden-chain onex.snapshot.projection.* invariant)
- 9 unit tests with mocked producer covering validation, 503, and happy path

Closes OMN-12145, OMN-12149

* feat(OMN-12147): add command dispatch UI — trace action buttons + Cmd+K palette (#129)

- New `command-dispatch/` directory with:
  - `useDispatch.ts` hook: POST /api/dispatch, availability probe, error/loading state
  - `DispatchButton.tsx`: icon button (RotateCw/Square) with loading/success/error states
  - `CommandPalette.tsx`: Cmd+K/Ctrl+K overlay with node search, JSON payload editor, dispatch result; includes `useCommandPalette` hook for global keyboard listener
  - `CommandPalette.stories.tsx`: Open, WithResults, Dispatching, Error story exports
- `TraceExplorerWidget.tsx`: adds Stop button on running traces, Re-run button on completed traces
- `App.tsx`: mounts `CommandPalette` at app root via `useCommandPalette` hook

Closes OMN-12147, relates OMN-12148

* fix(OMN-12149): align dispatch command topic to omnimarket namespace (#131)

The node_dispatch_request_handler subscribes to
onex.cmd.omnimarket.dispatch-request.v1 (topic naming lint requires
consumer service namespace). Aligns the Express bridge publisher topic.

* chore(OMN-12171): delete orphaned .js files and add CI enforcement gate (#132)

* chore(OMN-12171): delete orphaned .js files and add CI enforcement gate

Confirms 298 .js artifacts in src/ and shared/ are stale (all have .ts/.tsx
counterparts; tsconfig emits nothing to src/; build output goes to dist/).
Deletes them from working tree.

Adds scripts/check-no-orphaned-js.sh — fails if any .js file in src/ or shared/
shadows a .ts/.tsx source — and wires it as a CI gate in ci.yml.

Also fixes broken @storybook/test import in CommandPalette.stories.tsx
(package moved to storybook/test in v10), restoring tsc --noEmit clean pass.

* fix(OMN-12171): include command topic in server typecheck

* refactor(OMN-12187): Consolidate DRY violations in omnidash

* refactor(OMN-12187): Consolidate DRY violations in omnidash

Extract shared pure-data helpers (timestampValue, mergeDelegationSessions,
buildCostSavingsOverviewResult) into server/projection-reader-shared.ts.
Both sqlite-projection-reader.ts and postgres-projection-reader.ts delegate
to the shared module, eliminating ~120 lines of duplicated logic.

Add 13 unit tests for the shared module covering edge cases in timestamp
parsing, session merge semantics, and cost overview aggregation.

The integration test files (part2/part3/part4) are distinct suites with
different test content — they are not duplicates and are left intact.

* fix(OMN-12187): remove unused cost overview input

* chore: release v1.1.0

* ci: re-trigger CI for release promotion PR

* feat(OMN-6877): add 4 observability pages — trace explorer, session replay, sandbox monitor, voice sessions (#136)

- TraceExplorerPage: wraps existing TraceExplorerWidget as a full-page view
- SessionReplayPage: step/play/rewind through recorded sessions with state reconstruction and progress bar
- SandboxMonitorPage: real-time allow/deny stream, deny-rate bar, per-policy breakdown table
- VoiceSessionPage: audio timeline, transcript overlay, turn-taking visualization
- Extends AppPage type and App.tsx routing switch for all 4 new pages
- Adds Observability section to Sidebar with GitBranch/RotateCcw/Shield/Mic icons
- Adds sandboxDecisions, voiceSessions, sessionReplay topics to TOPICS constant

* ci(OMN-12243): add main target guard (#138)

* release: promote omnidash dev to main (2026-06-06) (#174)

* feat(OMN-11979): redesign delegation dashboard — dense metrics, narrative labels, stale .js cleanup (#119)

- Replace donut chart with terminal-style KPI tiles + stacked model bar + task breakdown
- Rename Quality Gate → Escalation Detail, humanize failure categories
- Fix postgres-projection-reader: correct column names, JSONB string coercion
- Add /projection Vite proxy for Express bridge
- Remove 8 stale compiled .js files that shadowed .tsx source via Vite resolution
- "Quality Gate" → "Local Success", "pass/fail" → "local/escalated" throughout

* feat(OMN-12069): delegation correlation trace view

Adds the delegation correlation trace view, trigger panel, and dashboard assertion updates needed for CI.

* feat(OMN-12133): add projection query endpoints for log entries and traces (#122)

Adds GET /api/projections/log-entries and GET /api/projections/traces
to the Express server, backed by new queryLogEntries and queryTraces
methods on PostgresProjectionReader. Adds traceExplorer topic constant
to shared/types/topics.ts. All query params are validated; limits are
capped (1000 for entries, 500 for traces). is_running = true when
last_event_at < 60s ago. 17 new unit tests + 2 route-level tests.

* feat(OMN-12089): B6 delegation savings proof pack — printable audit artifact (#123)

Adds DelegationSavingsProofPackPanel as a new 'Savings Proof Pack' tab in
the delegation control plane evidence tabs. Renders an exportable audit
report from live projection data: correlation IDs for every run, routing
decisions (model, rule, confidence), quality gate results (pass/fail +
detail), pricing manifest version, per-run savings, cumulative savings
summary, and a pass-rate totals row.

Print/Save as PDF is handled via window.print() with an injected @media
print stylesheet that isolates the printable region to the proof pack table.
No server-side PDF generation or new backend endpoints required — all data
sourced from DelegationEvidenceSnapshot already in context.

7 tests covering empty state, header rendering, run table, quality gate
labels, routing display, totals row, and print button.

* feat(OMN-12085): add swarm-runs projection topic to PostgresProjectionReader (#124)

Adds onex.snapshot.projection.swarm-runs.v1 case to the Postgres projection
reader. Queries omnidash_analytics.swarm_runs and returns all T4 display
fields: runId, status, subtask/succeeded/failed counts, modelsUsed,
totalCostUsd, cloudEquivalentCostUsd, savingsUsd, parallelismSpeedupRatio,
all latency fields, and createdAt. The swarm_runs table already exists on
stability-test (15436) with 11 rows.

* feat(OMN-12135): add TraceExplorerWidget — two-panel event bus trace explorer (#126)

Adds TraceExplorerWidget to omnidash for event bus observability Phase 2.
The widget consumes onex.snapshot.projection.traces.v1 to render a
left-panel trace list (with running/error indicators) and right-panel
chronological timeline of log entries for the selected trace.

- TraceExplorerWidget.tsx: two-panel layout, trace cards, timeline, search
- TraceExplorerWidget.stories.tsx: Empty, Populated, WithRunningTrace, ErrorTraces, Loading
- TraceExplorerWidget.test.tsx: 5 unit tests (loading, empty, render, filter, select prompt)
- storybook/fixtures/trace-explorer.ts: buildTraceGroups fixture factory
- generate-registry.ts: trace-explorer manifest with projectionSchema + displayContract
- index.ts: lazy import wired under trace-explorer/TraceExplorerWidget
- integration.part2/3: component count bumped 28→29 with trace-explorer

Closes OMN-12135. References OMN-12136, OMN-12137, OMN-12138, OMN-12139.

* feat(OMN-12082): add Context Effectiveness Heatmap widget (B4) (#127)

Implements the context factor effectiveness visualization showing which
context segments help/hurt which models across A3 ablation experiments:

- Segment × model matrix with color-coded cells:
  green (≥75% pass rate), amber (26–74%), red (≤25%)
- Clickable cells open an examples panel showing specific token impact,
  pass/fail counts, and qualitative findings per segment×model combination
- Fixture mode: when no live data exists, shows OMN-11241 research results
  (golden_chain green across all models, claude_md/architecture_patterns
  red for local models, exemplar green for exact-interface tasks,
  local_failures neutral)
- Fixture badge clearly marks when live projection is absent
- Reads from context_experiment_scores table via new
  onex.snapshot.projection.context.experiment-scores.v1 topic
- Adds postgres reader case for context_experiment_scores SELECT
- 10 tests passing (empty state, matrix render, cell selection, fixture
  badge, toggle deselect, footer count)

* feat(OMN-12145): add Express dispatch bridge and Kafka command topic (#128)

- POST /api/dispatch validates body, builds envelope, publishes to
  onex.cmd.omnidash.dispatch-request.v1 via kafkajs producer
- Returns 400 on missing fields, 503 when broker unavailable
- Kafka producer connects on server start, graceful disconnect on SIGTERM/SIGINT
- COMMAND_TOPICS registry in shared/types/command-topics.ts (separate from
  TOPICS to preserve golden-chain onex.snapshot.projection.* invariant)
- 9 unit tests with mocked producer covering validation, 503, and happy path

Closes OMN-12145, OMN-12149

* feat(OMN-12147): add command dispatch UI — trace action buttons + Cmd+K palette (#129)

- New `command-dispatch/` directory with:
  - `useDispatch.ts` hook: POST /api/dispatch, availability probe, error/loading state
  - `DispatchButton.tsx`: icon button (RotateCw/Square) with loading/success/error states
  - `CommandPalette.tsx`: Cmd+K/Ctrl+K overlay with node search, JSON payload editor, dispatch result; includes `useCommandPalette` hook for global keyboard listener
  - `CommandPalette.stories.tsx`: Open, WithResults, Dispatching, Error story exports
- `TraceExplorerWidget.tsx`: adds Stop button on running traces, Re-run button on completed traces
- `App.tsx`: mounts `CommandPalette` at app root via `useCommandPalette` hook

Closes OMN-12147, relates OMN-12148

* fix(OMN-12149): align dispatch command topic to omnimarket namespace (#131)

The node_dispatch_request_handler subscribes to
onex.cmd.omnimarket.dispatch-request.v1 (topic naming lint requires
consumer service namespace). Aligns the Express bridge publisher topic.

* chore(OMN-12171): delete orphaned .js files and add CI enforcement gate (#132)

* chore(OMN-12171): delete orphaned .js files and add CI enforcement gate

Confirms 298 .js artifacts in src/ and shared/ are stale (all have .ts/.tsx
counterparts; tsconfig emits nothing to src/; build output goes to dist/).
Deletes them from working tree.

Adds scripts/check-no-orphaned-js.sh — fails if any .js file in src/ or shared/
shadows a .ts/.tsx source — and wires it as a CI gate in ci.yml.

Also fixes broken @storybook/test import in CommandPalette.stories.tsx
(package moved to storybook/test in v10), restoring tsc --noEmit clean pass.

* fix(OMN-12171): include command topic in server typecheck

* refactor(OMN-12187): Consolidate DRY violations in omnidash

* refactor(OMN-12187): Consolidate DRY violations in omnidash

Extract shared pure-data helpers (timestampValue, mergeDelegationSessions,
buildCostSavingsOverviewResult) into server/projection-reader-shared.ts.
Both sqlite-projection-reader.ts and postgres-projection-reader.ts delegate
to the shared module, eliminating ~120 lines of duplicated logic.

Add 13 unit tests for the shared module covering edge cases in timestamp
parsing, session merge semantics, and cost overview aggregation.

The integration test files (part2/part3/part4) are distinct suites with
different test content — they are not duplicates and are left intact.

* fix(OMN-12187): remove unused cost overview input

* feat(OMN-12072): Swarm Control Plane widget — decomposition tree, wave view, live counters, savings bar (#125)

* feat(OMN-12072): add Swarm Control Plane widget (B2-T1 through B2-T5)

Implements the full swarm progression UI against the swarm_runs projection:
- B2-T1: SwarmDecompositionTree — parent→subtask tree with aggregate-derived
  per-subtask statuses and model hints; graceful empty state when subtask_count=0
- B2-T2: SwarmSubtaskCard — colored status badge, 3×3 metrics grid, models-used
  pill tags, registry hash footer
- B2-T3: SwarmWaveVisualization — heuristic 60/40 wave split with dependency
  connector; Wave 1 dimmed until run complete; empty state when subtask_count=0
- B2-T4: SwarmLiveCounters — 6 aggregate cards (total runs, subtasks, avg speedup,
  wall time, cost, savings) derived from swarm_runs projection
- B2-T5: SwarmSavingsBar — stacked local+savings bar vs cloud equivalent baseline,
  per-run mini bars (up to 10), zero-cost graceful empty state
- SwarmRunTable: 8-column run list with row selection, speedup/savings coloring
- SwarmControlPlane: tabbed widget (Overview, Decomposition, Run Detail, Wave View,
  Savings) wired to swarm.runs.v1 projection topic via useProjectionQuery
- Adds TOPICS.swarmRuns, swarm-runs.v1 postgres case, GET /api/swarm-runs route
- 10 tests passing

* feat(OMN-12072): add SwarmSubtaskDetailPanel — per-subtask model/tokens/cost/latency table (B5-T3 gap)

Adds SwarmSubtaskDetailPanel showing per-subtask rows with:
- subtask_id (derived: run_id prefix + padded index)
- description (Subtask 01, 02, ...)
- model assigned (cycled from run.models_used)
- status (succeeded/failed/skipped/pending, derived from aggregate counts)
- tokens_input / tokens_output (null — requires node_swarm_subtask_state_reducer)
- cost_usd (evenly distributed from total_cost_usd)
- latency_ms (estimated from dispatch_wall_latency_ms / parallelism)

Panel is clearly marked as aggregate-derived with a fixture notice.
Shows "No subtask data" with reducer name when subtask_count=0.

Wired as new "Subtask Detail" tab in SwarmControlPlane alongside existing
"Run Detail" aggregate card. Uses List icon from lucide-react.

Schema confirmed: swarm_runs has no jsonb subtask column and no separate
swarm_subtasks table exists yet in stability-test. All per-subtask data
requires node_swarm_subtask_state_reducer projection.

15 new tests + 10 existing passing (25 total).

* refactor(OMN-11578/OMN-11596): reduce cyclomatic complexity in ThreeCanvas and parseDashboardDefinition (#137)

OMN-11578: Extract advanceFocusProgress, applyFocusedOpacities,
applyIdleOpacities, and projectAxisLabels from the ThreeCanvas
useEffect god-function (CCN=83). The tick loop is now a thin
orchestrator calling four named helpers via a TickRefs context struct.

OMN-11596: Extract validateTopLevelFields and validateLayoutItem from
parseDashboardDefinition (CCN=31). The parse function is reduced to
an 8-line orchestrator; all conditional branches live in the helpers.

* fix(OMN-12245): silence unused swarm route request (#139)

* feat(OMN-11658): wire SEA demo dashboard projections (#140)

* feat(OMN-11658): wire SEA demo dashboard projections

* fix(OMN-11658): make trace rows toggle independently

* fix(OMN-11658): polish SEA dashboard rendering

* feat(OMN-11658): expose delegation trigger in SEA control plane (#141)

* fix(OMN-11658): improve SEA prompt feedback (#142)

* fix(OMN-11658): let dashboard fill wide viewports (#143)

* fix(OMN-11658): balance dashboard gutters (#144)

* fix(OMN-11658): keep SEA stream newest first (#145)

* fix(OMN-11658): refresh SEA traces after prompt submit (#146)

* fix(OMN-11658): center dashboard shell (#147)

* fix(OMN-11658): extend dashboard body to bottom (#148)

* fix(OMN-11658): repair empty SEA dashboard layout (#149)

* chore(DELEG-002): annotate pricing fallbacks, align fixture model names with bifrost config (#150)

* feat(OMN-12070): wire delegation trigger through omnimarket runtime (#151)

- Add COMMAND_TOPICS.delegateSkill ('onex.cmd.omnimarket.delegate-skill.v1')
  to shared/types/command-topics.ts so the topic is contract-declared rather
  than hardcoded in server/routes.ts
- Declare the delegation trigger command topic in contract.yaml with handler
  node, ingress path, payload shape, and OMNIDASH_RUNTIME_EFFECTS_URL env note
- server/routes.ts uses COMMAND_TOPICS.delegateSkill constant; runtime targets
  stability-test :18085/v1/effects via OMNIDASH_RUNTIME_EFFECTS_URL env var
- DelegationRunContext exposes pendingCorrelationId + setPendingCorrelationId
  so a freshly-dispatched correlation_id propagates to all child panels before
  projection rows materialize
- DelegationRunHeader wires onCorrelationId callback to DelegationTriggerPanel
- DelegationEvidenceTabs auto-navigates to correlation-trace tab on dispatch
  and displays a pending indicator with the correlation_id
- DelegationCorrelationTracePanel falls back to pendingCorrelationId when no
  run is selected and shows appropriate pending vs awaiting-events messages
- Test: setPendingCorrelationId updates context value (DelegationRunContext.test)

* fix(OMN-12243): add 'delegation' short-topic alias to postgres + sqlite projection readers (#152)

GET /projection/delegation was returning rows:[] because the query() switch
had no 'delegation' case — only the full versioned topic name. Adds a
fall-through alias in both PostgresProjectionReader and SqliteProjectionReader
so the short topic name routes to the same decisions.v1 query logic.

Test: new routes.test.ts case seeds a SQLite DB and asserts the short alias
returns the same rows as the full topic name.

* fix(OMN-12287): close fixture gaps and add file-mode banners to dashboard widgets (#153)

- Add fileMode prop to ComponentWrapper: renders a [File Mode] badge in the
  widget header when active, analogous to the [Live] badge, using Text component
  to comply with the no-typography-inline ESLint rule
- Wire fileMode={!isLiveDataSource(mode)} on all 13 widgets that use
  useDataSourceMode: TraceExplorer, LiveEventStream, ContextHeatmap,
  DelegationControlPlane, EventStream, ReadinessGate, DelegationMetrics2D,
  DelegationSavingsWidget, DelegationTokenUsageWidget,
  DelegationModelRoutingWidget, DelegationQualityGateWidget, AbCompareWidget
- Improve emptyHint messages on all updated widgets to include file-mode
  fixture directory paths so developers know exactly where to add test data
- 1124/1124 tests pass

* feat(OMN-12288): deep-link delegation runs to trace explorer (#154)

- Add traceFilter + setTraceFilter to UISlice so a correlation_id can be
  passed to TraceExplorer before navigating there (no URL routing required)
- DelegationRunTable adds a GitCommitHorizontal icon button per row; clicking
  it calls setTraceFilter(correlationId) + setActivePage('trace') and stops
  event propagation so the row-select handler is not also triggered
- TraceExplorerWidget initializes its query state from traceFilter on mount
  and immediately clears the store value so subsequent navigations start fresh
- Tests: DelegationRunTable.test.tsx (7 cases), TraceExplorerWidget deep-link
  test confirms filter pre-fill + store clear (1132 total tests pass)

* feat(T22): add DelegationCostComparisonWidget — per-run cost proof surface (#155)

- New widget DelegationCostComparisonWidget in delegation/ directory reads from
  TOPICS.delegationSavings (same source as DelegationSavingsWidget) and surfaces
  a per-session cost comparison table with columns: Session, Task, Model, Tokens,
  Latency, Local $, Cloud $, Saved, Provenance
- Provenance badges (measured/estimated) on each row per the § 1.3 spec — green
  for measured (llm_call_metrics), amber for estimated (routing-rule inferred)
- Summary KPI bar shows cumulative savings, local/cloud cost, session count,
  total tokens, measured vs estimated breakdown
- Sort controls: date (default), savings descending, tokens descending
- maxRows config cap with overflow notice; showProvenance=false hides source column
- Registered as 'delegation-cost-comparison' in generate-registry.ts and index.ts
- Integration tests updated for count 29 → 30
- 1132/1132 tests pass

* fix(OMN-12402): unify CommandPalette node-type badges and Dispatch button contrast (#156)

Issue 1: kindColor() mixed design tokens (var(--accent), var(--text-tertiary))
with hardcoded hex (#d97706, #7c3aed), so ORCHESTRATOR/COMPUTE badges rendered
as weak plain text while EFFECT/REDUCER read as vivid chips. Drive all four
badges from the design-system node-type role tokens (--<kind>{,-soft,-ink}),
which exist for both themes, so every badge is a consistent filled chip.

Issue 2: the Dispatch button used var(--accent) as its fill (which resolves to
--brand-soft, a pale hover tint) plus literal color:'white' text — invisible in
light mode. Use the brand fill + on-brand text token (--brand /
--primary-foreground) so the CTA stays legible in both themes.

Adds CommandPalette.test.tsx asserting both fixes. Visual proof:
docs/evidence/OMN-12402-{badges,dispatch-light}.png

* fix(OMN-12367): render Correlation Trace tab gracefully in file mode (#159)

In VITE_DATA_SOURCE=file mode the /api/delegation/* proxy is not registered
(it is wired only when VITE_PROJECTION_API_URL is set), so fetchCorrelationTrace
hit the Vite SPA fallback, received index.html, and surfaced a raw
'JSON.parse: unexpected character at line 1 column 1' error.

Detect file mode up front in fetchCorrelationTrace and read a per-correlation
fixture under fixtures/onex.snapshot.projection.delegation.correlation-trace.v1/
so the surface renders a representative event chain without postgres. In live
mode, parse defensively (res.json().catch) so any non-JSON body degrades to an
empty trace and the panel shows its graceful no-events message rather than a raw
parse error.

Adds the delegationCorrelationTrace topic, a representative 2-event fixture for
the file-mode decisions correlation (069e55b5...), and tests covering file-mode
fixture fetch and non-JSON hardening.

* fix(OMN-12399): register Context Effectiveness Heatmap widget in palette (#158)

The widget source (component, hook, fixtures, types, test) existed but was
registered in zero of the three Track A surfaces, so it never appeared in the
widget palette and was unreachable through the supported user flow.

- Add lazy import to componentImports in src/components/dashboard/index.ts
- Add context-effectiveness-heatmap manifest to MVP_COMPONENTS in
  scripts/generate-registry.ts (quality category, contextExperimentScores
  projection, OMN-11241 fixture fallback)
- Regenerate src/registry/component-registry.json (30 -> 31 components)
- Update integration.part2/part3 component-count + name-list assertions

Visual proof (OMN-7093): palette entry + widget rendered on dashboard in
file mode using the OMN-11241 research fixture.

* fix(OMN-12366): give Delegation Control Plane active tab a clear visual indicator (#157)

The 10-tab evidence strip distinguished active from inactive tabs only by
background (var(--panel-2) vs transparent); border, text color, and weight
were identical, so the strip did not read as tabs.

Active tab now uses the --brand accent token: brand border, a 2px brand
bottom-border underline, --brand-soft background, and brand-colored
semibold label text. All via existing design tokens (no hardcoded hex),
verified to read clearly in both light and dark themes.

Visual evidence (OMN-7093):
- docs/evidence/OMN-12366-tab-strip-dark.png
- docs/evidence/OMN-12366-tab-strip-light.png

* feat(OMN-12139): add Trace Explorer fixture for onex.snapshot.projection.traces.v1 (#161)

Wires a _trace_groups() factory into scripts/generate_fixtures.py and emits
five representative TraceGroup records under
fixtures/onex.snapshot.projection.traces.v1/, covering every TraceExplorer
UI state: multi-node completed, single-node completed, running, error, and a
longer multi-node trace. The widget reads each file as one TraceGroup via the
legacy useProjectionQuery readAll path; the file-source middleware
auto-generates index.json.

Shapes mirror the widget's TraceGroup interface exactly (no entity_id); each
record is keyed by its correlation_id so the fixture filename matches.

DoD: VITE_DATA_SOURCE=file dev server renders 5 traces (1 running, 1 error)
in the Trace Explorer in File Mode; Playwright screenshot under
docs/evidence/OMN-12139-trace-explorer-file-mode.png proves populated/running/
error states (OMN-7093). npm run build, lint, and full vitest suite (1172
tests) green.

* fix(OMN-12397): make Savings Proof Pack source line dynamic by data mode (#160)

The printable proof pack hardcoded "Source: OmniNode delegation_events
projection" even in file mode, so a PDF exported from fixture data was
misleading once removed from the parent Fixture banner. Derive the
"Source:" line and reproducibility sentence from useDataSourceMode():

  file     -> Local fixture file (fixtures/.../demo-delegation-snapshot.json)
  sqlite   -> Local SQLite projection
  http     -> OmniNode projection API via Express bridge
  postgres -> OmniNode delegation_events projection

Non-live modes (file/sqlite) state the report is from a local
development data source and is not a live projection.

Renamed fixture live-delegation-demo.json -> demo-delegation-snapshot.json;
the word 'live' inside fixtures/ was second-order misleading. The file is
discovered by directory glob (vite fixturesMiddleware readdirSync), so no
code references the old name.

dod_evidence: rendered_output screenshot
docs/evidence/OMN-12397-proof-pack-file-mode-source.png shows the
file-mode footer with the fixture source string and 15 fixture rows.

* fix(OMN-12392): repoint run-generate-fixtures.sh to local script and correct CLAUDE.md fixture claim (#162)

OMN-12392: `run-generate-fixtures.sh` was looking for `generate_fixtures.py` at
`$OMNIBASE_INFRA_PATH/scripts/generate_fixtures.py`, but that path does not exist in
omnibase_infra. The script lives in this repo at `scripts/generate_fixtures.py`.
Fix: set SCRIPT_PATH to `$REPO/scripts/generate_fixtures.py` while preserving
`--project "$OMNIBASE_INFRA_PATH"` for uv dependency resolution (the script imports
from omnibase_infra). `npm run generate:fixtures` now works without a non-standard
OMNIBASE_INFRA_PATH.

Also fix CLAUDE.md hard rule #6 which claimed "Fixtures and layouts are not committed"
— fixtures/ IS tracked via the OMN-10305 gitignore carve-out (!fixtures/, !fixtures/**).
Correct the claim and add the carve-out explanation.

OMN-12393: add a lineage classification table to CLAUDE.md documenting that
omnidash/ (dev) is CANONICAL_ACTIVE, omnidash-v2/ is SUPERSEDED, and
omnidash-v2-new/ is a TEMP_STAGING clone. Cite verification evidence.

* test(OMN-10947): pin dashboard e2e fixture proof (#163)

* test(OMN-10947): scope dashboard e2e locators (#164)

* fix(OMN-12394): add .env to omnidash .gitignore to prevent credential leak (#165)

The .env file holds OMNIDASH_ANALYTICS_DB_URL with a live Postgres password.
The existing rule only covered .env.local* — .env itself was unprotected.

Adds an explicit .env entry so git never tracks the credential file.

* fix: move omnidash event bus config to contract overlay (#166)

* chore(OMN-12459): add universal managed gitignore block to omnidash (#168)

Appends the onex-managed universal block to .gitignore, adding
.env.*, !.env.example, *.log, .idea/, .vscode/, test-results/, and
playwright-report/ coverage. .env and .DS_Store were already present
individually; the block makes the policy explicit and machine-readable.

No tracked files required untracking.

* ci(OMN-12477): add dev-only main-target-guard (forward-port from main) (#167)

Forward-ports the main-target-guard workflow to dev (the file existed on
main but was absent on dev — main<->dev drift). Adds the dev-only variant
WITHOUT the hotfix/* bypass: main accepts only dev promotion PRs carrying
promotion-receipt: OCC-N. The guard only triggers on base==main, so it is
inert on dev-targeting PRs and takes effect at the next dev->main promotion.

OMN-12477

* feat(OMN-12674): remove reviewdog workflows and configs (#170)

Removes the reviewdog caller workflow from omnidash. No reviewdog
required-status-check bindings existed on main protection, dev
protection, or any repo ruleset, so this is the only active reviewdog
surface in the repo.

dod_evidence: OMN-12674

* fix(OMN-12739): tighten delegation dashboard formatting (#171)

* fix(OMN-12740): add browser-safe dashboard id fallback (#172)

* ci: preserve hotfix main-target allowance for release

* chore(release)(OMN-12245): bump omnidash to v1.1.1

* release(OMN-12765): promote dash dev lane to main (#185)

* chore(release)(OMN-12765): bump omnidash to v1.1.2 (#187)

* chore(release)(OMN-12765): bump omnidash to v1.1.2

* chore(release)(OMN-12765): refresh hotfix release gates

* chore(release)(OMN-12765): promote dev integration to main (#190)

* feat(OMN-11979): redesign delegation dashboard — dense metrics, narrative labels, stale .js cleanup (#119)

- Replace donut chart with terminal-style KPI tiles + stacked model bar + task breakdown
- Rename Quality Gate → Escalation Detail, humanize failure categories
- Fix postgres-projection-reader: correct column names, JSONB string coercion
- Add /projection Vite proxy for Express bridge
- Remove 8 stale compiled .js files that shadowed .tsx source via Vite resolution
- "Quality Gate" → "Local Success", "pass/fail" → "local/escalated" throughout

* feat(OMN-12069): delegation correlation trace view

Adds the delegation correlation trace view, trigger panel, and dashboard assertion updates needed for CI.

* feat(OMN-12133): add projection query endpoints for log entries and traces (#122)

Adds GET /api/projections/log-entries and GET /api/projections/traces
to the Express server, backed by new queryLogEntries and queryTraces
methods on PostgresProjectionReader. Adds traceExplorer topic constant
to shared/types/topics.ts. All query params are validated; limits are
capped (1000 for entries, 500 for traces). is_running = true when
last_event_at < 60s ago. 17 new unit tests + 2 route-level tests.

* feat(OMN-12089): B6 delegation savings proof pack — printable audit artifact (#123)

Adds DelegationSavingsProofPackPanel as a new 'Savings Proof Pack' tab in
the delegation control plane evidence tabs. Renders an exportable audit
report from live projection data: correlation IDs for every run, routing
decisions (model, rule, confidence), quality gate results (pass/fail +
detail), pricing manifest version, per-run savings, cumulative savings
summary, and a pass-rate totals row.

Print/Save as PDF is handled via window.print() with an injected @media
print stylesheet that isolates the printable region to the proof pack table.
No server-side PDF generation or new backend endpoints required — all data
sourced from DelegationEvidenceSnapshot already in context.

7 tests covering empty state, header rendering, run table, quality gate
labels, routing display, totals row, and print button.

* feat(OMN-12085): add swarm-runs projection topic to PostgresProjectionReader (#124)

Adds onex.snapshot.projection.swarm-runs.v1 case to the Postgres projection
reader. Queries omnidash_analytics.swarm_runs and returns all T4 display
fields: runId, status, subtask/succeeded/failed counts, modelsUsed,
totalCostUsd, cloudEquivalentCostUsd, savingsUsd, parallelismSpeedupRatio,
all latency fields, and createdAt. The swarm_runs table already exists on
stability-test (15436) with 11 rows.

* feat(OMN-12135): add TraceExplorerWidget — two-panel event bus trace explorer (#126)

Adds TraceExplorerWidget to omnidash for event bus observability Phase 2.
The widget consumes onex.snapshot.projection.traces.v1 to render a
left-panel trace list (with running/error indicators) and right-panel
chronological timeline of log entries for the selected trace.

- TraceExplorerWidget.tsx: two-panel layout, trace cards, timeline, search
- TraceExplorerWidget.stories.tsx: Empty, Populated, WithRunningTrace, ErrorTraces, Loading
- TraceExplorerWidget.test.tsx: 5 unit tests (loading, empty, render, filter, select prompt)
- storybook/fixtures/trace-explorer.ts: buildTraceGroups fixture factory
- generate-registry.ts: trace-explorer manifest with projectionSchema + displayContract
- index.ts: lazy import wired under trace-explorer/TraceExplorerWidget
- integration.part2/3: component count bumped 28→29 with trace-explorer

Closes OMN-12135. References OMN-12136, OMN-12137, OMN-12138, OMN-12139.

* feat(OMN-12082): add Context Effectiveness Heatmap widget (B4) (#127)

Implements the context factor effectiveness visualization showing which
context segments help/hurt which models across A3 ablation experiments:

- Segment × model matrix with color-coded cells:
  green (≥75% pass rate), amber (26–74%), red (≤25%)
- Clickable cells open an examples panel showing specific token impact,
  pass/fail counts, and qualitative findings per segment×model combination
- Fixture mode: when no live data exists, shows OMN-11241 research results
  (golden_chain green across all models, claude_md/architecture_patterns
  red for local models, exemplar green for exact-interface tasks,
  local_failures neutral)
- Fixture badge clearly marks when live projection is absent
- Reads from context_experiment_scores table via new
  onex.snapshot.projection.context.experiment-scores.v1 topic
- Adds postgres reader case for context_experiment_scores SELECT
- 10 tests passing (empty state, matrix render, cell selection, fixture
  badge, toggle deselect, footer count)

* feat(OMN-12145): add Express dispatch bridge and Kafka command topic (#128)

- POST /api/dispatch validates body, builds envelope, publishes to
  onex.cmd.omnidash.dispatch-request.v1 via kafkajs producer
- Returns 400 on missing fields, 503 when broker unavailable
- Kafka producer connects on server start, graceful disconnect on SIGTERM/SIGINT
- COMMAND_TOPICS registry in shared/types/command-topics.ts (separate from
  TOPICS to preserve golden-chain onex.snapshot.projection.* invariant)
- 9 unit tests with mocked producer covering validation, 503, and happy path

Closes OMN-12145, OMN-12149

* feat(OMN-12147): add command dispatch UI — trace action buttons + Cmd+K palette (#129)

- New `command-dispatch/` directory with:
  - `useDispatch.ts` hook: POST /api/dispatch, availability probe, error/loading state
  - `DispatchButton.tsx`: icon button (RotateCw/Square) with loading/success/error states
  - `CommandPalette.tsx`: Cmd+K/Ctrl+K overlay with node search, JSON payload editor, dispatch result; includes `useCommandPalette` hook for global keyboard listener
  - `CommandPalette.stories.tsx`: Open, WithResults, Dispatching, Error story exports
- `TraceExplorerWidget.tsx`: adds Stop button on running traces, Re-run button on completed traces
- `App.tsx`: mounts `CommandPalette` at app root via `useCommandPalette` hook

Closes OMN-12147, relates OMN-12148

* fix(OMN-12149): align dispatch command topic to omnimarket namespace (#131)

The node_dispatch_request_handler subscribes to
onex.cmd.omnimarket.dispatch-request.v1 (topic naming lint requires
consumer service namespace). Aligns the Express bridge publisher topic.

* chore(OMN-12171): delete orphaned .js files and add CI enforcement gate (#132)

* chore(OMN-12171): delete orphaned .js files and add CI enforcement gate

Confirms 298 .js artifacts in src/ and shared/ are stale (all have .ts/.tsx
counterparts; tsconfig emits nothing to src/; build output goes to dist/).
Deletes them from working tree.

Adds scripts/check-no-orphaned-js.sh — fails if any .js file in src/ or shared/
shadows a .ts/.tsx source — and wires it as a CI gate in ci.yml.

Also fixes broken @storybook/test import in CommandPalette.stories.tsx
(package moved to storybook/test in v10), restoring tsc --noEmit clean pass.

* fix(OMN-12171): include command topic in server typecheck

* refactor(OMN-12187): Consolidate DRY violations in omnidash

* refactor(OMN-12187): Consolidate DRY violations in omnidash

Extract shared pure-data helpers (timestampValue, mergeDelegationSessions,
buildCostSavingsOverviewResult) into server/projection-reader-shared.ts.
Both sqlite-projection-reader.ts and postgres-projection-reader.ts delegate
to the shared module, eliminating ~120 lines of duplicated logic.

Add 13 unit tests for the shared module covering edge cases in timestamp
parsing, session merge semantics, and cost overview aggregation.

The integration test files (part2/part3/part4) are distinct suites with
different test content — they are not duplicates and are left intact.

* fix(OMN-12187): remove unused cost overview input

* feat(OMN-12072): Swarm Control Plane widget — decomposition tree, wave view, live counters, savings bar (#125)

* feat(OMN-12072): add Swarm Control Plane widget (B2-T1 through B2-T5)

Implements the full swarm progression UI against the swarm_runs projection:
- B2-T1: SwarmDecompositionTree — parent→subtask tree with aggregate-derived
  per-subtask statuses and model hints; graceful empty state when subtask_count=0
- B2-T2: SwarmSubtaskCard — colored status badge, 3×3 metrics grid, models-used
  pill tags, registry hash footer
- B2-T3: SwarmWaveVisualization — heuristic 60/40 wave split with dependency
  connector; Wave 1 dimmed until run complete; empty state when subtask_count=0
- B2-T4: SwarmLiveCounters — 6 aggregate cards (total runs, subtasks, avg speedup,
  wall time, cost, savings) derived from swarm_runs projection
- B2-T5: SwarmSavingsBar — stacked local+savings bar vs cloud equivalent baseline,
  per-run mini bars (up to 10), zero-cost graceful empty state
- SwarmRunTable: 8-column run list with row selection, speedup/savings coloring
- SwarmControlPlane: tabbed widget (Overview, Decomposition, Run Detail, Wave View,
  Savings) wired to swarm.runs.v1 projection topic via useProjectionQuery
- Adds TOPICS.swarmRuns, swarm-runs.v1 postgres case, GET /api/swarm-runs route
- 10 tests passing

* feat(OMN-12072): add SwarmSubtaskDetailPanel — per-subtask model/tokens/cost/latency table (B5-T3 gap)

Adds SwarmSubtaskDetailPanel showing per-subtask rows with:
- subtask_id (derived: run_id prefix + padded index)
- description (Subtask 01, 02, ...)
- model assigned (cycled from run.models_used)
- status (succeeded/failed/skipped/pending, derived from aggregate counts)
- tokens_input / tokens_output (null — requires node_swarm_subtask_state_reducer)
- cost_usd (evenly distributed from total_cost_usd)
- latency_ms (estimated from dispatch_wall_latency_ms / parallelism)

Panel is clearly marked as aggregate-derived with a fixture notice.
Shows "No subtask data" with reducer name when subtask_count=0.

Wired as new "Subtask Detail" tab in SwarmControlPlane alongside existing
"Run Detail" aggregate card. Uses List icon from lucide-react.

Schema confirmed: swarm_runs has no jsonb subtask column and no separate
swarm_subtasks table exists yet in stability-test. All per-subtask data
requires node_swarm_subtask_state_reducer projection.

15 new tests + 10 existing passing (25 total).

* refactor(OMN-11578/OMN-11596): reduce cyclomatic complexity in ThreeCanvas and parseDashboardDefinition (#137)

OMN-11578: Extract advanceFocusProgress, applyFocusedOpacities,
applyIdleOpacities, and projectAxisLabels from the ThreeCanvas
useEffect god-function (CCN=83). The tick loop is now a thin
orchestrator calling four named helpers via a TickRefs context struct.

OMN-11596: Extract validateTopLevelFields and validateLayoutItem from
parseDashboardDefinition (CCN=31). The parse function is reduced to
an 8-line orchestrator; all conditional branches live in the helpers.

* fix(OMN-12245): silence unused swarm route request (#139)

* feat(OMN-11658): wire SEA demo dashboard projections (#140)

* feat(OMN-11658): wire SEA demo dashboard projections

* fix(OMN-11658): make trace rows toggle independently

* fix(OMN-11658): polish SEA dashboard rendering

* feat(OMN-11658): expose delegation trigger in SEA control plane (#141)

* fix(OMN-11658): improve SEA prompt feedback (#142)

* fix(OMN-11658): let dashboard fill wide viewports (#143)

* fix(OMN-11658): balance dashboard gutters (#144)

* fix(OMN-11658): keep SEA stream newest first (#145)

* fix(OMN-11658): refresh SEA traces after prompt submit (#146)

* fix(OMN-11658): center dashboard shell (#147)

* fix(OMN-11658): extend dashboard body to bottom (#148)

* fix(OMN-11658): repair empty SEA dashboard layout (#149)

* chore(DELEG-002): annotate pricing fallbacks, align fixture model names with bifrost config (#150)

* feat(OMN-12070): wire delegation trigger through omnimarket runtime (#151)

- Add COMMAND_TOPICS.delegateSkill ('onex.cmd.omnimarket.delegate-skill.v1')
  to shared/types/command-topics.ts so the topic is contract-declared rather
  than hardcoded in server/routes.ts
- Declare the delegation trigger command topic in contract.yaml with handler
  node, ingress path, payload shape, and OMNIDASH_RUNTIME_EFFECTS_URL env note
- server/routes.ts uses COMMAND_TOPICS.delegateSkill constant; runtime targets
  stability-test :18085/v1/effects via OMNIDASH_RUNTIME_EFFECTS_URL env var
- DelegationRunContext exposes pendingCorrelationId + setPendingCorrelationId
  so a freshly-dispatched correlation_id propagates to all child panels before
  projection rows materialize
- DelegationRunHeader wires onCorrelationId callback to DelegationTriggerPanel
- DelegationEvidenceTabs auto-navigates to correlation-trace tab on dispatch
  and displays a pending indicator with the correlation_id
- DelegationCorrelationTracePanel falls back to pendingCorrelationId when no
  run is selected and shows appropriate pending vs awaiting-events messages
- Test: setPendingCorrelationId updates context value (DelegationRunContext.test)

* fix(OMN-12243): add 'delegation' short-topic alias to postgres + sqlite projection readers (#152)

GET /projection/delegation was returning rows:[] because the query() switch
had no 'delegation' case — only the full versioned topic name. Adds a
fall-through alias in both PostgresProjectionReader and SqliteProjectionReader
so the short topic name routes to the same decisions.v1 query logic.

Test: new routes.test.ts case seeds a SQLite DB and asserts the short alias
returns the same rows as the full topic name.

* fix(OMN-12287): close fixture gaps and add file-mode banners to dashboard widgets (#153)

- Add fileMode prop to ComponentWrapper: renders a [File Mode] badge in the
  widget header when active, analogous to the [Live] badge, using Text component
  to comply with the no-typography-inline ESLint rule
- Wire fileMode={!isLiveDataSource(mode)} on all 13 widgets that use
  useDataSourceMode: TraceExplorer, LiveEventStream, ContextHeatmap,
  DelegationControlPlane, EventStream, ReadinessGate, DelegationMetrics2D,
  DelegationSavingsWidget, DelegationTokenUsageWidget,
  DelegationModelRoutingWidget, DelegationQualityGateWidget, AbCompareWidget
- Improve emptyHint messages on all updated widgets to include file-mode
  fixture directory paths so developers know exactly where to add test data
- 1124/1124 tests pass

* feat(OMN-12288): deep-link delegation runs to trace explorer (#154)

- Add traceFilter + setTraceFilter to UISlice so a correlation_id can be
  passed to TraceExplorer before navigating there (no URL routing required)
- DelegationRunTable adds a GitCommitHorizontal icon button per row; clicking
  it calls setTraceFilter(correlationId) + setActivePage('trace') and stops
  event propagation so the row-select handler is not also triggered
- TraceExplorerWidget initializes its query state from traceFilter on mount
  and immediately clears the store value so subsequent navigations start fresh
- Tests: DelegationRunTable.test.tsx (7 cases), TraceExplorerWidget deep-link
  test confirms filter pre-fill + store clear (1132 total tests pass)

* feat(T22): add DelegationCostComparisonWidget — per-run cost proof surface (#155)

- New widget DelegationCostComparisonWidget in delegation/ directory reads from
  TOPICS.delegationSavings (same source as DelegationSavingsWidget) and surfaces
  a per-session cost comparison table with columns: Session, Task, Model, Tokens,
  Latency, Local $, Cloud $, Saved, Provenance
- Provenance badges (measured/estimated) on each row per the § 1.3 spec — green
  for measured (llm_call_metrics), amber for estimated (routing-rule inferred)
- Summary KPI bar shows cumulative savings, local/cloud cost, session count,
  total tokens, measured vs estimated breakdown
- Sort controls: date (default), savings descending, tokens descending
- maxRows config cap with overflow notice; showProvenance=false hides source column
- Registered as 'delegation-cost-comparison' in generate-registry.ts and index.ts
- Integration tests updated for count 29 → 30
- 1132/1132 tests pass

* fix(OMN-12402): unify CommandPalette node-type badges and Dispatch button contrast (#156)

Issue 1: kindColor() mixed design tokens (var(--accent), var(--text-tertiary))
with hardcoded hex (#d97706, #7c3aed), so ORCHESTRATOR/COMPUTE badges rendered
as weak plain text while EFFECT/REDUCER read as vivid chips. Drive all four
badges from the design-system node-type role tokens (--<kind>{,-soft,-ink}),
which exist for both themes, so every badge is a consistent filled chip.

Issue 2: the Dispatch button used var(--accent) as its fill (which resolves to
--brand-soft, a pale hover tint) plus literal color:'white' text — invisible in
light mode. Use the brand fill + on-brand text token (--brand /
--primary-foreground) so the CTA stays legible in both themes.

Adds CommandPalette.test.tsx asserting both fixes. Visual proof:
docs/evidence/OMN-12402-{badges,dispatch-light}.png

* fix(OMN-12367): render Correlation Trace tab gracefully in file mode (#159)

In VITE_DATA_SOURCE=file mode the /api/delegation/* proxy is not registered
(it is wired only when VITE_PROJECTION_API_URL is set), so fetchCorrelationTrace
hit the Vite SPA fallback, received index.html, and surfaced a raw
'JSON.parse: unexpected character at line 1 column 1' error.

Detect file mode up front in fetchCorrelationTrace and read a per-correlation
fixture under fixtures/onex.snapshot.projection.delegation.correlation-trace.v1/
so the surface renders a representative event chain without postgres. In live
mode, parse defensively (res.json().catch) so any non-JSON body degrades to an
empty trace and the panel shows its graceful no-events message rather than a raw
parse error.

Adds the delegationCorrelationTrace topic, a representative 2-event fixture for
the file-mode decisions correlation (069e55b5...), and tests covering file-mode
fixture fetch and non-JSON hardening.

* fix(OMN-12399): register Context Effectiveness Heatmap widget in palette (#158)

The widget source (component, hook, fixtures, types, test) existed but was
registered in zero of the three Track A surfaces, so it never appeared in the
widget palette and was unreachable through the supported user flow.

- Add lazy import to componentImports in src/components/dashboard/index.ts
- Add context-effectiveness-heatmap manifest to MVP_COMPONENTS in
  scripts/generate-registry.ts (quality category, contextExperimentScores
  projection, OMN-11241 fixture fallback)
- Regenerate src/registry/component-registry.json (30 -> 31 components)
- Update integration.part2/part3 component-count + name-list assertions

Visual proof (OMN-7093): palette entry + widget rendered on dashboard in
file mode using the OMN-11241 research fixture.

* fix(OMN-12366): give Delegation Control Plane active tab a clear visual indicator (#157)

The 10-tab evidence strip distinguished active from inactive tabs only by
background (var(--panel-2) vs transparent); border, text color, and weight
were identical, so the strip did not read as tabs.

Active tab now uses the --brand accent token: brand border, a 2px brand
bottom-border underline, --brand-soft background, and brand-colored
semibold label text. All via existing design tokens (no hardcoded hex),
verified to read clearly in both light and dark themes.

Visual evidence (OMN-7093):
- docs/evidence/OMN-12366-tab-strip-dark.png
- docs/evidence/OMN-12366-tab-strip-light.png

* feat(OMN-12139): add Trace Explorer fixture for onex.snapshot.projection.traces.v1 (#161)

Wires a _trace_groups() factory into scripts/generate_fixtures.py and emits
five representative TraceGroup records under
fixtures/onex.snapshot.projection.traces.v1/, covering every TraceExplorer
UI state: multi-node completed, single-node completed, running, error, and a
longer multi-node trace. The widget reads each file as one TraceGroup via the
legacy useProjectionQuery readAll path; the file-source middleware
auto-generates index.json.

Shapes mirror the widget's TraceGroup interface exactly (no entity_id); each
record is keyed by its correlation_id so the fixture filename matches.

DoD: VITE_DATA_SOURCE=file dev server renders 5 traces (1 running, 1 error)
in the Trace Explorer in File Mode; Playwright screenshot under
docs/evidence/OMN-12139-trace-explorer-file-mode.png proves populated/running/
error states (OMN-7093). npm run build, lint, and full vitest suite (1172
tests) green.

* fix(OMN-12397): make Savings Proof Pack source line dynamic by data mode (#160)

The printable proof pack hardcoded "Source: OmniNode delegation_events
projection" even in file mode, so a PDF exported from fixture data was
misleading once removed from the parent Fixture banner. Derive the
"Source:" line and reproducibility sentence from useDataSourceMode():

  file     -> Local fixture file (fixtures/.../demo-delegation-snapshot.json)
  sqlite   -> Local SQLite projection
  http     -> OmniNode projection API via Express bridge
  postgres -> OmniNode delegation_events projection

Non-live modes (file/sqlite) state the report is from a local
development data source and is not a live projection.

Renamed fixture live-delegation-demo.json -> demo-delegation-snapshot.json;
the word 'live' inside fixtures/ was second-order misleading. The file is
discovered by directory glob (vite fixturesMiddleware readdirSync), so no
code references the old name.

dod_evidence: rendered_output screenshot
docs/evidence/OMN-12397-proof-pack-file-mode-source.png shows the
file-mode footer with the fixture source string and 15 fixture rows.

* fix(OMN-12392): repoint run-generate-fixtures.sh to local script and correct CLAUDE.md fixture claim (#162)

OMN-12392: `run-generate-fixtures.sh` was looking for `generate_fixtures.py` at
`$OMNIBASE_INFRA_PATH/scripts/generate_fixtures.py`, but that path does not exist in
omnibase_infra. The script lives in this repo at `scripts/generate_fixtures.py`.
Fix: set SCRIPT_PATH to `$REPO/scripts/generate_fixtures.py` while preserving
`--project "$OMNIBASE_INFRA_PATH"` for uv dependency resolution (the script imports
from omnibase_infra). `npm run generate:fixtures` now works without a non-standard
OMNIBASE_INFRA_PATH.

Also fix CLAUDE.md hard rule #6 which claimed "Fixtures and layouts are not committed"
— fixtures/ IS tracked via the OMN-10305 gitignore carve-out (!fixtures/, !fixtures/**).
Correct the claim and add the carve-out explanation.

OMN-12393: add a lineage classification table to CLAUDE.md documenting that
omnidash/ (dev) is CANONICAL_ACTIVE, omnidash-v2/ is SUPERSEDED, and
omnidash-v2-new/ is a TEMP_STAGING clone. Cite verification evidence.

* test(OMN-10947): pin dashboard e2e fixture proof (#163)

* test(OMN-10947): scope dashboard e2e locators (#164)

* fix(OMN-12394): add .env to omnidash .gitignore to prevent credential leak (#165)

The .env file holds OMNIDASH_ANALYTICS_DB_URL with a live Postgres password.
The existing rule only covered .env.local* — .env itself was unprotected.

Adds an explicit .env entry so git never tracks the credential file.

* fix: move omnidash event bus config to contract overlay (#166)

* chore(OMN-12459): add universal managed gitignore block to omnidash (#168)

Appends the onex-managed universal block to .gitignore, adding
.env.*, !.env.example, *.log, .idea/, .vscode/, test-results/, and
playwright-report/ coverage. .env and .DS_Store were already present
individually; the block makes the policy explicit and machine-readable.

No tracked files required untracking.

* ci(OMN-12477): add dev-only main-target-guard (forward-port from main) (#167)

Forward-ports the main-target-guard workflow to dev (the file existed on
main but was absent on dev — main<->dev drift). Adds the dev-only variant
WITHOUT the hotfix/* bypass: main accepts only dev promotion PRs carrying
promotion-receipt: OCC-N. The guard only triggers on base==main, so it is
inert on dev-targeting PRs and takes effect at the next dev->main promotion.

OMN-12477

* feat(OMN-12674): remove reviewdog workflows and configs (#170)

Removes the reviewdog caller workflow from omnidash. No reviewdog
required-status-check bindings existed on main protection, dev
protection, or any repo ruleset, so this is the only active reviewdog
surface in the repo.

dod_evidence: OMN-12674

* fix(OMN-12739): tighten delegation dashboard formatting (#171)

* fix(OMN-12740): add browser-safe dashboard id fallback (#172)

* feat(OMN-12745): add DelegationModelOutputWidget rendering inference generated_text (#177)

Implements the demo-blocking deliverable for OMN-12745: DelegationModelOutputWidget
renders ModelLlmInferenceResponse.generated_text sourced from bus topic
onex.evt.omnibase-infra.inference-response.v1 in fixture mode (offline, no .201
dependency). Live surfacing is gated on the OMN-12746 demo redeploy.

Changes:
- src/components/dashboard/delegation/DelegationModelOutputWidget.tsx — new widget
  rendering latest generated_text, model identity, task type, token count, latency,
  and a recent response history list
- src/components/dashboard/delegation/DelegationModelOutputWidget.test.tsx — 8 unit
  tests (TDD-first: loading, empty, generated_text, model identity, topic label,
  upstream notice, provisioned, token count)
- src/components/dashboard/delegation/DelegationModelOutputWidget.stories.tsx — 6
  stories: Empty, Loading, Populated, FixtureMode, Provisioned, SingleResponse
- shared/types/topics.ts — adds inferenceResponseText topic key
- src/storybook/fixtures/delegation-routing.ts — adds buildInferenceResponseProjection
- src/storybook/fixtures/index.ts — exports new fixture builder
- scripts/generate-registry.ts — adds delegation-model-output MVP manifest entry
- src/registry/component-registry.json — regenerated (32 components)
- src/components/dashboard/index.ts — lazy-import registration
- src/templates/sea-demo.ts — delegation-model-output added to SEA Demo template
- src/templates/templates.test.ts — updated SEA Demo widget order assertions
- src/integration.part2.test.tsx — component count 31 to 32 and name list
- src/integration.part3.test.tsx — component count 31 to 32
- src/storybook-coverage-compliance.test.ts — adds story file to compliance list
- fixtures/onex.snapshot.projection.delegation.inference-response-text.v1/snapshot.json
  — fixture data for file mode (offline demo)

Evidence-Source OCC SHA: c872f8edbabda4306e3dd290dbb8995031da3d7c

* feat(OMN-12748): render delegated prompt/response from contract-declared projection (#178)

The Correlation Trace panel already renders prompt_text/response_text per run,
but fetchCorrelationTrace called a bespoke /api/delegation/correlation-trace/<id>
REST route that the deployed contract-driven projection API does not implement
(404), so the panel always showed 'No events found' and the dashboard could not
surface the actual delegated work.

Read the per-correlation trace from the contract-declared projection instead:
/projection/onex.snapshot.projection.delegation.correlation-trace.v1?correlation_id=<id>
(a read path the projection API already supports). Selecting a run now renders
its prompt + response. File mode still reads the per-correlation fixture.

Pairs with omnimarket OMN-12748, which exposes that projection over
delegation_events. Canonical: client renders a contract-declared projection; no
bespoke REST route.

* chore(release)(OMN-12245): backmerge omnidash v1.1.1 to dev (#176)

* fix(OMN-12756): publish delegation trigger through command bus (#179)

* feat(OMN-12748): render delegated prompt/response from contract-declared projection

The Correlation Trace panel already renders prompt_text/response_text per run,
but fetchCorrelationTrace called a bespoke /api/delegation/correlation-trace/<id>
REST route that the deployed contract-driven projection API does not implement
(404), so the panel always showed 'No events found' and the dashboard could not
surface the actual delegated work.

Read the per-correlation trace from the contract-declared projection instead:
/projection/onex.snapshot.projection.delegation.correlation-trace.v1?correlation_id=<id>
(a read path the projection API already supports). Selecting a run now renders
its prompt + response. File mode still reads the per-correlation fixture.

Pairs with omnimarket OMN-12748, which exposes that projection over
delegation_events. Canonical: client renders a contract-declared projection; no
bespoke REST route.

* fix(OMN-12756): route delegation API to dashboard server

* chore(OMN-12765): backmerge omnidash main to dev (#181)

* feat(OMN-11614, OMN-11617): deduplicate projection readers and integration test utilities (#120)

* feat(OMN-11979): redesign delegation dashboard — dense metrics, narrative labels, stale .js cleanup (#119)

- Replace donut chart with terminal-style KPI tiles + stacked model bar + task breakdown
- Rename Quality Gate → Escalation Detail, humanize failure categories
- Fix postgres-projection-reader: correct column names, JSONB string coercion
- Add /projection Vite proxy for Express bridge
- Remove 8 stale compiled .js files that shadowed .tsx source via Vite resolution
- "Quality Gate" → "Local Success", "pass/fail" → "local/escalated" throughout

* feat(OMN-11614, OMN-11617): deduplicate projection readers and integration test utilities

OMN-11614: Extract shared session-merge and cost-savings-overview aggregation
logic from postgres-projection-reader.ts and sqlite-projection-reader.ts into
server/projection-utils.ts. Removes ~160 lines o…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant