Skip to content

feat(web/console): show run provenance — input message + platform icons#1881

Merged
Wirasm merged 2 commits into
devfrom
feat/console-run-provenance
Jun 5, 2026
Merged

feat(web/console): show run provenance — input message + platform icons#1881
Wirasm merged 2 commits into
devfrom
feat/console-run-provenance

Conversation

@Wirasm

@Wirasm Wirasm commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Problem: Console run cards/rows/detail showed status, workflow, cost, elapsed — but never what input started a run. The prompt was already parsed into the Run primitive (toRun) yet rendered nowhere; the origin badge was text-only. Two CLI runs of the same workflow looked identical.
  • Why it matters: Directly addresses a stated console-vs-old-dashboard parity gap ("what did I enter into the dialog… cli/chat") — part of making /console the default UI.
  • What changed: Render user_message on ActiveRunCard, RecentRunRow, RunStartedLine, RunDetailHeader; add per-platform Lucide icons to OriginBadge; add the first test for the run.ts primitive.
  • What did NOT change (scope boundary): No server/DB/schema/api.generated.d.ts change. No new renderers beyond text rows. Old UI untouched. The "from chat →" link is deferred (see Deviation).

UX Journey

Before

RunDetailHeader:  project / a1b2c3d4   ● running   maintainer-standup  [CLI]   $1.07  11m  ⧉
RecentRunRow:     ▸ completed  maintainer-standup  proj  a1b2c3d4  11m  $1.07  [CLI]
(no prompt shown anywhere; origin is text-only)

After

RunDetailHeader:  project / a1b2c3d4   ● running   maintainer-standup  [⌨ CLI]  $1.07  11m  ⧉
                  input  summarise this week's merged PRs and open issues          ◄── NEW sub-row
ActiveRunCard:    input  summarise this week's merged PRs…  (full text on hover)    ◄── NEW grid row
RecentRunRow:     ▸ completed  maintainer-standup  ⟨summarise this week's…⟩  …  [⌨ CLI]  (h-9 kept; title tooltip)
RunStartedLine:   ▶ Workflow maintainer-standup started  10:31
                  ▏ summarise this week's merged PRs and open issues                ◄── NEW blockquote

Architecture Diagram

toRun() (run.ts) — already parses user_message + origin — now exported normalizeOrigin for tests
  → Run.userMessage / Run.origin
      → OriginBadge        (+ Lucide PLATFORM_ICONS)
      → ActiveRunCard      (+ `input` grid row)
      → RecentRunRow       (+ inline truncated message, h-9 preserved)
      → RunStartedLine     (+ blockquote line)
      → RunDetailHeader    (+ full-width `input` sub-row)

Connection inventory:

From To Status Notes
OriginBadge lucide-react new one icon per platform (existing dep)
Run.userMessage 4 components new rendering of an already-populated field
primitives/run.ts run.test.ts new first primitive test; normalizeOrigin exported

Label Snapshot

  • Risk: risk: low
  • Size: size: S
  • Scope: web
  • Module: web:console

Change Metadata

  • Change type: feature
  • Primary scope: web

Linked Issue

Validation Evidence (required)

bun --filter @archon/web type-check   # exit 0
bun run lint                          # exit 0 (--max-warnings 0)
bun run format:check                  # clean
bun run validate                      # exit 0 — all 7 CI checks incl. full test suite
# console tests: 29 pass / 0 fail (21 event + 8 new run normalizer)
  • Evidence: bun run validate is green end-to-end. run.test.ts covers normalizeOrigin (all 6 platforms + case-insensitive + null/undefined/unknown fallback), userMessage default/passthrough, origin derivation, conversationPlatformId passthrough, and pendingrunning.

Security Impact (required)

  • New permissions/capabilities? No
  • New external network calls? No
  • Secrets/tokens handling changed? No
  • File system access scope changed? No

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Database migration needed? No

