Skip to content

fix(web/console): gate /console behind auth + correct run-event normalizer#1878

Merged
Wirasm merged 2 commits into
devfrom
fix/console-auth-event-normalizer
Jun 5, 2026
Merged

fix(web/console): gate /console behind auth + correct run-event normalizer#1878
Wirasm merged 2 commits into
devfrom
fix/console-auth-event-normalizer

Conversation

@Wirasm

@Wirasm Wirasm commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Problem: Two issues that block making /console the default UI (replacing old /chat + dashboard): (1) /console/* was reachable without login when web auth is enabled, and (2) the run-event normalizer read keys the server never writes, so node durations were always null, approvals never rendered, and resume-skipped nodes vanished.
  • Why it matters: You can't retire the old UI while the replacement bypasses auth or silently mis-renders run events.
  • What changed: Wrapped /console/* in SessionGate; fixed the event.ts normalizer (durationduration_ms, approval_requested/approval_received with decision/comment/reason, added node_skipped_prior_success, carry node-completion enrichment); added the first console test + wired src/experiments/console/ into the web test script.
  • What did NOT change (scope boundary): No new renderers (the per-node accordion and in-stream approval rendering are a follow-up PR); tool-event normalization untouched (already correct); old UI untouched.

UX Journey

Before

web auth ON:  user → /console  → console loads (NO login required)   ✗ auth bypass
run detail:   node_completed → duration shows "—" (always null)      ✗
              resume → already-done nodes simply missing             ✗
              approval gate → raw "approval_received — {…}" fallback  ✗

After

web auth ON:  user → /console  → SessionGate → /login if no session  ✓
web auth OFF: user → /console  → passthrough (unchanged)             ✓
run detail:   node_completed → real duration (read from duration_ms) ✓
              resume → skipped-prior-success nodes render as skipped ✓
              approvals classified correctly (requested/received)    ✓

Architecture Diagram

Before / After

App.tsx Routes
  /console/* ──▶ ConsoleApp                 (before: ungated)
  /console/* ──▶ [~SessionGate] ──▶ ConsoleApp   (after: gated, still no Layout/TopNav)

experiments/console/primitives/event.ts  (toRunEvent normalizer)
  reads workflow_events.data ── corrected keys ──▶ RunEvent ──▶ RunStream/NodeDivider

Connection inventory:

From To Status Notes
App.tsx /console/* SessionGate new console now inside the auth gate
SessionGate ConsoleApp new passthrough when web auth disabled
event.ts toRunEvent workflow_events.data modified reads the keys the server actually writes
web/package.json test src/experiments/console/ new CI now runs console tests

Label Snapshot

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

Change Metadata

  • Change type: bug (security + correctness)
  • 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                  # all files clean
bun --filter @archon/web test         # 180 pass / 0 fail (incl. 14 new console normalizer tests)
bun run check:bundled{,-skill,-schema} # all OK (unaffected)
  • Evidence: commands above all pass. The new event.test.ts (14 cases) asserts each corrected mapping against payload shapes taken from real workflow_events rows in a live DB (duration_ms, node_output/cost_usd/stop_reason/num_turns, approval_received decision/comment/reason, node_skipped_prior_success reason:'prior_success').
  • Skipped: full all-packages bun run test — change is isolated to @archon/web; ran that package's full suite instead. CI runs the rest.

Security Impact (required)

  • New permissions/capabilities? No — this removes an unintended capability (unauthenticated console access).
  • New external network calls? No
  • Secrets/tokens handling changed? No
  • File system access scope changed? No
  • Risk/mitigation: closes an auth-bypass. SessionGate is the same component already gating every other route; it no-ops when web auth is disabled, so solo/default installs are unaffected.

Compatibility / Migration

  • Backward compatible? Yes — no-op when web auth is disabled (the default).
  • Config/env changes? No
  • Database migration needed? No

Human Verification (required)

  • Verified scenarios: ran the 14-case normalizer test green; payload shapes cross-checked against real workflow_events rows (node_completed, node_skipped, node_skipped_prior_success, tool_called/completed) and against the emit sites for approval rows (dag-executor.ts, workflow-operations.ts). Confirmed RunStream/NodeDivider already consume durationMs/skipReason, so the duration + skip fixes are immediately visible.
  • Edge cases checked: long node output truncates to 300 chars; node_started has null duration/enrichment; the never-written approval_pending type correctly does NOT render as an approval (regression guard).
  • Not verified: in-stream rendering of approval events — RunStream doesn't yet render kind:'approval' entries (the normalizer now classifies them correctly; the renderer is a follow-up).

Side Effects / Blast Radius (required)

  • Affected subsystems: console route gating; console run-event normalization; web test script.
  • Potential unintended effects: none expected — SessionGate passthrough preserves current behavior when auth is off; normalizer changes only correct mismatched keys (the only NodeTransitionEvent constructor is the normalizer itself).
  • Guardrails: the new console test segment now runs in CI.

Rollback Plan (required)

  • Fast rollback: git revert <merge-commit> — 4 files, web-only.
  • Feature flags/toggles: none.
  • Observable failure symptoms: console redirects to /login unexpectedly (would indicate an auth-status misconfig, not this change) or node durations regress to null.

Risks and Mitigations

  • Risk: a consumer somewhere constructs NodeTransitionEvent literals and breaks on the new required fields.
    • Mitigation: verified the normalizer is the sole constructor; RunStream only reads the type. Type-check passes.

Summary by CodeRabbit

  • New Features

    • Event details now include enhanced metadata: output previews, cost, stop reasons, and turn counts.
  • Bug Fixes

    • Console route now enforces authentication consistently.
    • Approval event handling revised for more accurate request/response mapping.
  • Tests

    • Expanded test coverage for console event transformation and enrichment logic.
  • Documentation

    • Clarified experiments docs about route status and CI test scope.

…lizer

Two correctness/security fixes that block the console from replacing the old
chat + dashboard UI.

Security: /console/* mounted OUTSIDE SessionGate, so with web auth enabled the
console was reachable without login while every other route is gated. Wrap it
in SessionGate — still outside Layout so it keeps omitting TopNav. SessionGate
is a passthrough when web auth is disabled (the solo default), so this is a
no-op there.

Correctness: the run-event normalizer read keys the server never writes —
- node duration read `duration`, but the row carries `duration_ms`, so every
  rendered node duration was null;
- approvals checked `approval_pending`/`approval_resolved` with a `resolution`
  key, but the server writes `approval_requested`/`approval_received` with
  `decision` + `comment`/`reason`, so approvals fell through to the raw-JSON
  text fallback;
- `node_skipped_prior_success` (emitted on resume for already-done nodes) was
  dropped entirely.
Also carry the node_completed enrichment already in the payload (output
preview, cost, stop reason, turns) for the upcoming per-node detail view.

Adds the first test under experiments/console and wires
src/experiments/console/ into the web test script so CI starts covering the
console as it is promoted.
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4392a072-5c10-4e0f-97ef-0c47393d9d3c

📥 Commits

Reviewing files that changed from the base of the PR and between 4aa039d and c11c7b1.

📒 Files selected for processing (4)
  • packages/web/src/App.tsx
  • packages/web/src/experiments/README.md
  • packages/web/src/experiments/console/primitives/event.test.ts
  • packages/web/src/experiments/console/primitives/event.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/web/src/experiments/README.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/web/src/App.tsx
  • packages/web/src/experiments/console/primitives/event.ts

📝 Walkthrough

Walkthrough

Wraps the console route in SessionGate for auth; enriches node transition events with outputPreview, costUsd, stopReason, numTurns; changes approval normalization to approval_requested/approval_received pairs; adds Bun tests and runs them via the web package test script.

Changes

Console Authentication & Event Normalization

Layer / File(s) Summary
Console Route Auth Enforcement
packages/web/src/App.tsx
/console/* route rendered inside SessionGate while remaining outside Layout (avoids TopNav inheritance) to enforce web auth.
Event Data Schema & Normalization
packages/web/src/experiments/console/primitives/event.ts
NodeTransitionEvent adds outputPreview, costUsd, stopReason, numTurns; introduces NODE_TRANSITION_BY_EVENT; toRunEvent reads duration_ms into durationMs, sets skip metadata only for skipped, truncates node_output into outputPreview, and uses approval_requested/approval_received rows for approval events.
Event Transformation Tests & Test Runner
packages/web/package.json, packages/web/src/experiments/console/primitives/event.test.ts, packages/web/src/experiments/README.md
test script now runs bun test src/experiments/console/. New Bun test suite adds a raw() factory and covers base mappings, node transitions (duration, output preview, skip handling), tool calls, approvals (request/received pairs and resolutions), errors/lifecycle events, and unknown-event fallback; experiments README clarifies CI coverage for experiment primitives.
sequenceDiagram
  participant Client as Console Route
  participant SessionGate as SessionGate
  participant toRunEvent as toRunEvent
  participant DB as Server Rows
  participant UI as Console UI

  Client->>SessionGate: request /console/*
  SessionGate->>Client: allow/deny based on web auth

  DB->>toRunEvent: emit event rows (node_*, tool_*, approval_*, workflow_*)
  toRunEvent->>UI: normalized events (node_transition with enrichment, tool_call/completion, approval request/received, system/text fallbacks)
Loading

🎯 3 (Moderate) | ⏱️ ~20 minutes

🐰 I hopped through routes and tests tonight,
Wrapped the console snug and kept outputs light,
Approvals paired, previews trimmed with care,
Tests hop in to check each change we share. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.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 'fix(web/console): gate /console behind auth + correct run-event normalizer' directly and specifically describes the two main bug fixes in the changeset: auth gating and event normalizer corrections.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering all template sections: it clearly articulates the problem, explains significance, details what changed, defines scope boundaries, includes before/after UX journeys, provides architecture diagrams with connection inventory, includes validation evidence and security impact assessment, and includes all required sections like human verification and rollback plan.
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 fix/console-auth-event-normalizer

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.

@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.

🧹 Nitpick comments (1)
packages/web/src/experiments/console/primitives/event.ts (1)

228-246: 💤 Low value

Consider explicit handling for unexpected decision values.

If decision is empty, missing, or an unexpected value (e.g., 'pending'), readString returns '', which silently falls through to the 'approved' branch. Per coding guidelines, fallback behavior should be documented when intentional, or handled explicitly.

A brief comment would suffice if this is intentional, or add an explicit check:

Option A: Document the fallback
   if (et === 'approval_received') {
-    const decision = readString(data, 'decision'); // 'approved' | 'rejected'
+    // Server sends 'approved' | 'rejected'; default to approved if malformed
+    // (safe fallback — doesn't block workflow, and backend state is authoritative).
+    const decision = readString(data, 'decision');
     return {
Option B: Explicit check
   if (et === 'approval_received') {
     const decision = readString(data, 'decision'); // 'approved' | 'rejected'
     return {
       ...base,
       kind: 'approval',
       prompt: '',
       resolution:
         decision === 'rejected'
           ? {
               kind: 'rejected',
               at: raw.created_at,
               reason: readString(data, 'reason'),
             }
+          : decision === 'approved'
+            ? {
+                kind: 'approved',
+                at: raw.created_at,
+                comment: readStringOrNull(data, 'comment'),
+              }
           : {
               kind: 'approved',
               at: raw.created_at,
-              comment: readStringOrNull(data, 'comment'),
+              comment: `[unknown decision: ${decision || '(empty)'}]`,
             },
     };
   }
🤖 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 `@packages/web/src/experiments/console/primitives/event.ts` around lines 228 -
246, The code currently treats any non-'rejected' decision (including '' or
unexpected values) as 'approved'; update the logic around the decision variable
(read via readString) to explicitly handle allowed values: check if decision ===
'rejected' -> rejected branch, else if decision === 'approved' -> approved
branch, and else -> an explicit fallback (e.g., return resolution with kind
'unknown' or log/warn and include the raw decision) so unexpected/empty values
are not silently treated as approved; reference variables/functions: decision,
readString, readStringOrNull, kind:'approval', resolution, raw.created_at.
🤖 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.

Nitpick comments:
In `@packages/web/src/experiments/console/primitives/event.ts`:
- Around line 228-246: The code currently treats any non-'rejected' decision
(including '' or unexpected values) as 'approved'; update the logic around the
decision variable (read via readString) to explicitly handle allowed values:
check if decision === 'rejected' -> rejected branch, else if decision ===
'approved' -> approved branch, and else -> an explicit fallback (e.g., return
resolution with kind 'unknown' or log/warn and include the raw decision) so
unexpected/empty values are not silently treated as approved; reference
variables/functions: decision, readString, readStringOrNull, kind:'approval',
resolution, raw.created_at.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 33675beb-81e3-46e3-86e9-b850be9caa0d

📥 Commits

Reviewing files that changed from the base of the PR and between c834c2a and 4aa039d.

📒 Files selected for processing (4)
  • packages/web/package.json
  • packages/web/src/App.tsx
  • packages/web/src/experiments/console/primitives/event.test.ts
  • packages/web/src/experiments/console/primitives/event.ts

@Wirasm

Wirasm commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator Author

PR Review Summary — multi-agent review

7 specialized agents reviewed this PR (code, docs, tests, comments, errors, types, simplify). Three of them (code-reviewer, comment-analyzer, silent-failure-hunter) independently traced every key-mapping back to the actual server emit sites — and all the core fixes are correct:

Claim Verified at
duration_ms (not duration) on node_completed dag-executor.ts:1189
cost_usd / stop_reason / num_turns / node_output dag-executor.ts:1191-1193
approval_requested + approval_received (decision/comment/reason) dag-executor.ts:2335,2531, workflow-operations.ts:162,192,247
node_skipped_prior_success carries reason:'prior_success' dag-executor.ts:2693-2696

Console reads from DB rows (/api/workflows/runs/:id), so the DB-persisted key names are the right thing to map — the in-process emitter's approval_pending naming correctly does not apply here.


Critical Issues (0)

None. No blocking issues; CI is green (ubuntu + windows).

Important Issues (2) — recommended, neither blocks merge

# Agent(s) Issue Location
I1 code-reviewer, silent-failure-hunter approval_received resolution uses decision === 'rejected' ? rejected : approved. Any non-'rejected' value — including '' from a missing decision key — silently renders as approved. Harmless today (server always writes a valid decision) but is exactly the silent key-mismatch class this PR set out to kill. Prefer an explicit 3-way with a null fallback. event.ts approval_received branch (~L229-244)
I2 comment-analyzer Comment claims "a renderer pairs the two by nodeId, the way tool_called/tool_completed pair." That pairing does not existRunStream skips approval events entirely and the approval UI is driven from run.approval metadata. The comment describes planned behavior as current fact; will mislead whoever builds the approval renderer. Reword to "a future renderer would pair…". event.ts approval block comment

Suggestions (6)

# Agent Suggestion Location
S1 code-simplifier Replace the 4-level nested transition ternary with a Record<string, …> lookup + ?? 'skipped' default — flatter, shows the whole mapping at a glance, preserves behavior. event.ts (~L158-163)
S2 comment-analyzer, type-design Forward-reference "the per-node accordion (follow-up)" in the enrichment JSDoc will rot if the UI lands differently. Drop the feature name → "Not rendered by any current component." event.ts NodeTransitionEvent JSDoc
S3 type-design Enrichment fields (outputPreview/costUsd/stopReason/numTurns) are completed-only but live flat-nullable with no per-field JSDoc. Add one-line null contracts now; defer the discriminated-union-on-transition refactor to the accordion PR when the fields are actually rendered. event.ts:62-70
S4 pr-test-analyzer, type-design Add a node_failed test asserting transition:'failed' + null enrichment. The transition chain ends in a 'skipped' catch-all, so if node_failed were dropped from the guard it'd silently mis-route — no test catches that today. event.test.ts
S5 pr-test-analyzer Untested normalizer branches now shipping in CI: error events (the error-then-message || priority is invisible), workflow_* → system events (4-key detail fallback), and nodeName fallback (existing node_started test asserts nodeId but never nodeName). One-line adds, 4-6/10 criticality. event.test.ts
S6 comment-analyzer App.tsx comment says SessionGate is "a no-op" when auth is off — it actually renders a brief FullScreenLoader until /api/auth/status resolves (cached after). Minor wording. App.tsx /console/* comment

Pre-existing latent notes (not regressions, safe to defer)

  • argsSummary: readString(data,'argsSummary') is always '' — the server never writes that key (silent-failure-hunter). Defer until a renderer consumes it.
  • skipReason JSDoc omits the real when_condition_parse_error value the server also emits (silent-failure-hunter).
  • No DOM test infra in @archon/web (no testing-library/jsdom), so the SessionGate security fix has no automated test. The highest-value future target is a SessionGate unit test (pure fn of two queries), not a route-tree test (pr-test-analyzer, 7/10 — accepted given the existing no-DOM-tests constraint).

Documentation

  • packages/web/src/experiments/README.md:7"CI does not guarantee these routes work" is now stale; this PR wires src/experiments/console/ into the CI test script. Narrow it: "CI does not guarantee these routes work end-to-end, but unit tests for experiment primitives now run in CI." (CLAUDE.md and docs-web need no change — console is internal/experimental.)

Strengths

  • Every key-mapping fix verified correct against real server emit sites — the heart of the PR is sound.
  • The duration_ms test ships with an inline regression-guard comment explaining the historical null bug.
  • Negative test proves the old approval_pending keys no longer route to the approval branch.
  • Explicit transition mapping is strictly safer than the old et.replace('node_','') string-slice.
  • Parameters<typeof toRunEvent>[0] ties the test to the function signature — drifts break at compile time.
  • Experiment isolation boundary respected (type-only @/lib/api import); SessionGate wrapping matches every other gated route; shared ['auth-status'] query → no double fetch.

Verdict: READY TO MERGE

No critical or blocking issues; CI green; the core correctness + security fixes are verified against the source of truth. The two "important" items (I1 misclassification, I2 inaccurate comment) are quick, worthwhile polish — I1 hardens the normalizer against the same silent-mismatch class it fixes, and I2 is a one-line reword — but neither blocks. S1-S6 are optional.

Recommended actions (all ~5-line changes)

  1. I1 — explicit 3-way decision check, null resolution on unknown.
  2. I2 — reword the "pairs by nodeId" comment to future tense.
  3. S1 — transition Record lookup (optional, nice).
  4. S4/S5 — a handful of cheap normalizer test cases (node_failed, error, workflow_*, nodeName).
  5. README.md:7 doc line.

🤖 Multi-agent review via Claude Code

…ocs)

I1  approval_received now matches `decision` explicitly — an unknown/missing
    decision stays unresolved (null) instead of silently rendering as approved
    (the exact silent-mismatch class this normalizer set out to kill).
I2  reworded the approval comment to future tense and noted that nothing renders
    approval events in the run stream today (paused gates use run.approval metadata).
S1  transition mapping → NODE_TRANSITION_BY_EVENT record lookup (flatter, shows
    the whole map; node_failed kept explicit so its test guards the default).
S2  enrichment JSDoc: dropped the "per-node accordion (follow-up)" forward
    reference; stated the completed-only null contract instead.
S6  App.tsx comment: SessionGate is a passthrough after a brief (cached)
    auth-status check, not a literal no-op.
S4  added a node_failed test (guards the map vs the `?? 'skipped'` default).
S5  added error (error>message priority), workflow_*→system, and nodeName
    fallback tests; plus an I1 guard test (unknown decision → unresolved).
docs narrowed the stale "CI does not guarantee these routes work" line in
    experiments/README.md now that console primitives run in CI.

Skipped with rationale: S3 discriminated-union-on-transition refactor (defer to
the PR that renders the fields); argsSummary dead-read cleanup (pre-existing, no
consumer yet); a SessionGate automated test (needs jsdom/testing-library infra
not present in @archon/web — a future SessionGate unit test is the right target).
@Wirasm

Wirasm commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed in c11c7b1f. Thanks for the thorough pass — the I1 catch is the good one.

Fixed

  • I1approval_received now matches decision explicitly; an unknown/missing decision stays unresolved (null) instead of silently rendering as approved. Added a guard test for {} and {decision:'???'}.
  • I2 — reworded the approval comment to future tense and noted that nothing renders approval events in the run stream today (paused gates use run.approval metadata) — so this is correctness of classification, not display.
  • S1 — transition mapping is now a NODE_TRANSITION_BY_EVENT record lookup; node_failed stays an explicit entry so S4's test guards it against the ?? 'skipped' default.
  • S2 — enrichment JSDoc drops the "accordion (follow-up)" forward-reference; states the completed-only null contract instead.
  • S4/S5 — added node_failed, nodeName-fallback, error (error>message priority), and workflow_*→system tests. Test count 14 → 21.
  • S6 — App.tsx comment corrected: passthrough after a brief cached auth-status check, not a literal no-op.
  • docs — narrowed the stale experiments/README.md CI line now that console primitives run in CI.

Skipped (with rationale)

  • S3 discriminated-union-on-transition — deferred to the PR that actually renders the enrichment fields; modelling the union before anything reads it is speculative.
  • argsSummary dead-read — pre-existing, no consumer; defer until a renderer needs tool args.
  • SessionGate automated test@archon/web has no jsdom/testing-library, so this needs new DOM-test infra. A future SessionGate unit test (pure fn of two queries) is the right target, not a route-tree test in this PR.

type-check / lint / format / 21 console tests all green.

@Wirasm Wirasm merged commit 07645a8 into dev Jun 5, 2026
4 checks passed
@Wirasm Wirasm deleted the fix/console-auth-event-normalizer branch June 5, 2026 08:22
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