feat(dashboard): per-instance failover-history drawer (M26-3 follow-up) #555

Merged
charles merged 24 commits from feat/agents-history-drawer into main 2026-04-29 22:49:57 +00:00
Collaborator

Summary

Closes the M26-3 follow-up flagged when PR #554's redesign repurposed the drawer slot for the per-type editor — no UI consumer for the agent_provider_events ledger that's been live since M26-1.

Changes

UI:

  • New History button on each instance row alongside Adv / Del.
  • Click → InstanceHistoryDrawer slides in from the right (matches TypeEditorDrawer shape: backdrop + Esc + × close). 640 px wide.
  • Top — 24h tier-time stacked bar. computeTierIntervals walks the ledger chronologically, closes the last interval at now, and bins durations per tier. Empty ledger → single full-width interval at current_tier. Legend underneath: percent + minutes per tier.
  • Bottom — chronological 7-day event list. Each row: relative time, tier transition (tier 1 → 2 or tier 2 for resets), failure kind, optional task id.
  • Tier colours: tier 1 bg-state-running, tier 2 bg-warning, tier 3 bg-error. Matches the TierBadge palette.

Wire-up:

  • React-query caches per-agent for 10 s; no SSE plumbing — drawer is a debugging surface, occasional refetch is fine.
  • Reuses existing fetchProviderEvents(name, { since, limit }) from lib/api.ts (M26-3 added this; previously had no consumer).

Test plan

  • just typecheck clean.
  • Manual: click History on a tier-1 healthy agent — bar shows 100% green, list says "no failovers in last 7 days".
  • Manual: trigger a fake failover via DB (or wait for one), reopen drawer, see the row + the bar updates.
  • Manual: Esc + backdrop click + × button all close the drawer.

🤖 Generated with Claude Code

## Summary Closes the M26-3 follow-up flagged when PR #554's redesign repurposed the drawer slot for the per-type editor — no UI consumer for the `agent_provider_events` ledger that's been live since M26-1. ## Changes **UI:** - New `History` button on each instance row alongside `Adv` / `Del`. - Click → `InstanceHistoryDrawer` slides in from the right (matches `TypeEditorDrawer` shape: backdrop + Esc + × close). 640 px wide. - **Top — 24h tier-time stacked bar.** `computeTierIntervals` walks the ledger chronologically, closes the last interval at `now`, and bins durations per tier. Empty ledger → single full-width interval at `current_tier`. Legend underneath: percent + minutes per tier. - **Bottom — chronological 7-day event list.** Each row: relative time, tier transition (`tier 1 → 2` or `tier 2` for resets), failure kind, optional task id. - Tier colours: tier 1 `bg-state-running`, tier 2 `bg-warning`, tier 3 `bg-error`. Matches the TierBadge palette. **Wire-up:** - React-query caches per-agent for 10 s; no SSE plumbing — drawer is a debugging surface, occasional refetch is fine. - Reuses existing `fetchProviderEvents(name, { since, limit })` from `lib/api.ts` (M26-3 added this; previously had no consumer). ## Test plan - [x] `just typecheck` clean. - [ ] Manual: click `History` on a tier-1 healthy agent — bar shows 100% green, list says "no failovers in last 7 days". - [ ] Manual: trigger a fake failover via DB (or wait for one), reopen drawer, see the row + the bar updates. - [ ] Manual: Esc + backdrop click + × button all close the drawer. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(dashboard): per-instance failover-history drawer
Some checks failed
qa / qa (pull_request) Has been cancelled
qa / dockerfile (pull_request) Has been cancelled
bdd62dbfaa
Closes the M26-3 follow-up flagged in PR #554 — surfaces the
`agent_provider_events` ledger that's been live since M26-1 but had
no UI consumer.