Human Verification (required)

  • Verified scenarios: type-check/lint/format/validate green; 29 console tests pass. Folded the input row into ActiveRunCard's existing activity grid (no double grid); confirmed RecentRunRow stays h-9 with the message sharing the workflow line + title tooltip; RunDetailHeader sub-row uses w-full to wrap onto its own line in the flex-wrap header.
  • Edge cases: empty userMessage → no row/blockquote/tooltip anywhere; origin unknown → no icon, label ; long message → truncates with full text on hover.
  • Not verified: live browser screenshot (presentational; @archon/web has no jsdom/testing-library, consistent with fix(web/console): gate /console behind auth + correct run-event normalizer #1878's accepted constraint).

Side Effects / Blast Radius (required)

  • Affected subsystems: console run-display components + the run primitive (web only).
  • Potential unintended effects: none expected — the only logic change is toRun exporting normalizeOrigin; everything else is additive rendering of an existing field.
  • Guardrails: the console test segment runs in CI (added in fix(web/console): gate /console behind auth + correct run-event normalizer #1878); run.test.ts now covers the normalizer.

Rollback Plan (required)

  • Fast rollback: git revert <merge-commit> — 7 files, web-only.
  • Feature flags/toggles: none.
  • Observable failure symptoms: none (presentational); worst case an input row renders for an empty message (guarded against).

Risks and Mitigations

  • Risk: RecentRunRow message crowds the row on narrow widths.
    • Mitigation: workflow name capped at max-w-[55%], message min-w-0 truncate; row height fixed at h-9; full text via title.

Deviation from the plan (documented)

The plan's "from chat →" link is deferred, not built. Investigation found the console has no conversation-deep-link routeChatPage mounts at p/:projectId/chat and manages its active conversation internally, so the link has no valid destination. Per the plan's explicit "do not invent a route" constraint, the link and the workerPlatformId/parentPlatformId fields that only existed to feed it were dropped (they'd be unread data — the unused-field smell the #1878 review flagged). The link moves to a follow-up once the console chat route supports deep-linking to a specific conversation. This PR ships the unambiguous wins that fully address the stated need.

Summary by CodeRabbit

  • New Features

    • Origin badges now include icons for quicker identification.
    • Run cards, headers, recent rows, and lifecycle views show user input/provenance with truncation and full-text tooltips.
  • Bug Fixes

    • Improved row/layout behavior so provenance displays consistently alongside other run details.
  • Tests

    • Added tests for run origin normalization, provenance parsing, cost parsing, and approval parsing.

Console run cards/rows/detail showed status, workflow, cost, and elapsed but
never what input started a run — the prompt was parsed into the Run primitive
(toRun) yet rendered nowhere, and the origin badge was text-only. This surfaces it:

- OriginBadge: a Lucide icon per platform (web/cli/slack/telegram/discord/github;
  none for unknown), mirroring the old dashboard's PLATFORM_ICONS.
- ActiveRunCard: an `input` row folded into the activity grid (truncated, full
  text on hover) for running + paused cards.
- RecentRunRow: the message truncated inline beside the workflow name; the row
  stays h-9 (title tooltip for the full text).
- RunStartedLine: a blockquote input line under "Workflow X started".
- RunDetailHeader: a full-width `input` sub-row.
- run.test.ts: first test for the primitive — normalizeOrigin (all platforms +
  case-insensitive + unknown fallback), userMessage default, pending→running,
  conversationPlatformId passthrough. (normalizeOrigin is now exported.)

Deviation from plan (documented): the "from chat →" link is DEFERRED, not built.
The console has no conversation-deep-link route — ChatPage is mounted at
p/:projectId/chat and manages its active conversation internally, so a chat link
has no valid destination. Per the plan's "do not invent a route" constraint, the
link AND the workerPlatformId/parentPlatformId fields that only existed to feed it
were dropped (they'd be unread data). This ships the unambiguous wins that fully
address the stated need ("what did I enter… cli/chat"); the link moves to a
follow-up once console chat supports conversation deep-linking.
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7c677f10-8e19-4339-a986-976d2fb09996

📥 Commits

Reviewing files that changed from the base of the PR and between 2d3795c and 0ad090a.

📒 Files selected for processing (5)
  • packages/web/src/experiments/console/components/ActiveRunCard.tsx
  • packages/web/src/experiments/console/components/OriginBadge.tsx
  • packages/web/src/experiments/console/components/RecentRunRow.tsx
  • packages/web/src/experiments/console/components/RunDetailHeader.tsx
  • packages/web/src/experiments/console/primitives/run.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/web/src/experiments/console/components/RunDetailHeader.tsx

📝 Walkthrough

Walkthrough

This PR surfaces run provenance (userMessage) across several console components, adds per-origin icons to origin badges, and exports normalizeOrigin with expanded Bun tests for normalization and parsing.

Changes

Provenance Tracking in Console UI

Layer / File(s) Summary
Provenance Data Primitives & Tests
packages/web/src/experiments/console/primitives/run.ts, packages/web/src/experiments/console/primitives/run.test.ts
normalizeOrigin is exported and tested for platform-type mapping and fallback. toRun tests validate userMessage defaults/passthrough, origin derivation, status normalization, cost parsing, and approval parsing.
Origin Icon Styling in Badge
packages/web/src/experiments/console/components/OriginBadge.tsx
Add Lucide icon imports and an ORIGIN_ICON mapping; render the selected icon before the origin label and adjust badge layout (items-center, gap-1).
Provenance Display Across Console Components
packages/web/src/experiments/console/components/ActiveRunCard.tsx, packages/web/src/experiments/console/components/RecentRunRow.tsx, packages/web/src/experiments/console/components/RunDetailHeader.tsx, packages/web/src/experiments/console/components/RunLifecycle.tsx
Render run.userMessage when non-empty with truncation and title tooltips; ActiveRunCard uses showDetailGrid to show provenance + activity detail and consolidates emptiness checks with hasValue; RecentRunRow shares the workflow row with an optional truncated message; RunDetailHeader and RunLifecycle render provenance sub-rows/blocks.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • coleam00/Archon#1747: Earlier console UI changes touching ActiveRunCard, RecentRunRow, RunDetailHeader, and OriginBadge.

Poem

🐰 I nibble on the trailing trace,
A tiny note that shows its place.
With icons bright and tooltips true,
The run's small message sings anew. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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
Title check ✅ Passed The title clearly and concisely summarizes the main changes: rendering user input messages (provenance) and adding platform icons to the origin badge in the console UI.
Description check ✅ Passed The description comprehensively covers all required sections: problem statement, rationale, specific changes made, scope boundaries, before/after UX flows, architecture diagram with connection inventory, validation evidence, security impact, compatibility, human verification, side effects, rollback plan, and documented deviations.
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 feat/console-run-provenance

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@Wirasm

Wirasm commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator Author

PR Review Summary — multi-agent review

5 agents reviewed this PR (code, docs, tests, comments, simplify). I skipped silent-failure-hunter (no error handling / try-catch / new fallbacks — normalizeOrigin's unknown fallback pre-existed) and type-design-analyzer (no new or modified types — RunOrigin is unchanged; only an export keyword + a value-level Record were added). I verified the core safety claims against run.ts: userMessage is raw.user_message ?? '' (always a string, so !== '' is the correct total guard everywhere), and ORIGIN_ICON: Record<RunOrigin, …> is compile-time exhaustive over all 7 origins.

Critical Issues (0)

None. (CI was still running at review time — verify it's green before merge.)

Important Issues (3) — all quick, none hard-blocks (presentational PR)

# Agent Issue Location
I1 code-reviewer Layout regression for runs with no input message. The workflow name span changed from flex-1 to max-w-[55%] shrink-0. When userMessage === '' (Pi/Codex runs that don't report it, or historical rows) the name is still capped at 55%, leaving ~45% blank and truncating long workflow names that previously rendered full. Fix: make the span flex-1/min-w-0 when there's no message, max-w-[55%] shrink-0 only when a message is present. RecentRunRow.tsx (changed workflow <span>)
I2 comment-analyzer Comment says the input row is shown "always", but it's gated on run.userMessage !== '' — it does NOT render for an empty message. Misleading to a reader scanning before the code. Reword to "when present". ActiveRunCard.tsx (provenance/activity comment, ~L128)
I3 comment-analyzer Comment claims the icon map "mirrors the old dashboard's WorkflowRunCard PLATFORM_ICONS" — but that map has 5 entries (no discord) and falls back to <Globe>, whereas the new map has 7 (adds discord + unknown: null → no icon). "mirrors" implies parity; it's a superset with a different fallback. Say "extends", or drop the cross-reference. OriginBadge.tsx (ORIGIN_ICON comment)

Suggestions (5)

# Agent Suggestion Location
S1 code-simplifier Extract a local const hasValue = (v: string|null|undefined): v is string => v != null && v !== '' to replace the repeated !== null && !== undefined && !== '' chains on currentNode/lastTool (also adds type-narrowing). Keep it local to the component, not a shared util. ActiveRunCard.tsx (~L130-142)
S2 code-simplifier Optional: name the outer guard const showDetailGrid = run.userMessage !== '' || run.status === 'running' — matches the file's own elapsed/canOpen idiom. ActiveRunCard.tsx (~L128)
S3 code-simplifier Trivial consistency: RecentRunRow uses run.userMessage.length > 0 in one spot (URL param) but !== '' in the render guard — pick one. RecentRunRow.tsx:56
S4 pr-test-analyzer run.ts is now under test but several pre-existing branches are uncovered: readCost (incl. the > 0 guard that silently suppresses $0.00 — rating 6), approval-metadata parsing (the most intricate type-narrowing chain — rating 5), unknown-status → running fallback (one-liner — rating 5). Worth adding as the console graduates to default UI. run.test.ts
S5 comment-analyzer The "link back to the originating conversation is deferred…" comment is accurate today (no console conversation deep-link route exists) but is an untracked prose forward-reference. Add a // TODO(#NNNN) anchor so it's searchable and doesn't rot silently. RunDetailHeader.tsx (~L141)

Strengths

  • Type safety is airtight: userMessage always a string (so the !== '' guards can't leak null/undefined); ORIGIN_ICON is Record<RunOrigin, …> so a future origin can't silently render no icon — the compiler forces a decision.
  • Experiment isolation boundary respected — lucide-react is an existing dep, not lint-restricted; no production-module import.
  • normalizeOrigin export is pure and test-only; zero runtime effect.
  • RunStartedLine's fragment return is correct (not used in a list, no key needed); lucide icons auto-aria-hidden, badge keeps its title → a11y preserved; RecentRunRow keeps h-9 (no flex-wrap on the inner div).
  • Good test hygiene: Parameters<typeof toRun>[0] ties the test to the signature, realistic raw() helper, all 6 origins + case-insensitivity + null/undefined/unknown covered, meaningful value assertions.
  • The deferred "from chat →" link was correctly dropped along with the workerPlatformId/parentPlatformId fields that only fed it — avoids the unused-field smell flagged in the fix(web/console): gate /console behind auth + correct run-event normalizer #1878 review.

Documentation

None needed — purely intra-experiment change; CLAUDE.md's experiments/console/ description and experiments/README.md (CI line updated in #1878) remain accurate.


Verdict: READY TO MERGE (after a quick layout fix)

0 critical. The only material item is I1 — a real but cosmetic layout regression for runs without an input message (the common case for non-Claude/historical runs); it truncates long workflow names rather than breaking anything, so it doesn't hard-block, but it's a 2-line conditional-className fix worth doing before merge. I2/I3 are one-line comment-accuracy rewords. S1-S5 are optional polish + test backfill.

Recommended actions

  1. I1 — conditional workflow-span width so it fills when there's no message.
  2. I2/I3 — reword the "always" and "mirrors" comments to match reality.
  3. S1hasValue predicate (clean win; removes the repeated null-chains).
  4. S4 — backfill readCost / approval / unknown-status tests as the console heads toward default UI.

🤖 Multi-agent review via Claude Code

I1  RecentRunRow layout regression: when userMessage is empty the workflow name
    now fills the full width (flex-1) again instead of being capped at 55% with
    blank space + needless truncation; capped at 55% only when a message shares
    the line.
I2  ActiveRunCard comment: "always" → "when present" (the input row is gated on a
    non-empty message).
I3  OriginBadge comment: "mirrors" → "extends" — the icon map is a 7-origin
    superset of the old 5-entry map, adds discord, and renders no icon for unknown
    (vs the old Globe fallback).
S1  Extracted a local `hasValue` type-guard to replace the repeated
    null/undefined/'' chains on currentNode/lastTool (also narrows to string).
S2  Named the grid guard `showDetailGrid` (matches the file's elapsed/canOpen idiom).
S3  RecentRunRow: rerun-param guard uses `!== ''` to match the render guard.
S4  Backfilled run.ts coverage: unknown-status→running, readCost (positive, the
    $0.00 >0-guard, and non-numeric→null), and approval-metadata parsing
    (well-formed, message default, malformed→null). 29 → 36 console tests.
S5  RunDetailHeader: the deferred "from chat" link now has a tracked anchor —
    TODO(#1882) (filed the follow-up issue).
@Wirasm

Wirasm commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed in `0ad090a7` — thanks, I1 was a real regression I introduced.

Fixed

  • I1RecentRunRow: the workflow name now fills the full width (flex-1) when there's no input message, capped at max-w-[55%] only when a message shares the line. Fixes the blank-space + needless-truncation for Pi/Codex/historical rows.
  • I2ActiveRunCard comment "always" → "when present" (the input row is gated on a non-empty message).
  • I3OriginBadge comment "mirrors" → "extends": it's a 7-origin superset of the old 5-entry map (adds discord, renders no icon for unknown vs the old Globe fallback).
  • S1 — extracted a local hasValue type-guard replacing the repeated null/undefined/'' chains (also narrows to string).
  • S2 — named the grid guard showDetailGrid.
  • S3RecentRunRow rerun-param guard now uses !== '' to match the render guard.
  • S4 — backfilled run.ts: unknown-status→running, readCost (positive / the $0.00 >0-guard / non-numeric→null), and approval parsing (well-formed / message default / malformed→null). 29 → 36 console tests.
  • S5 — the deferred "from chat" link now has a tracked anchor: TODO(#1882) (filed Console: wire "from chat →" link once the chat route supports conversation deep-linking #1882).

bun run validate green end-to-end (all 7 checks). Nothing skipped — every item was quick and correct.

@Wirasm Wirasm merged commit 58fd9dc into dev Jun 5, 2026
3 of 4 checks passed
@Wirasm Wirasm deleted the feat/console-run-provenance branch June 5, 2026 08:53
Wirasm added a commit that referenced this pull request Jun 5, 2026
…1890)

* feat(web/console): settings core — assistant config + system panel

Adds the console's first settings surface (/console/settings, global) — the
parity floor before cutover. PR #4 of the console sequence (after #1878/#1881/#1885).

- skills/settings.ts: getConfig/updateAssistantConfig/getHealth/getUpdateCheck plus
  the pure buildAssistantUpdate(form) transform (8 unit tests). skills/providers.ts:
  listProviders. Types from @/lib/api.generated (console isolation boundary).
- store/keys.ts: config/health/providers/updateCheck keys (health reuses the literal
  'health' so it shares lib/health's cache entry).
- lib/health.ts: full HealthResponse + useHealth(); useIsDocker derives from it.
- AssistantConfigPanel: default-assistant picker (registered providers) + free-text
  model per provider + codex reasoning/web-search; dirty-gated Save → PATCH
  /api/config/assistants → ~/.archon/config.yaml → invalidate(K.config) re-seeds.
  Model is free-text for every provider (Archon does not validate model strings).
- SystemPanel: status/adapter/db/version, concurrency (active/maxConcurrent, coerced
  defensively — concurrency is an open record), running workflows, platform badges,
  update-check.
- ConsoleApp: /console/settings route, gear header link, ',' global keybinding;
  shortcuts.ts catalogue entry.

Honors the error-is-undefined cache contract throughout (the #1885 gotcha).
Excludes the GitHub device-flow panel (PR #5) and env-var editing (project-scoped).

Validation: web type-check / lint / format:check clean; 59 console tests pass (8 new).
Verified end-to-end on an isolated server: read APIs return the expected shapes, save
round-trips to config.yaml (binary paths preserved via the server deep-merge), and a
browser smoke of /console/settings renders both panels (5 providers, codex
effort/web-search, system grid, Save dirty-gated).

* fix(web/console): address PR #1890 review — comments + update-check silent failure

I1: correct the buildAssistantUpdate JSDoc. Verified the PATCH route does NOT
safe-filter per field on the write path — it validates only provider ids and merges
the body into config.yaml unfiltered (safe-filtering is read-path only). The real
invariant is that this function only ever attaches codex-only fields to the codex
entry; the comment now says that instead of the false "server safe-filters" claim.

I2: rewrite the K.health note. This PR routes lib/health through K.health, so the
old "lib/health already caches under this literal" premise is stale; the invariant
is that both consumers read via useHealth() to share one cache entry.

I3 (silent failure): SystemPanel showed "checking…" forever on a failed
update-check (the error was destructured away). Surface updateError via an
UpdateStatus helper → "update check unavailable".

S1: SettingsSection children typed ReactNode (ReactElement|ReactElement[] fought the
`cond && <el/>` pattern).
S5: replaced the nested update-status ternary with the UpdateStatus helper + early
returns for the health loading/error states.
S6: extracted the shared SettingsSection card shell (PR #5 is the 3rd consumer), a
SELECT_CLASS const for the two codex selects, and bound activePlatforms once.

Docs: added /console/settings to the console README routes.

Deferred (with rationale):
- S2 (re-seed on providers identity): latent only — nothing invalidates K.providers,
  and a ref-snapshot "fix" introduces a config-vs-providers load-order race. Keep the
  simple [config, providers] effect.
- S3 (literal-union effort/webSearch types): kept bare string so seedForm tolerates an
  out-of-enum value in config.yaml; the <select> is the write-side enforcement.
- S4 (show both load errors): an error panel showing the first error is acceptable.

Validation: web type-check / lint / format:check clean; 59 console tests pass.
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