Skip to content

Surfaces v2: slotted schema, opinionated action rendering, design contract - #1041

Merged
selfcontained merged 7 commits into
mainfrom
agt_b6425926e982/agent-26e982
Sep 3, 2026
Merged

Surfaces v2: slotted schema, opinionated action rendering, design contract#1041
selfcontained merged 7 commits into
mainfrom
agt_b6425926e982/agent-26e982

Conversation

@selfcontained

Copy link
Copy Markdown
Owner

Summary

Breaking pre-release redesign of agent surfaces, driven by a 6-surface UX audit (screenshots + critique + design proposal in the agent's media). The core move: placement and visual weight are now renderer-owned — agents supply meaning, the renderer owns the design.

Schema (v2, breaking — feature is unreleased)

  • Document slots: optional header (status + progress strip, always first), blocks, optional footer.actions (the surface's verbs).
  • Standalone actions block removed — actions live only in slots: document footer, section.actions footer, and per-item/row actions[] (replacing singular action). Bad placement is now inexpressible.
  • text gains tone (callout rendering); form submit loses its style knob (always primary); table align:"center" removed; ≤3 primary table columns enforced; block ids header/footer reserved.
  • Migration 0044 adds header/footer jsonb columns. Stored v1 documents render a "recreate this tab" notice (no crash), refuse interactions, and upgrade in place via a full v2 blocks replacement.

Renderer

  • Action policy: slot actions render as a compact split button — primary visible, the rest in an overflow menu, destructive verbs de-emphasized (ghost + border, always last, never gaining area). Single item/row actions are compact bordered chips on the title row; 2+ collapse into a per-item ⋯ menu.
  • Tone rescale: healthy states render dim, neutral badges are plain text — color budget goes to exceptions.
  • Tables: 2-column action-free tables render as key/value stat lists; the inset border box is gone (restores the left alignment spine); the dead md: responsive branch is removed (the rail is a fixed 400px).
  • Typographic ramp: small-caps section/container labels < item text < block titles; unified collapsed/expanded section vocabulary; hairline dividers + wider inter-block rhythm.
  • Renderer-owned timestamp formatting (relative <7d, absolute on hover) and enum-label humanization (IN_PROGRESS → "In progress"); progress bars neutral by default with an inline percentage; form submits full-width primary, muted required markers, trailing checkbox runs grouped as an attestation gate.

Guidance

  • dispatch_surface_create/update tool descriptions carry a thin design contract; the surfaces plugin skill is rewritten for v2 (slots, contract, re-authored recipes).
  • Seed gallery, server tests, web tests, and e2e fixtures re-authored under v2.

Validation

  • pnpm run check, pnpm run finalize:web clean; full unit suite passes; full e2e: 186 passed / 12 skipped (including the 4 re-authored surfaces specs).
  • The six audit baselines were re-authored under v2 (all passed schema validation first try), screenshotted on an isolated dev stack, and compared before/after by a codex visual-QA agent — verdict: clear improvement (calmer actions, scannable tables, real hierarchy). Screenshots + the comparison report are in the agent's media sidebar.

🤖 Generated with Claude Code

selfcontained and others added 7 commits September 2, 2026 22:19
…tract

Breaking pre-release schema change (v1 docs render a recreate notice):
- Document slots: header (status+progress strip), footer.actions; section
  actions footer; per-item/row actions[] (1 inline ghost, 2+ kebab menu).
- Standalone actions block removed — placement is renderer-owned.
- Action policy: split button with overflow, destructive de-emphasized
  (ghost-destructive, never gains area), compact h-7/h-6 buttons.
- Tone rescale (healthy dim, neutral plain text), typographic ramp,
  3-primary-column table budget, 2-col tables render as key/value,
  timestamp + enum-label normalization, form submit full-width primary,
  muted required markers, attestation checkbox grouping, text tone callouts.
- Tool descriptions carry a thin design contract; surfaces skill rewritten;
  seed gallery + tests + e2e re-authored under v2; migration 0044 adds
  header/footer columns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…line link arrow

- Single item/row actions render as compact bordered chips (they read as
  buttons again, per feedback) instead of bare ghost text.
- A standalone destructive button keeps a visible border.
- List item link arrows flow inline after the last word instead of
  wrapping to their own line.
- Checkbox labels use the muted required marker like other fields.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ance

- Block-header counts render as "(4)" on the title line so they read as
  intentional rather than a stray digit.
- Skill's table guidance: keep a relative-time column primary when
  recency matters (a history with no time cue is worse than one fewer
  column).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Nested action menus claim Escape (preventDefault + controlled close) and
  GlassSidebar ignores claimed Escapes, so the first press dismisses only
  the menu in the mobile drawer (#2479); e2e regression covers it.
- Confirm dialogs opened from an overflow menu item return focus to the
  split trigger via a returnFocusRef instead of falling to <body> (#2480);
  e2e asserts cancel refocuses the trigger.
- Menu items get the coarse-pointer 44px minimum (#2481).
- Authored-disabled overflow actions stay focusable via aria-disabled with
  their reason rendered inside the item and aria-describedby wired (#2482).
- humanizeLabel only rewrites separator-bearing tokens; plain all-caps
  values (API, AWS, NASA) pass through, with unit regression tests (#2483).
- Checkbox, radiogroup, select trigger, and multi-select group carry
  aria-required now that the visual asterisk is aria-hidden (#2484).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The non-claude harvest test walked the developer's actual codex sessions
dir (gigabytes of rollout files on a busy machine), blowing the 30s test
timeout on the self-hosted runner. Point CODEX_HOME at an empty temp dir
for the test's duration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- dispatch_surface_list projection strips header/footer along with blocks
  so list stays the tab-summary contract; MCP assertion added (#2538).
- Section-action interaction snapshots omit the section's descendant tree
  (up to 100 nested blocks) to stop repeatable clicks amplifying storage;
  test asserts the snapshot carries no blocks (#2539).
- Escape e2e regression test waits for focus inside the menu before
  dismissing, closing the mount race that flaked under load (#2540).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Also imports AgentPin/AgentStatus in apps/web/src/components/app/types.ts:
the file used them locally but only re-exported them (a re-export creates
no local binding), so check:web failed on main after #1044 — pushes to
main don't run CI, which is how it slipped through.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@selfcontained
selfcontained merged commit 6f3551f into main Sep 3, 2026
1 check passed
@selfcontained
selfcontained deleted the agt_b6425926e982/agent-26e982 branch September 3, 2026 15:06
selfcontained added a commit that referenced this pull request Sep 4, 2026
* test(server): isolate the vitest suite from the host's ~/.codex

The Codex token harvester resolves its rollout directory from CODEX_HOME and
falls back to the host's real ~/.codex (src/agents/codex-sessions.ts:11). Any
server test that harvests a codex agent then recursively walks that tree and
reads the head of every rollout file. On this machine that is 5.7 GB / ~11.7k
files, and the self-hosted CI runner is the same machine — so it is not a
"works on CI" situation.

PR #1041 patched the one test whose 30s budget it blew by swapping CODEX_HOME
inside that test, but stopAgent harvests fire-and-forget (manager.ts:1173), so
codex agents stopped by other tests still walked the real directory. The file
was still spending ~24s on host I/O.

Point CODEX_HOME at an empty directory for the whole server suite via
vitest.config.ts `test.env`, and drop the now-redundant per-test override.

apps/server/test/db/agent-manager.test.ts alone: 55.5s with one 30s timeout
before, 7.5s and 129/129 after. Full server suite: 3130 passed in 40.4s.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(server): use mkdtempSync for the suite's isolated CODEX_HOME

Review 1107 item 2612: a fixed name under os.tmpdir() is not guaranteed to be
empty — mkdirSync(..., { recursive: true }) silently reuses an existing
directory or follows an existing symlink, so a stale or planted `sessions` tree
would put host-controlled data back in the harvester's path.

mkdtempSync creates a fresh 0700 directory with a random suffix on every run, so
it can never adopt an existing tree, and concurrent runs no longer share one.
Removed on process exit.

Verified: zero `dispatch-server-vitest-codex-*` directories in TMPDIR before and
after a run; agent-manager.test.ts 129/129 in 8.1s; full server suite 3130
passed in 32.2s; pnpm run check clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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