- New `History` button on each instance row alongside `Adv` / `Del`.
- Click → `InstanceHistoryDrawer` slides in from the right (matches
  the `TypeEditorDrawer` pattern: backdrop click + Esc + × button to
  close). Drawer width 640px (narrower than type editor's 900px since
  there's less to fit).
- Top: 24h tier-time stacked bar. `computeTierIntervals` walks the
  ledger chronologically + closes the last interval at `now`. Empty
  ledger → single full-width interval at `current_tier`. Legend
  underneath shows percent + minutes per tier.
- Bottom: chronological 7-day event list. Each row: relative time,
  tier transition (`tier 1 → 2` or `tier 2` for resets), failure
  kind, optional task id.
- Tier colours: tier 1 green, tier 2 amber, tier 3 red. Matches the
  TierBadge palette.
- React-query caches per agent for 10s; no SSE plumbing — the drawer
  is a debugging surface, occasional refetch is fine.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(dashboard): slide-in animation on drawers
Some checks failed
qa / qa (pull_request) Has been cancelled
qa / dockerfile (pull_request) Has been cancelled
706b972a58
Two new keyframes in `apps/web/src/styles/index.css`:
- `ch-slide-in-right` — drawer slides 100% → 0 from the right edge.
- `ch-fade-in` — backdrop fades 0 → 1.

Both run for 200 ms on `cubic-bezier(0.16, 1, 0.3, 1)` (ease-out-expo —
fast start, gentle settle, matches the iOS / Tokyo Night feel).

Applied via utility classes (`ch-drawer-enter`, `ch-backdrop-enter`) to
both `TypeEditorDrawer` (Phase 2) and `InstanceHistoryDrawer` (M26-3
follow-up) for a consistent slide motion across the page.

No exit animation — drawers unmount immediately on close. Could add
later via Base UI's `Dialog` primitive (which handles enter/exit phases)
but the current shape is plain `<aside>` and the pop-out feels fine for
the operator workflow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(dashboard): drawer leave animation + dead-code cleanup
All checks were successful
qa / dockerfile (pull_request) Successful in 9s
qa / qa (pull_request) Successful in 15m38s
f284976b25
Adds slide-out + fade-out keyframes mirroring the enter animation:

- `ch-slide-out-right` — drawer translates 0 → 100 % off the right edge.
- `ch-fade-out` — backdrop opacity 1 → 0.
- 180 ms (slightly faster than enter's 200 ms — established UX
  convention; exits feel more responsive when shorter).
- Curve: `cubic-bezier(0.7, 0, 0.84, 0)` (ease-in-expo) — accelerates
  away. Mirror of the enter's ease-out-expo.

Implementation:
- Each drawer carries a local `leaving` boolean.
- `triggerClose` wraps `onClose`: sets `leaving = true`, then
  `setTimeout(onClose, 200)` unmounts via parent state.
- Idempotent — `leaving` guard prevents re-fire on rapid Esc /
  backdrop spam.
- All three close paths (Esc, backdrop click, × button) wired to
  `triggerClose` instead of `onClose` directly.
- Applied to both `TypeEditorDrawer` and `InstanceHistoryDrawer`.

Cleanup hitchhikers (lint surfaced once dead state was orphaned):
- Drop `ThresholdsSection` import (unused after Phase 1).
- Drop `healthLoading` / `healthError` destructure (FleetHealthStrip
  consumed them; component removed in Phase 1).
- Drop `Selection` type + `parseSelection` / `encodeSelection` /
  `selection` / `setSelection` — relics of the master-detail master
  view that Phase 2's TypeGroupCard list replaced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(dashboard): type editor opens with all sections collapsed
Some checks failed
qa / dockerfile (pull_request) Successful in 4s
qa / qa (pull_request) Has been cancelled
43bebbd2d0
Previously Provider was auto-open; everything else collapsed. Operator
prefers a clean slate — open whatever you came to edit. URL deep-link
via the `section` query-param still seeds the matching section open
so links from elsewhere land on the right block.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(dashboard): type group cards collapse by default
All checks were successful
qa / dockerfile (pull_request) Successful in 10s
qa / qa (pull_request) Successful in 2m53s
a457974518
Boss / dev / reviewer / etc. now render with the instances list
collapsed at first paint. Operator clicks the chevron to expand the
group they care about. Matches the type-editor accordion's
"open-when-asked" behaviour from the previous commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(dashboard): hide foreman from the agents page
All checks were successful
qa / dockerfile (pull_request) Successful in 13s
qa / qa (pull_request) Successful in 1m54s
678591bf5e
Foreman is the host-mode singleton — runs in-process, no container,
no instances to spawn or destroy. The card was rendering with a
read-only host explainer and an empty body, taking up vertical space
without offering any control. Filter it out of `typeNames`.

Config still lives in `agents.json::types.foreman`; operators who need
to edit prompt / plugins / template can drop into the (future) JSON
escape hatch or hand-edit the file. The host-mode allowlist gate
(M18-4) is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(dashboard): Adv into a drawer + icon-only row buttons
All checks were successful
qa / dockerfile (pull_request) Successful in 6s
qa / qa (pull_request) Successful in 1m55s
3e257610d2
- New `InstanceAdvancedDrawer` mirrors `InstanceHistoryDrawer` /
  `TypeEditorDrawer` (slide-in animation, leave guard, Esc/backdrop/×
  closes). Wraps the existing `InstanceAdvancedEdit` body so the
  prompt-appendix + notes textareas now live in a focused panel
  instead of an inline expansion row.
- Drop the inline-row machinery: `expanded` Set state, `toggleAdv`,
  the colSpan adv `<tr>`, and the `Fragment` wrapping that needed.
  Net: ~30 lines lighter.
- New `RowActionButton` — square 28×28 icon-only ghost button. Wraps
  `title` + `aria-label` + `data-testid` for consistency. Variants:
  default ghost or `tone="error"` (red on hover for destructive).
- History → 🕘 (clock).
- Adv edit → ✎ (pencil) — distinct from `⚙` used by type-level edit.
- Delete → 🗑.
- Operator routes the action via tooltip; row stays compact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(dashboard): stack advanced-edit textareas vertically + grow them
All checks were successful
qa / dockerfile (pull_request) Successful in 6s
qa / qa (pull_request) Successful in 1m43s
8ba229203a
Side-by-side `grid-cols-2` made the textareas tiny on the 640px drawer.
Switch to flex-col so each textarea spans the full width. Bump initial
rows: 10 for the prompt appendix (longer, multi-paragraph), 6 for
notes (shorter operator memo). Both keep `resize-y` so the operator
can drag-resize per their typing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(dashboard): autosave drawer edits, retire Save button
All checks were successful
qa / dockerfile (pull_request) Successful in 6s
qa / qa (pull_request) Successful in 1m57s
605abd29f2
The Save button was redundant — most page edits were already silent
auto-saves (inline model + match-labels + appendix + notes via
patchAgent on blur; tier reset/pause via dedicated POSTs). Only the
type-level drawer edits still required an explicit Save click.

Now they don't:

- New `useEffect` watches `typesDraft` + `globalsDraft`. 800 ms after
  the last mutation, fires `putAgentConfig` once. Saves cluster
  cleanly: editing a tier provider + cooldown_min back-to-back fires
  one PUT, not two.
- `lastSavedRef` carries the last successfully-saved JSON snapshot;
  the bootstrap effect updates it so the initial load doesn't trigger
  a redundant save.
- Page-header Save button → status indicator only:
  `Saving…` / `Saved` / `Save failed` (red on failure).
- Toast on success removed (would be noisy with autosave); error toast
  preserved so a 400 from the server is hard to miss.
- Server-side validation still rolls the file back on bad save, so
  the operator's edit-in-place draft never wedges the live config.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(dashboard): trim new-agent dialog to 4 fields
All checks were successful
qa / dockerfile (pull_request) Successful in 6s
qa / qa (pull_request) Successful in 1m53s
8aaa4bae8d
Drop Type dropdown + Model override from the create-instance dialog.
Type is implicit from the `+ Instance` button on the type-group card
(passed via `presetType`); model defaults to the type's `default_model`
and the operator overrides inline on the row after creation.

Remaining fields: Name, Match labels, Prompt appendix, Notes — the
genuinely-per-instance overrides operators care about at creation time.

Title now reads "New <type> instance" (with the type as an accent-coloured
code span) so the operator can confirm which type they're spawning into
without a dropdown.

`AgentEditor` props slimmed: `types`, `models`, `initial` removed; only
`open`, `onClose`, `presetType` survive. Props that surfaced edit-mode
were dead anyway since #554 (edit lives inline + in the Adv drawer).
Also drops the now-unused `availableTypes` + `models` derived values
in `AgentsRoute`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(dashboard): drop Model column, move model_override into Adv drawer
All checks were successful
qa / dockerfile (pull_request) Successful in 11s
qa / qa (pull_request) Successful in 1m54s
eaf9dce90f
Type-level Provider chain editor is the source of truth for model
selection — the per-row Model column duplicated it. Tier badge already
displays the active model id in the same row, so the column is
redundant.

- Drop the Model `<th>` + per-row `InlineModelEdit` cell.
- Drop the `InlineModelEdit` component (no consumer left).
- Add a `Model override` input to `InstanceAdvancedEdit` (Adv drawer
  body). Free-text, blur-commits, empty value clears the override
  and inherits `type.default_model`. Helper text explains it's rare.

Operator workflow:
- Same model for all instances of a type → set once on Provider chain
  tier 1 (drawer).
- One instance pinned to a different model → click ✎ on the row,
  type into Model override.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
refactor(agents): remove model_override completely
All checks were successful
qa / dockerfile (pull_request) Successful in 6s
qa / qa (pull_request) Successful in 1m59s
f411430bed
Per-instance Provider chain is now the only model surface. The model
field was a dead UI input — Provider chain on the type already covers
the use case via tier 1.

Server: response always reports `model: type.default_model` and
`model_override: null`. POST/PATCH silently drop the `model` key.
mergeAgent ignores the row column.

Web: drop ModelCombobox import, drop model from Create/Patch payloads.

Tests: rewrite the 4 model-override tests to assert the field is
ignored on PATCH/POST and that resolveAgent always returns
type.default_model.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(dashboard): drop Queue + Status columns from instances table
Some checks failed
qa / dockerfile (pull_request) Successful in 5s
qa / qa (pull_request) Has been cancelled
4368e2afb7
Both surface live worker state — useful on the monitor page, noise on
the agents config page. Tier already conveys health via the failover
state machine.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(dashboard): make whole type-card header clickable to expand
All checks were successful
qa / dockerfile (pull_request) Successful in 7s
qa / qa (pull_request) Successful in 1m57s
f77ce0d570
Absolute-positioned overlay button covers the full row; left content
gets pointer-events-none so clicks fall through to the toggle. Edit
type button stays clickable as a sibling above the overlay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(design-system): consolidate Button + adopt lucide-react icons
All checks were successful
qa / dockerfile (pull_request) Successful in 13s
qa / qa (pull_request) Successful in 2m40s
26e72f636d
Foundation primitive — every clickable in the app should now route
through `<Button>`. Agents page is the migration template; remaining
features sweep in follow-up PRs.

Button changes:
- Add `iconOnly` modifier (square aspect, sm/md/lg → 28/36/44 px)
- Add `loading` prop (Loader2 spinner, disables click, aria-busy)
- Add `tone="error"` (red focus ring + hover for destructive actions)
- Add `leadingIcon` / `trailingIcon` slots typed as `LucideIcon`
- `cursor-pointer` baseline; per-variant focus-visible rings

Icon library: lucide-react (~1-2 kB per icon, tree-shakable, MIT).
No other icon deps needed.

Agents page migration:
- RowActionButton primitive removed → `<Button variant="ghost" iconOnly>`
- 🕘 ✎ 🗑 → Clock / Pencil / Trash2 (row actions)
- ⚙ → Settings (Edit type)
- ▶ ▼ → ChevronRight / ChevronDown (collapse)
- ⏸ ▶ → Pause / Play (tier badge pause toggle)
- ↺ → RotateCcw (reset tier)
- × → X (drawer + dialog close, chip remove, env row remove, tier remove)
- ⚠ → AlertTriangle (provider chain validation)

Tier number glyphs (① ② ③) kept as semantic numbered markers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(agents): hoist add-type CTA to header + mobile responsive layout
All checks were successful
qa / dockerfile (pull_request) Successful in 11s
qa / qa (pull_request) Successful in 1m51s
7091db13a4
Page header:
- Move `+ Add agent type` button to title row (right-aligned)
- Hide save status on mobile (autosave is fast enough that the indicator
  is mostly cosmetic — drop the noise on small viewports)
- Compact label on mobile: "Add type" instead of "Add agent type"

Type card header:
- `flex-wrap` so summary chip + instance count fall to a second line
  cleanly on narrow viewports
- Instance count → numeric badge on mobile (×N), full text on desktop
- Edit-type button: icon-only on mobile, icon+label on desktop
- Tighter horizontal padding (px-3) on mobile

Instances surface:
- Desktop: keep table layout
- Mobile: stack each instance as a card (name + actions row, then tier
  badge, match labels, last-active line) — no more squashed columns
- Shared action-button render avoids duplication

Container padding: p-3 on mobile, p-4 on sm+.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(agents): move dispatch-pause to drawer, clean up tier column
All checks were successful
qa / dockerfile (pull_request) Successful in 5s
qa / qa (pull_request) Successful in 1m48s
76eed90526
The pause button on the row read as "pause running task" — confusing
on idle agents (nothing to pause), and pausing flipped the row to a
red ✕ + recovery icons that looked like a crash state.

Tier column now shows status only:
- healthy: tier number + active model (no controls)
- degraded (tier > 1): adds the reset button
- paused: red "Paused" pill + Resume + Reset (recovery actions only)

Pause/disable moves into the Advanced drawer as a labelled toggle
("Dispatch · Enabled/Disabled"). It's a rare ops action — drawer has
the room to explain what it actually does (stop accepting new tasks).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three fixes from PR review.

1. Autosave race (data loss):
   - Mutation snapshots payload at fire-time, not at resolve-time, so
     edits made between PUT fire and resolve can't be rolled back into
     `lastSavedRef`.
   - Bootstrap effect now gates on `inFlightSaveRef` so a cache
     invalidation triggered by another query (or a refetch) mid-save
     can't stomp the user's in-progress edits.
   - Drop the redundant `invalidateQueries(["agent-config"])` on
     onSuccess — the stale-while-revalidate refetch already brings the
     server snapshot back; the explicit invalidate just doubled writes.

2. Duplicate testids in DOM:
   - `InstancesTable` previously rendered both desktop table and mobile
     stack with `hidden sm:*` classes. Both branches lived in the DOM,
     so every `agent-row-*`, `agent-inspect-*`, etc. testid duplicated
     and Playwright strict locators would throw.
   - Add `useMediaQuery` hook (SSR-safe via useSyncExternalStore) and
     mount only the active layout.

3. Drawer mutual exclusion:
   - Three independent state vars (editingTypeName, inspectingInstance,
     advEditingInstance) let drawers stack on top of each other.
   - Collapse to a single tagged-union state — opening any drawer
     closes the other two.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UX polish from PR review.

- Tier column displays status only when not paused. Reset moves into
  the row's action cluster (next to History / Edit / Delete) so the eye
  doesn't confuse a Reset button for a status glyph — same trap the
  user hit with the previous red ✕.
- Replace  emoji with the lucide `Fuel` icon (last emoji left after
  the icon migration).
- Provider summary chip on the type-card header now truncates with
  ellipsis at `max-w-[55vw]` on mobile / `max-w-[280px]` on sm+, so
  long provider · model strings don't push the header to a third line
  on narrow viewports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chore: cleanup loose ends from PR review
All checks were successful
qa / dockerfile (pull_request) Successful in 5s
qa / qa (pull_request) Successful in 1m54s
6e04453d50
Three minor fixes:

1. DeleteAgentDialog now uses the new `tone="error"` Button modifier
   (was raw `border-error text-error` className override). Also wires
   `loading={mutation.isPending}` so the spinner replaces the text.

2. ProviderSection up/down buttons migrate from raw Unicode arrows to
   lucide `ArrowUp` / `ArrowDown` via the foundation Button. Closes the
   last spot where the lucide migration was inconsistent.

3. `model_override` field removed from the GET /agents response shape
   (was always null), from `AgentsListEntry` on the web side, and from
   the e2e fixture. PATCH response now forces `model: null` so legacy
   rows with stale non-null `model` columns don't leak through. The
   column itself is left in the DB — it's dead but harmless, and a
   migration to drop it is out of scope.

Tests updated to assert `model_override` is undefined on the response.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three drawers in agents.tsx duplicated the same scaffold (~50 LOC × 3):
leaving state, triggerClose with setTimeout, ESC listener, backdrop,
<aside> shell, header with close button. Extract to a reusable
component and migrate.

A11y wins from the audit, applied once in the primitive:

- role="dialog" + aria-modal="true" + aria-labelledby on the header
  heading (was a plain <aside> — screen readers got no dialog cue)
- Focus trap: Tab / Shift+Tab cycle inside the drawer
- Focus return: previously-focused element regains focus on close
  (captured at open via document.activeElement)
- ESC + backdrop click already covered, kept

Migrate TypeEditorDrawer (900px), InstanceHistoryDrawer (640px),
InstanceAdvancedDrawer (640px). Width is per-drawer via a `width`
prop; default 640.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four findings from the a11y audit, all touching the agents page or
shared CSS.

1. Type-card header — drop the absolute-overlay button and convert
   the disclosure region to a real <button>. The previous layout had
   a position:absolute toggle covering the row with sibling
   pointer-events-none content; screen readers announced only the
   aria-label and skipped the visible chip content. The new layout
   has the chevron+name+chips+count cluster as the actual button,
   sibling Edit-type Button outside it. Tab focus + visible focus
   ring + correct SR reading order.

2. Save status live region — wrap the Saving…/Saved/Save failed
   indicator in role="status" aria-live="polite" so SR users hear
   autosave failures. The error variant is now also visible on
   mobile (was hidden:sm:flex previously); Saving and Saved stay
   sm-only because they're transient cosmetic feedback.

3. Tier glyph + cooldown — circled-digit Unicode (①②③) is read
   inconsistently across SR engines. Add an sr-only "tier N" span
   alongside the visible glyph, mark the glyph aria-hidden. Cooldown
   "(5m)" gets an sr-only "5 minutes until retry" longform.

4. prefers-reduced-motion — global @media block in index.css clamps
   every animation/transition to ~0ms so vestibular users don't get
   slide-in/spin/pulse motion. Required !important to win against
   per-element animation utilities; biome-ignore documents why.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(a11y): form field semantics in AgentEditor + AddAgentTypeWizard
All checks were successful
qa / dockerfile (pull_request) Successful in 5s
qa / qa (pull_request) Successful in 1m56s
b72eebb45f
Form-related WCAG findings from the audit.

AgentEditor:
- Field wrapper changes from <div> to <label> so screen readers
  programmatically associate the visible label with the contained
  input. No htmlFor/id needed — the input is a descendant.
- Name input gets aria-invalid + aria-describedby pointing at the
  error <p> when an error is present, so SR users hear the validation
  message after submit failure.

AddAgentTypeWizard (Step2):
- Name + Forgejo-user inputs get aria-invalid + aria-describedby
  pointing at their respective inline error spans (the spans now
  carry stable IDs).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(a11y): bump --ch-color-text-dim to clear WCAG 2.1 AA
All checks were successful
qa / dockerfile (pull_request) Successful in 6s
qa / qa (pull_request) Successful in 1m54s
9b61e4aa32
Token-only change. The previous values failed AA contrast for body
text:

- Dark: #565f89 on #1a1b26 — ~4.0:1 (fails 4.5:1)
- Light: #9aa5ce on #e9ecf2 — ~2.5:1 (fails 4.5:1)

Bumped both. New values:

- Dark:  #7d8aae (~4.9:1 on #1a1b26)
- Light: #5d6a92 (~4.7:1 on #e9ecf2)

`text-dim` is the most-faded text tier — used for "any" placeholders,
"No data."/"Loading…" hints, "skipped"/"pending" stage glyphs and
115 other call sites across the web app. Single-token change, no
component edits needed.

In the light theme, "dim" goes darker than "muted" because dimming a
foreground on a light surface means moving toward the bg, which
sacrifices contrast. The new value is less saturated than text-muted
so it still reads as visually faded.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
charles deleted branch feat/agents-history-drawer 2026-04-29 22:49:58 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!555
No description provided.