Skip to content

Feat/webui - #1607

Open
jtenniswood wants to merge 171 commits into
mainfrom
feat/webui
Open

jtenniswood wants to merge 171 commits into
mainfrom
feat/webui

Conversation

@jtenniswood

Copy link
Copy Markdown
Contributor

Summary

Development stage

  • Plan / Interface — Bounded/Architectural behavioral and exact-interface contract; no implementation
  • Implementation — based on an approved, merged Plan / Interface PR
  • Combined — compact one-task Bounded/Architectural exception; no separate plan PR,
    and the in-PR plan declares **Expected tasks:** 1, a non-placeholder
    **Combined rationale:**, and no runtime/public/operator/persistence/trust-boundary
    interface change (None — rationale; workflow-only meta-changes may review process
    docs/skills here)
  • Spike / Routine — acceptance-plan spine exempt; Spike evidence does not ship as-is

Contract linkage

  • Work classification: Spike / Routine / Bounded / Architectural
  • Classification rationale:
  • Decision record: ADR link / None with rationale
  • Human waiver of spine: No / Yes —
  • Acceptance plan:
  • Human decisions resolved and recorded: Yes / N/A with rationale
  • Plan / Interface PR:
  • Approved commit baseline:
  • Combined/exemption rationale:

Interface conformance

Issue relationship

Relates to #

Type of change

  • Behavioral/interface plan
  • Bug fix
  • New feature
  • Refactoring (no behavior change)
  • Dependency update
  • Documentation/process
  • Other (describe):

Test plan

Baseline checks

  • Acceptance-plan checker
  • Linting (task lint)
  • Offline test suite (task test)
  • Offline demo (go run ./cmd/mecademo)
  • Markdown changed: docs generation/link checks (task docs)
  • User docs/user-facing behavior changed: site build (task site:build)
  • Guarded engine API affected: compatibility check (task api:check)
  • Intentional engine API change: task api:update + engine/CHANGELOG.md
  • Landed plan: strict acceptance trace (task ac-trace-strict)
  • Final implementation review: /panel-review

Changes

File Change

User-facing change

Special notes for reviewers

jtenniswood and others added 30 commits August 18, 2026 14:11
Vendor drop of the Atrium workspace UI from
stacklok/enterprise-ui-prototypes @ claude/atrium-ui-updates (649bc1efb91f889e117c79f4189d8abba34c22d4),
filtered before import so prototype residue never enters this repo's
history. Every imported file is byte-identical to its source.

Excluded at import (413 of 572 tracked files):
- auth stack (better-auth/pg/OIDC), signin flow, permissions context
- legacy ai-sdk assistant sidebar, /api/chat, MCP client wiring
- MSW mock server, mock OIDC/registry/user-management routes, hey-api
  generated clients and their OpenAPI specs
- Atrium demo data (features/agent/mock-data.ts, skills fixtures):
  Studio is daemon-only; probe failure renders offline, never demo data
- disk-touching harness routes (skills/agents authoring deferred to a
  follow-up; transcript route superseded by the daemon transcript API)
- prototype deploy/repo infra (Docker, helm, GitHub workflows, husky,
  renovate, pnpm lockfile/workspace) and residue tests/e2e specs

The app does not build at this commit; wiring lands in the commits that
follow. The complete filter manifest is recorded in the pull request.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Eight imported files carried `SPDX-License-Identifier: Proprietary`
headers — stray artifacts of the source repository's
enterprise-platform sync that contradicted its own Apache-2.0 LICENSE.
This repository has no SPDX header convention of its own; the headers
are corrected to Apache-2.0 — the license the source repository
actually grants this content under — rather than dropped, so the
imported files keep their provenance visible.

Kept separate from the vendor drop so the relicensing is auditable on
its own. A CI grep gate (added with the studio workflow later in this
series) rejects Proprietary headers so they cannot regress.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Turn the imported prototype into mecatl-studio:

- package.json: rename, prune every residue dependency (ai-sdk stack,
  better-auth/pg, msw/express/oidc-provider, hey-api, dexie, redoc,
  archiver/tar-stream, ...), npm with a regenerated package-lock.json,
  pnpm security overrides translated to npm `overrides` (kept only for
  packages still present in the tree), engines >=22.13, .nvmrc 22.22.0,
  drop husky/lint-staged (git hooks in a Node subdir of a Go monorepo
  would hijack .git/hooks for every contributor; CI is the enforcement
  point).
- next.config.ts: keep the strict security headers; drop standalone
  output, mock rewrites, tracing includes, and the /api/proxy CSP
  carve-out (all served the deleted deployment).
- Flatten the (authenticated) route group: its only job was
  verifySession. src/app/workspace/layout.tsx keeps the shell without
  PermissionsProvider/assistant/config-server gates.
- client-providers: drop the connector-status demo context; keep
  next-themes + Toaster + the Suspense boundary useSearchParams needs.
- Root layout/metadata rebranded to Mecatl Studio; mock-scenario dev
  panel unmounted.
- vitest/knip/tsconfig/biome/playwright configs trimmed of deleted
  paths (MSW setup, auth env pins, @api/@Mocks aliases, generated-file
  overrides); .npmrc (pnpm-only settings) removed.
- src/lib/utils.ts reduced to cn(): the other helpers parsed ToolHive
  registry metadata via the deleted generated types (their test file
  covered only those helpers and goes with them).
- Restore three files the import filter over-matched, byte-identical
  to the source branch: workspace/_components/{chat-input.tsx,
  composer-mentions.ts,resize-handle.tsx} and
  src/components/brand-logo.tsx (+ test).
- TEMPORARY typed-empty shims for the excluded demo fixtures
  (features/agent/mock-data.ts, skills/_data/skills.ts) keep the
  not-yet-converted hooks compiling; the daemon-only hook rewrite
  deletes both. The mock-data-bound e2e spec and search-data test are
  removed; their daemon-backed replacements land with the test suite
  later in this series.

next build, tsc --noEmit, biome check, and vitest (44 tests) are green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the prototype's permissive /api/harness* proxies with the
Studio server tier from feat/studio-module (PR #548), carried
near-verbatim:

- src/lib/server-proxy.ts — Host/Origin/CSRF trust gate, header
  allowlists in both directions, server-side bearer injection in
  external mode, managed/external mode switch (MECATL_BASE_URL
  presence), controller mutations tagged with the server-set
  x-mecatl-studio-request header, daemon-unreachable mapped to a
  friendly 503. New here: workspace injection for POST /v1/sessions
  and /v1/teams (managed: from controller /status; external: from
  MECATL_WORKSPACE), adopted from the prototype's proxy so the
  browser never learns or chooses the workspace path.
- src/lib/controller-security.mjs — verbatim: controller CSRF +
  DNS-rebinding policy and MCP gateway egress validation.
- scripts/local-controller.mjs — verbatim (import path only): the
  managed-mode supervisor. Random loopback daemon port, generated
  bearer, restart/rollback queue, model-router and operator settings,
  MCP gateway OAuth with the unguessable loopback proxy.
- scripts/dev-local.mjs — verbatim: process orchestration and the
  managed/external switch; npm scripts now match the old studio's
  (dev, dev:web, mecatl, start --production).

Routes are the old studio names: /api/mecatl/[...path] and
/api/mecatl-control/[...path]; the browser client only changes its two
base-path constants. In external mode /api/mecatl-control synthesizes
/status and answers 409 for every other control path.

Env inventory is the old studio's exactly (MECATL_BASE_URL,
MECATL_AUTH_TOKEN, MECATL_WORKSPACE, MECATL_STUDIO_PUBLIC_ORIGIN,
MECATL_STUDIO_ORIGINS, MECATL_STUDIO_PROVIDER,
MECATL_ALLOW_INSECURE_LOOPBACK_MCP); the prototype's MECATL_URL,
MECATL_CONTROL_URL, MECATL_ALLOW_REMOTE, MECATL_SKILLS_DIR and
MECATL_STORE_DIR are gone, documented in .env.example.

The client functions still pointing at the removed disk routes
(/api/harness-skills, /api/harness-agents) are pruned with the hook
rewrites later in this series.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One wire seam under src/lib/protocol/, the only reader of raw daemon
JSON. The base is the old studio's tested decoders; the session and
schedule halves re-land the closed PRs #615 and #616 designs; the
event translation replaces the prototype's looser inline translate().

- events.ts: parseMecatlEvent (structural, throw-on-missing-type) +
  translateEvent. The terminal `result` frame now always reaches the
  UI: stop, error text, and permanence ride a run_result StreamEvent —
  a failed turn must render as failed, never as an empty success.
  Usage carries all five token fields. permission.retract withdraws
  its ask. subagent.start / team.start roster / parallel.branch starts
  become delegation badges; advisory kinds (tool.progress, compaction,
  provider.route, recover_notice, no_progress) become one-line
  notices; and an UNKNOWN event kind is surfaced as "not rendered
  yet", never silently dropped.
- sessions.ts (#615): SessionSummary with capability-driven actions
  (omitted capability = denial, closed per-action reasons,
  inspect_only_kind as the one not-a-chat reason), bounded-cursor
  inventory pages, and the authoritative message-level transcript
  decoder.
- schedules.ts (#616): full spec decode (protojson AND stdlib-JSON
  shapes for timestamps, durations, enums), fire-stage inference with
  the "pending" claim sentinel, fire-history decoding (newest first,
  in-flight keyed off the absent stop), and encodeScheduleSpec — the
  protojson request builder with the carried-spec round-trip, because
  PUT replaces the whole spec and responses can never be echoed back
  as requests.

client.ts rides the seam: streamHarnessPrompt gains the 120s idle
timeout and the saw-result guard (a stream that closes without a
terminal frame throws); approvals send the daemon's three-way verdict
(allow_once / allow_always / deny); new wrappers cover the session
inventory walk, rename (adopting the clamped title echo), delete,
transcript, schedule rows/fires/save, and slash-command discovery
(workspace query injected by the proxy, like session bodies).

26 decoder/translation tests pin the invariants, including the
protojson-request/stdlib-response asymmetry and the carried-field
round-trip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Delete the demo layer entirely: the mock-data shim is gone, and no hook
fabricates content when the daemon is away. An unreachable daemon is now
a rendered state, never demo data.

- RuntimeStatusProvider (features/agent/runtime-status.tsx): one
  connection authority polling the daemon and controller every 5s,
  replacing eleven independent one-shot probes that could disagree and
  never noticed a daemon dying after mount. Offline renders a shell
  banner naming the fix, with Retry; surfaces gate their loads on it
  and reload on reconnect. It also refreshes the composer capabilities
  on every reconnect.
- use-agent-sessions: the chat list is the daemon's session store.
  Bounded cursor walk + 20s poll; rows removed only when a COMPLETE
  walk proves them gone; non-chat rows (subagents, team members,
  scheduled fires) filtered by the decoder's inspect_only_kind rule;
  rename optimistic with rollback, adopting the daemon's clamped title
  echo; delete daemon-backed with the 404-only-removal rule; action
  eligibility read from row capabilities, never re-derived.
- use-agent-chat: the sidebar id IS the daemon session id — the lazy
  UI-id→daemon-id map is gone. Opening a chat rehydrates from the
  authoritative transcript endpoint (incomplete transcripts say so).
  A null id is a draft whose session is minted on first send. Handles
  the full translated stream: failed turns render failed (permanent
  errors say so), permission retractions withdraw their ask, notices
  and delegation badges accumulate on the turn, usage keeps all five
  token counts, and retryLast() resends the last prompt after a
  failure.
- use-agent-cron: schedule registry only, via the protocol decoders;
  "scheduler not wired" (the daemon's own error) is distinct from an
  empty registry; every action re-reads durable state; quick-create
  builds a plan-mode, non-mutating spec; full rows (mode, mutating,
  workspace, carried spec) exposed for the schedules surface.
- use-agent-memory: user-model index with size/sha metadata; a daemon
  running --no-user-model is a distinct disabled state; the mock edit
  path is gone (memory is read-only by construction).
- use-agent-roster: the daemon's resolved subagent inventory.
- use-agent-projects: placeholder — the daemon has no project concept;
  the grouping UI goes with the sidebar rework.
- composer-capabilities: @-mentions from GET /v1/agents and /-commands
  from GET /v1/commands (workspace query injected by the proxy, which
  now also injects workspace into schedule creation bodies).
- atrium-search-data: fixture index removed; the live index lands with
  the surface wiring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Chats — the flat server-backed workspace. The project/agent grouping and
every demo flow are gone; the sidebar is one recency-bucketed list of
daemon sessions (Today / This week / Earlier) with rename/delete gated
by row capabilities and the daemon's refusal reasons shown, never
re-derived. "New chat" is a draft route; the daemon session is minted on
first send and the URL adopts its id. Failed turns render as destructive
alerts (never empty successes), notices and delegation badges ride the
turn, approvals offer the daemon's real three-way verdict, an error
strip offers Retry, and the header shows live token usage.

Scheduled — full authoring and audit. The create/edit dialogs share one
form: cron + IANA timezone + max fires XOR one-shot + retry, and an
explicit "allow file and shell writes" opt-in that couples mutating with
a write-capable mode so the invalid pairing is unconstructible; edits
seed from the stored row and round-trip its carried spec (PUT replaces
the whole spec). Rows carry mode/mutating/workspace/owner badges and
fire-stage indicators; the detail page shows the real fire log (newest
first, in-flight tracked, refusals verbatim) with per-fire transcripts;
"not wired" is distinguished from "empty" using the daemon's own words.

Skills — the resolved inventory, read-only: provenance badges (learned
vs workspace), and an honest note where SKILL.md content used to be —
the client tier has no body-read endpoint.

Memory — three honest states (disabled with the daemon's reason, empty,
populated), store footprint (bytes + sha prefix), no editing
affordances, and no fabricated timestamps.

Settings — appearance/notifications plus the runtime sections: provider
status (read-only; credentials never enter Studio), the full model
router editor with the operator-settings lock, and MCP gateway connect
with a user-entered, controller-validated URL (bearer or OAuth with
status polling). External mode renders all three as owned by the
deployment.

Global search indexes live data (sessions, schedules, skills, memory
keys) built per palette open; transcripts are not indexed.

Rides a new hermetic server-tier suite (tests/rendered-html.test.mjs):
a real production build in external mode against a fake recording
daemon proves bearer injection, workspace injection, the CSRF 403, the
external-mode 409 policy, and that an unreachable daemon is a friendly
503 — plus the controller CSRF/DNS-rebinding truth table and gateway
egress validation. Dead prototype residue flagged by knip is deleted
(fixture data, orphaned components, unused exports, date-fns/zod).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Five Playwright smokes drive the production build in external mode
against a fixture mecated (tests/e2e/fixture-daemon.mjs) — a test
double behind the API seam, speaking the daemon's real wire shapes
(stdlib-JSON responses, SSE prompt relay), which is not the UI-level
demo fallback Studio forbids. The specs assert daemon-sourced renders:
the chat list and rehydrated transcript, schedule posture badges, the
skill inventory, the read-only user model, and external mode marking
runtime settings as deployment-owned.

The suite immediately earned its keep: the browser client's base path
had silently lost its /v1 prefix in the proxy graft (the prototype's
proxy used to rewrite /api/harness/* onto /v1/*; the grafted tier is
transparent), so every daemon call 404'd in a real browser while unit
and server-tier suites — which speak full paths — stayed green. The
prefix now lives in the client's own URLs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- .github/workflows/studio.yml: path-scoped to studio/**; SHA-pinned
  actions; Node from studio/.nvmrc with the npm cache; installs run
  --ignore-scripts (nothing in the tree needs install scripts, so the
  supply-chain surface stays off in CI). Job "checks" runs biome,
  tsc, knip, the vitest suite, the production build plus the hermetic
  server-tier suite, npm audit --audit-level=high, and a grep gate
  rejecting Proprietary SPDX headers. A separate "e2e" job runs the
  Playwright fixture-daemon smokes so a browser-infra flake never
  masks the checks.
- dependabot: the repo's first npm entry, for /studio (weekly,
  minor+patch grouped, matching the gomod grouping style).
- Taskfiles: the root include gains the studio namespace; the studio
  Taskfile carries the supervisor-aware dev/stop/restart/status tasks
  (stop kills the controller first so it tears down its mecated
  instead of orphaning it) with test/lint retargeted to the new stack.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- ADR 0228 — Studio: the Atrium workspace as mecatl's daemon-only web
  client. The module boundary (a Node module, never a Go module), the
  two pure deployment modes, the daemon-only rule (offline is a
  rendered state, never demo data), the single typed wire seam, the
  inherited security posture, the deliberate non-features, and the
  toolchain. Subsumes the unmerged ADR drafts from PR #548.
- ADR 0229 — Studio's chat list is the daemon's session store: the
  sidebar id IS the daemon session id, complete-walk removal, clamped
  rename echoes, capability-driven actions, draft-on-first-send, and
  the stated costs (mtime reordering, synthetic transcript turns, no
  live re-attach over HTTP). Re-lands the design from PR #615.
- AGENTS.md gains the studio component bullet with the same-PR
  wire-break rule; docs/architecture.md gains the Studio client
  section; PRODUCTION-READINESS gains the Studio row with its open
  deferrals; user-docs/what-you-get/studio.md is the operator guide;
  studio/CLAUDE.md carries the rules-that-have-teeth list, each named
  to its backing test. llms.txt regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI's Node 22 ships npm 10, which rejected the lockfile a local npm 11
wrote (`npm ci`: missing transitive typescript peer). The lockfile is
regenerated with npm@10.9.4 — the toolchain CI actually runs — and
`npm ci --ignore-scripts` is verified against it.

The audit gate then flagged the postcss vendored inside next@16.2.11
(GHSA-qx2v-qp2m-jg93 and friends, high): fixed upstream in
next@16.3.1, the version the previous studio module already ran.
Bumped; the react-compiler build, unit, hermetic, and e2e suites all
pass on it, and `npm audit --audit-level=high` exits clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The freshness gate regenerates llms.txt and diffs; the Studio row added
to PRODUCTION-READINESS after the previous regeneration shifted the
link graph.

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

Settings becomes five subpages behind a left secondary nav (Appearance,
Notifications, Provider, Model router, MCP gateway) instead of one long
scroll, each still backed by the same shared useHarnessRuntime instance
so the runtime sections share one status/busy/error channel.

New browser-local identity preferences (src/lib/profile-preferences.ts —
cosmetic only, nothing here has a daemon concept to write back to):
- Agent display name (defaults to "Mecatl", was hardcoded "Assistant"
  everywhere), editable from Settings → Appearance.
- User avatar: a person-icon placeholder by default, replacing the
  prototype's broken remote portrait (blocked by our own CSP — this was
  also silently killing the whole message list, since the crash inside
  <UserAvatar> was swallowed by an error boundary with the transcript
  rehydrated behind it). Upload stores a small local data URL; no
  daemon endpoint exists or is implied.
Both render in a new two-group "Profile" card ("You" / "Agent").

Provider settings gain real visibility without adding a credential
path: the controller now line-scans auth.yaml for provider block NAMES
only (never values, never a full YAML parse) and reports them plus
which one MECATL_STUDIO_PROVIDER currently selects. The Provider page
lists them, marks the active one, and states the exact file + env var
to add or switch — credentials still never cross the browser/controller
boundary (ADR 0228), so no add/remove-provider UI was built.

provider.route events move from an advisory notice to silent: routing
detail ("Azure", a raw provider name) was leaking under assistant
turns as noise with no actionable content.

Empty-state polish: Skills and Scheduled get an icon, human copy, and
(Scheduled) a working create CTA inline, replacing a bare line of
muted text. Memory drops its footer store-footprint line, its two
explainer paragraphs on the list and detail pages, and the Refresh
button (the runtime-status provider already reloads it on reconnect).

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

The settings-subpages commit carried a package-lock.json regenerated by
a local npm 11, which deduped away a transitive typescript entry npm 10
requires — the same desync fixed once before. Regenerated with
npm@10.9.4 and pinned `packageManager: "npm@10.9.4"` so corepack-aware
setups stop reintroducing it.

The same commit moved the runtime settings sections to their own
subpages, which orphaned the external-mode e2e assertion still visiting
/workspace/settings; it now targets /workspace/settings/provider.

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

Implements the new workspace shell from Figma (node 4:2): the left rail
is gone. A 64px top navigation bar sits on a fixed dark-green radial
gradient — white Stacklok mark, the five surfaces as a pill nav (active
route renders as a light-sage pill with icon + label; the rest are
icon-only with tooltips), and the ⌘K global search styled as the
design's search field. All five surfaces render inside one rounded
20px card that follows the theme; the gradient shell is identical in
light and dark.

The chat workspace flips: conversation on the left (title in a 64px
card header, content and composer on the design's 768px measure),
"Session List" on the right — recency-bucketed daemon sessions with
the design's row treatment (3px brand accent + tint on the active row,
muted relative times, a pulsing brand dot for running sessions) and an
AGENTS section fed by the daemon's real GET /v1/agents roster
(clicking one starts a new draft). The resize handle and collapse,
keyboard shortcuts, capability-gated rename/delete, and the draft
mint flow all carry over mirrored.

Design content with no daemon backing is deliberately not fabricated:
the mock PROJECTS folders render as recency groups per the base PR's
decision, and the "New project" button, notification bell, and
per-agent chat nesting are dropped.

The old shell (console-shell, shell, navbar, nav-drawer, rail sidebar)
and its orphaned hooks are deleted; ShortcutsProvider moved into the
workspace layout; nav config consolidated into one nav-items module.

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

The conversation and composer keep the 768px measure but hug the pane's
left edge instead of centering; the session list's resize clamp rises
from 500px to 720px.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sending from a draft minted the daemon session and router.replace()d
/workspace/chat to /workspace/chat/<id> — but moving the optional
catch-all from zero segments to one changes the route shape, so Next
remounts the page. The remount replaced the chat hook instance
mid-stream: tokens rendered into unmounted state and the pane sat
empty until a reload (the prototype's own comments warned about this
remount; its mock module-stores existed to survive it).

The URL now updates with native history.replaceState, which the App
Router syncs without remounting, so the in-flight stream keeps its
state and renders live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- A brand-colored spinner sits left of the chat title while a response
  streams.
- The session list and the thread/file side panels now share one
  persisted resize setting (usePanelWidth, localStorage-backed,
  200-720px): resizing either carries to the other and survives
  reloads. The side panel previously reset to a hardcoded width every
  open.
- Approval action badges key by position: parsed actions can repeat or
  parse without a verb, which produced duplicate React keys.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dark mode deepens each stop of the green radial gradient so the shell
recedes behind the dark card instead of outglowing it; light mode keeps
the design's original stops.

A draft chat now renders the same 64px title bar as an open chat
("New chat" + the sidebar toggle) instead of a bare centered composer
with a floating button.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
task studio:dev re-runs the fingerprinted install task whenever
package.json changes — with the system npm on PATH. An npm 11 there
rewrites the lockfile in a shape npm 10 (what CI's Node 22 ships)
rejects, which has now broken npm ci twice. The install command runs
through npx npm@10.9.4, matching the packageManager pin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A new browser-local preference (Settings → Appearance → Session list
position) moves the session list to either side of the chat. The
docked panel, the compact overlay, the resize handle's drag direction,
and the panel-toggle icons all follow the chosen side; the thread and
document panels stay on the right regardless. The Appearance page's
theme picker is regrouped alongside the new control as labeled pill
rows.

Also aligns the resize handle's default max width (720px) with the
shared panel-width store's clamp — dragging previously stopped at the
handle's stale 500px cap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When the session list is on the left, its toggle renders leading in the
chat header (before the generating spinner and title); when right, it
stays in the trailing action cluster.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The approval badges' index-in-key tripped noArrayIndexKey at error
level in CI (the ids are now precomputed per row, the pattern the
message bubbles already use), and the side panel's drag effect needed
its stable setWidth in the dependency array.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ns, working tap-to-open

The shell's CSS gates used Tailwind's sm: (640px) while the JS hooks
call <500px mobile; they now agree on 500px (min-[500px]: variants).
The gradient edge margin halves below the breakpoint (10px vs 20px).

Tapping a chat row on a phone did nothing: useIsMobile is undefined on
the first render, so sidebarOpen initialised as if on desktop, and the
row's router.push changed the optional catch-all's segment count —
remounting the page and re-running that init, so the list rendered
straight back over the chat. Selection now uses native
history.pushState (no remount, the draft-mint precedent) and a
measured-mobile effect closes the list over a selected chat on mount.

The search palette becomes a full page below the breakpoint — the
on-screen keyboard eats half the viewport, so a floating dialog leaves
no room for results.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Web app manifest (standalone display, /workspace/chat start URL, brand
theme/background, 192/512/maskable icons generated from the logo mark
on a solid brand ground), apple-touch-icon + appleWebApp metadata, and
a viewport export with media-queried theme colors matching the shell
gradient plus viewportFit cover for the notch.

Deliberately no service worker: Studio is a local daemon client, so
offline caching would lie about live state — and installability no
longer requires one on Chrome and never did on iOS.

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

Bottom sheets follow mobile conventions: full-bleed with square
corners, no X button, a centered grab handle, iOS safe-area padding,
and dependency-free drag-to-close (handle or unscrolled body; >25%
height or a flick dismisses, taps and scrolls are untouched).

Threads (and file/canvas panels) now work on mobile: the same desktop
panels render maximized inside a 94dvh bottom sheet with their window
controls hidden — the grab handle owns dismissal — so the thread
composer sits above the on-screen keyboard.

Message actions: the hover-only reveal gains a touch path — a
horizontal left swipe on a message reveals the action row, right swipe
hides it. The Edit pencil is gone on all form factors (the daemon has
no message-edit capability; it was prototype residue).

The thread composer gains parity with the main one (model selector +
memory control), and entering a chat or thread focuses the input:
tiptap autofocus on mount plus a focusKey refocus when the open chat
changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The five surfaces move to a native-style bottom tab bar on mobile
(Material-3-style: light pill behind the active icon, small always-on
labels, safe-area padding), sitting on the same green gradient; the top
bar keeps only logo + search there. The gap between the content card
and the bar is trimmed to a sliver.

Settings gets a native mobile IA: the index renders grouped inset lists
(icon chip, label, chevron) linking to each subpage, and subpages show
a "back to Settings" header with the section's own title. Desktop keeps
the left secondary nav and lands on the first section via a client
redirect (the viewport decides, so the server can't). Profile splits
out of Appearance into its own page on both form factors, and the
section inventory is shared from one module so the nav, the drill-down
list, and the back-header can't drift.

Also drops the sheet base's gap-4 on bottom sheets — the grab handle's
own padding is the only space wanted above sheet content, which was
padding the thread title down.

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

Mobile settings subpages swap the back-link + big title for the chat
header pattern: a full-bleed h-16 bar with a back arrow and the section
title. The desktop secondary nav now pivots on the same 500px edge as
the mobile drill-down list (hidden + min-[500px]:flex, not
max-[499px]:hidden), so a fractional viewport width can never render
both at once. Tab bar labels drop leading-none — truncate clips
overflow, so the line box must clear descenders.

The profile picture no longer rejects large files: the browser
downscales any image to a 512px-longest-edge JPEG before storing it.

Settings copy diet: card descriptions and how-it-works paragraphs are
gone across all six pages. What stays is functional text only — offline
and unconfigured-state recovery notes, the operator-managed refusal,
error/notice channels, and the one warning a rule requires (writes that
restart the daemon say so).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The mobile settings index adopts the inset-grouped-list convention:
filled cards with no border, a solid per-item tinted icon square with a
white glyph, hairline dividers inset to the text edge, and tighter
46px rows.

Memory leaves the top-level nav (top bar and mobile tab bar both read
the one nav config) and becomes a settings section on both form
factors: /workspace/settings/memory with the entry detail beneath it.
Old /workspace/memory URLs redirect. Section resolution is now
prefix-aware, so a memory entry keeps the desktop nav highlight and
gets the mobile back header — whose arrow now goes up one level (entry
to list, list to index) instead of always to the index. The inline
Back button hides on mobile where that header covers it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A little gap between top-nav pills so hover and active states don't
touch. On mobile the schedules create button shortens to "New" (the
full label was truncating the page title) and the session-list
position control hides (the list is full-screen there).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keeps Studio's SDK-only bullet in AGENTS.md alongside upstream's revised perf bullet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
jtenniswood and others added 13 commits September 16, 2026 23:28
Upstream took 0345 for direct MCP onboarding; every reference follows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Eight concurrently-edited parity packages integrated, gated (biome, tsc,
knip, vitest 1813/1813, hermetic server suite) and committed as one wave.

- approvals-plan-review: a PresentPlan ask renders as a plan review (card +
  side panel, three plan verdicts) and this tab's approval auto-sends the
  harness-framed proceed prompt once the plan_approved run settles; every
  run terminal fires onRunEnded so the Mode pill re-adopts the daemon mode.
- composer-attachments-any-file-and-media-gates: any local file attaches
  (picker, drag-and-drop, "@" menu rows); text files inline as delimited
  blocks, media becomes parts gated on session/server capabilities at stage
  and send time; SDK limits pre-flighted and PromptValidationError mapped.
- delegation-fleet-status-chip: a persistent fleet chip above the context
  meter (subagents/parallel/team counts) that opens the Agents panel; the
  e2e fixture streams a subagent lifecycle.
- diag-chat-status-strip: a 28px status strip under the title (session
  handle, resolved model/route/effort, mode, server, posture badge; DEBUG
  row on an AI-debug chat); provider.route is now a translated event and a
  mid-run mode switch is held until the run ends.
- keyboard-keymap-rebinding: Settings → Keyboard remaps any dispatched
  shortcut (recorder, reserved/collision validation, per-row and global
  reset) through one effective-bindings store shared by the dispatcher, the
  help reference and the ⌘K hint.
- models-picker-filter-default: filterable provider-qualified Model picker
  with a provenance header, a browser-local default for new chats, and a
  tri-state draft pick resolved by one pure resolveDraftModel.
- sessions-clear-successor: Clear conversation from the header menu,
  ⌘⇧X and /clear through one handoff onto the daemon-minted successor,
  gated on the row's fork capability.
- settings-runtime-controller: controller-owned runtime settings
  (learning mode/sensitivity, steer opt-out, soul knobs) at
  GET|PUT /runtime-settings + POST /soul/approve, spawn flags and a merged
  CLI-tier learning file; browser client + useRuntimeSettings hook.

Integration fixes: cmdk names its input via the Command label, so the Model
picker's filter is labelled "Filter models"; the composer test follows the
tri-state Reset (null); the retry fixture advertises image capability
(its leaked once-rejection was failing a sibling test); three file-local
symbols un-exported for knip; one stray test literal conformed to its type.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…llment

- approvals-debug-mcp-attach: "Debug with AI" opens a consent dialog that
  lists the daemon's configured MCP servers (client.mcp.listSources) as
  attachable debugger servers when capabilities.debug_mcp is on; debugger
  MCP asks withhold "Always allow" and carry a Debugger MCP badge; the
  status strip names the mounted debugger tools.
- composer-paste-images-and-large-text: Ctrl/Cmd+V stages a clipboard
  image through the same attachment gate as the picker, and a large text
  paste becomes one atomic "[Pasted text #N]" chip whose full payload rides
  the send.
- diag-perf-admin-surface: Settings → Diagnostics gains a Performance card
  (loopback admin surface via a controller-probed --metrics-addr, perf MCP
  mount with the exact .mcp.json, goroutine-leak alarm, /metrics snapshot
  tiles relayed through the controller; managed mode only).
- keyboard-composer-draft-guards: Esc Esc clears an idle draft, composer
  text persists per session in sessionStorage, and a beforeunload confirm
  arms while an unsent draft exists or this tab drives a run.
- providers-definition-write: the controller writes a custom provider's
  non-secret definition into settings.yaml (POST /providers/custom) and
  removal has the TUI's two scopes (DELETE ?scope=credential|all), both
  refused while an imported operator settings file is active.
- sessions-worktree-picker: "Switch worktree…" lists the daemon's eligible
  worktrees and clears or forks the chat there using only the opaque
  worktree_selector (ADR 0291).
- settings-shortcuts-rebinding: the reserved-chord set covers the browser's
  own chords with the "Reserved by the browser" refusal, a non-⌘ binding
  warns that it won't fire while typing, and Personalize links to Keyboard.
- startup-workspace-enrollment: the "workspace services not connected"
  notice connects/retries/cancels through the daemon, opening the consent
  window on the click and never keeping the presentation URL.

Integration fixes: nine unused exports from the new modules made
module-private (one dead constant dropped) so knip is clean; the session
list's per-row kebab is now named "Options for chat: <title>" so it no
longer collides with the chat header's "Chat options" (the ambiguity broke
the Clear-conversation and Switch-worktree Playwright tests); studio
CLAUDE.md rules 2/16 extended and rule 20 added for the new surfaces.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tab title

- permissions-trust-prompt-banner: first-encounter and drift workspace trust
  banner (Trust project / Trust for this session / Not now) backed by the
  controller's trust registry (/status.trust, POST /permissions/trust and
  /permissions/trust-once, anchor-stamped grants), plus a Project trust row
  with Forget trust / Trust again in Settings -> Permissions.
- transcript-stop-reason-and-reasoning: a client-side cancel now stamps the
  `cancelled` stop chip (a stale 409 refusal leaves the daemon's terminal in
  charge), rehydrated cancelled sessions get the chip, and assistant turns
  carry a collapsible reasoning-summary disclosure with a live "Reasoning..."
  line while only reasoning has streamed.
- diag-report-and-client-build: the About card always shows Studio's own
  build stamp and server mode, degrades the daemon rows to a lookup-class
  identity row, and Copy debug info / Send to a new chat share the
  /diagnostics composer (TUI field order, byte-exact debugger fence).
- keyboard-dynamic-tab-title: document.title mirrors mecatui's window title
  (chat title + Working / Approval / Connecting / Offline), sanitized and
  clamped to 40 code points; a run's end re-walks the inventory so a fresh
  chat's title lands at the first terminal instead of the next poll.
- auth-recovery-overlay: 401/403/502 credential failures render a named
  recovery banner (Sign-in required / Session expired / Credential rejected /
  Identity provider unreachable) with a sign-in popup and a focus/postMessage
  re-probe; the SDK error adapter reads the raw RFC 9457 body off `cause`.
- sessions-pagination-status: the sidebar inventory walk merges pages as they
  land behind a "Loading chats... N so far (page P)" status with Cancel,
  Retry, bounded-walk and cancelled copy.
- settings-theming-palettes: Settings -> Personalize gains a Palette row
  (Default / Aztec / Mono / Solar) as a second axis to light/dark, applied
  before first paint by an inline boot script, persisted per browser and
  operator-pinned via BRAND_PALETTE; shell colours promoted to tokens.
- startup-trust-posture-controller: pins Studio's three restated posture
  ladders to the SDK vocabulary and proves (Playwright) that the Permissions
  page in external mode shows only the daemon-reported tier.

Integration: un-exported four module-internal symbols knip flagged
(serverIdentityText, SERVER_INFO_LOOKUPS, HarnessTrustDecision,
HarnessTrustSource). Gates: biome, tsc, knip, vitest 220 files / 2308 tests,
test:server 8/8, test:e2e 24/24.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Eight parity packages landed together; the integration gate fixed the
collision damage and the leftovers below, then ran biome, tsc, knip,
vitest (255 files), the hermetic server suite (14) and Playwright (27)
green.

- permissions-mode-switch-shortcut: Shift+Tab in the composer cycles
  Manual → Plan → Accept edits (composer-owned fixed registry row);
  pending mid-run switches show on the pill, box tint, a new header
  PermissionModeBadge and the status strip; a daemon refusal toasts.
- tool-cards-detail-tiers-diffs-changed-files: three-tier tool cards
  (activity line / expanded rows with MCP heads, arg + result summaries,
  resource-link and image parts, inline Edit/Write diff / drill-down with
  Raw toggle), a conversation-wide changed-files panel, and a global
  "Expand details" preference on mod+shift+g.
- diag-debug-session-flow: "Debug with AI" consent dialog names the
  target, lists MCP reporting servers, previews and sends the TUI's
  byte-identical opening prompt once the debug chat is live; mod+shift+y.
- keyboard-away-resume-notice: one "while you were away" toast after a
  20 s+ hidden/frozen tab, composed only after the runtime + inventory
  refresh settles.
- auth-remote-login-hardening: RFC 9728 discovery with review-then-
  confirm, copy sign-in link, MECATL_TLS_CA/INSECURE dispatcher,
  PREFER_STATIC/ANONYMOUS ordering, callback timeout, encrypted file
  token store; fuller Remote sign-in card; env + user-docs.
- sessions-inventory-kinds: Chats / Runs / Scheduled / Drafts / Other
  sidebar tabs over the inventory's inspect-only rows, read-only
  transcript dialog with Retry and parent link, delegation-card Inspect,
  ⌘K Runs group with ?inspect= deep links.
- settings-theming-custom-palettes: user-pasted and STUDIO_PALETTE_DIR
  operator palettes behind one isomorphic validator, /api/palettes,
  generated <style> pair, boot script keeps a custom id pre-paint.
- startup-help-about-version: Settings → Help & about with Studio/SDK
  versions, docs + source links, a scanned configuration reference and
  GET /api/studio/about (names and booleans only); ⌘K Pages group.

Integration fixes: matchCombo now takes the structural ComboKeyEvent it
reads (drops the invalid KeyboardEvent cast that failed tsc); knip's
five unused type exports un-exported; the e2e fixture serves the
snapshot GET for its two stored inspect rows, which the transcript
loader's sessions.get requires (the unexecuted Runs-tab spec now passes).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- approvals-keyboard-verdicts: y/a allow once, w always allow, n/d deny, and Esc denies the pending ask instead of cancelling the run; a shared ApprovalVerdictBar (inline panel, thread card, expanded detail) with arrow/Home/End cycling, a Kbd keycap component, and an "Approvals" shortcuts group rendered by the reference and Settings → Keyboard
- composer-clear-draft-and-mod-enter-newline: one-shot draft clear (⌘⇧U / Ctrl+Shift+U, a labelled Clear draft button, and the existing double-Esc guard sharing one clearDraft closure) plus ⌘Enter / Ctrl+Enter as an unconditional newline mid-run, both documented as fixed registry rows
- diag-developer-tools: Settings → Labs "Developer tools" gate; /debug-ask and the ··· menu's "Inject fake approval" park a clearly-labelled synthetic Shell ask on the real ApprovalPanel that never reaches the daemon; a bounded steer correlation trace renders under the queue strip
- chat-provider-route-echo: each assistant bubble carries a muted "via <route>" marker stamped from the provider_route event on the live and watch paths (display-only, absent on a cache hit, never replayed)
- sessions-live-feed: session.title events update the chat header, sidebar row and tab title live through a revision-guarded applySessionTitle; an idle chat keeps a metadata watch that detects runs driven elsewhere; ConnectionStatusBanner mirrors the SDK status store for reconnecting/unauthorized
- settings-statusline-templates: Settings → Status line edits header and footer template lanes (Full/Compact/Minimal by container width) over a safe {{fact}} grammar with a live preview, fact chips, a clamped refresh interval and strict JSON export/import; the defaults reproduce today's chat exactly
- startup-failed-prompt-restore: a run-entry failure offers Edit next to Retry (the refused prompt returns to the composer and the failed exchange is dropped); the draft view renders the same TurnErrorStrip and auto-restores a refused text prompt
- mcp-inventory-panel: Settings → MCP tools lists resolved sources, servers, skip diagnostics and ToolHive groups with Refresh; the chat gains a Plug-button MCP side panel with broker connectors, enrollment state and Connect/Cancel through the existing enrollment controller; fixture and e2e coverage added

Integration gate: un-exported eleven file-internal helpers knip flagged (mcp-sources-list, statusline facts/preferences/template, harness/mcp) and let biome format one file. biome, tsc, knip, vitest (283 files / 2883 tests) and test:server (Next build + 14 hermetic tests) are green.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…kers

- approvals-hook-notices: hook fires are rendered (typed `hook` StreamEvent -> per-call chips, panel "Hooks" section, lifecycle notices); a blocked fire never double-fails the call
- chat-seed-prompt-deeplink: `?prompt=` pre-fills the composer, `&send=1` requires a one-click confirmation showing the exact text; PWA share_target (prefill-only)
- diag-product-metrics-optout: Settings -> Diagnostics "Product metrics" card (opt out / back in, dry run, controller-env opt-out honoured, restart confirm)
- sessions-row-actions: View transcript / Fork chat / Copy session ID on sidebar rows and the chat header, capability-gated; `c` and mod+shift+s shortcuts
- settings-steer-opt-out: "Queue only" message-queuing option, Settings -> Agent "Mid-run steering" daemon switch (--no-steer); capabilities.steer now wins over the static http_steer row
- startup-resume-latest: "Start on: Most recent chat" preference with once-per-mount auto-open, Continue-latest chip on the draft, `l` shortcut
- mcp-composer-pickers: "Insert from MCP" Prompt.../Resource... pickers in the composer (never sends), mod+. mod+; mod+' Tools shortcuts
- permissions-posture-badge: daemon-reported posture as a chrome chip in the top nav (auto/yolo warnings, Trusted project), linking to Settings -> Permissions

Integration: dropped `export` from 12 module-local symbols knip flagged; ran the five Playwright tests three packages had left unexecuted (all 38 e2e pass).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Eight parity packages landed concurrently; the integration gate ran
biome, lint, tsc, knip, vitest (326 files / 3321 tests) and the
hermetic server suite (Next build + 14 tests) green. The only
collision damage was over-exporting: thirteen module-internal
symbols across six new files were exported without an importer, so
knip failed; their `export` keywords were dropped.

- settings-preferences-file: Settings → Personalize gains a
  "Preferences file" card — export every browser-local preference as
  mecatl-studio-preferences.json and import it through a preview
  dialog (set / cleared / refused-with-reason) before anything is
  written; 22-key inventory with an anti-drift walk over src/.
- startup-welcome-hints: the draft chat renders the mascot
  (public/mecatito.png) and capability-gated WelcomeHints rows plus the
  "Connected · provider · posture · deployment" identity line.
- slash-palette-builtins: the composer's `/` palette lists the TUI's
  client-side built-ins (/title /mcp /prompts /resources /agents
  /skills /soul /usermodel /reflections /reflect /dream /models
  /effort /schedule /tools-connect /tools-cancel /posture /learning)
  ahead of daemon commands, one shared capability gate across palette,
  classifier and dispatcher; new SoulDialog and model-picker opener.
- settings-learning-mode: Settings → Learning opens with a "Learning
  mode" card (mode + sensitivity, effective values with source hints,
  from → to report, Save and restart); RuntimeStatusProvider.refresh
  re-reads /v1/compatibility so gates flip without a reload.
- startup-workspace-root: new Settings → Workspace page shows and
  changes the managed daemon's root via the header-gated controller
  POST /workspace (validated in src/lib/workspace-config.mjs,
  per-root session store and memory dir, trust grants withdrawn);
  About card gains a Workspace row.
- usermodel-entry-detail: the memory detail page performs the
  key-scoped GET /v1/usermodel?key= read and shows the value, derived
  Details rows and bounded History; Settings → Memory gains the
  `N facts · M bytes · sha256` footprint line.
- settings-soul-persona: Settings → Agent hosts the read-only Persona
  card over GET /v1/soul and the Persona settings card bound to the
  controller's runtime-settings soul document (load / strict / file /
  approve baseline).
- startup-connected-indicator: an always-present connection pill in
  the top nav (Connected / Connecting… / Offline / Sign in required /
  Credential rejected) in a role=status live region, linking to
  Settings → Diagnostics.

Playwright specs added by four packages were type-checked and built
but not executed by their authors or by this gate (test:e2e is
outside the wave gate).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…lish

Eight parity packages landed concurrently; integration gate ran biome,
lint, tsc, knip, vitest (337 files / 3462 tests) and test:server (14/14).

- settings-composer-mode-rail: mode-coloured composer rail via the pure
  composerFrameClass helper (composer-frame.ts), modeDotClass dot on the
  Mode pill and mobile Mode row, 3-row default / 8-row cap growth policy
  in globals.css with a 1-row mobile pin; the dead rows/compact props now
  drive --composer-min-rows and the two rows={1} callers were removed.
- startup-auth-failure-signin: AuthRecoveryBanner takes the controller
  mode; a managed-mode daemon 401 offers Restart daemon instead of a link
  to a settings page with no sign-in card; oidc_* causes keep sign-in.
- session-tool-profile: "Tools" section (All tools / No filesystem) on a
  draft rides the first send's mint as profile:"no-fs" (default omits the
  key); browser-local session-profile-memory recalls it on a live chat;
  schedules gain a Tool profile select, quick-create field and detail fact.
- startup-initial-prompt-param: ?prompt= deep link hardened against
  control characters (C0/DEL dropped, \n/\t kept, CRLF folded), e2e case
  for seeding an open chat, CLAUDE.md rule 23 pins never-sends-without-click.
- dream-review-polish: Consolidate memory confirms generate/regenerate in
  an AlertDialog, locks to same-decision retry on dream_in_progress or a
  dropped connection, classifies dream_conflict as stale, lists every
  target with unavailable reasons, shows duplicate badge / current values /
  expiry, and a five-bucket receipt with conflicted/failed guidance.
- startup-placement-label: chat header PlacementBadge (label · branch,
  kind @ short revision on hover, "No filesystem" for no-fs) projected via
  placementFromSnapshot off the GET-session snapshot; display-only, ADR 0291.
- learning-review-depth: Learning review queue gains a Deferred pill,
  page-size-50 cursor paging with Load more and Refresh, per-row Details
  with evidence provenance / decisions / promotion receipt / learned-skill
  link (skills page honours ?view=learned), approve for staged+deferred
  per isProposalApprovable, and a version-moved re-read via
  getLearningProposal; e2e fixture serves the learning routes.
- gate: un-exported DreamOperation, LearningDecision and
  LearningPromotionReceipt (intra-file types knip flagged as unused exports).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…sona viewer

- memory-status-indicator: the composer's inert local "Memory On/Off" toggle
  is replaced by a read-only, daemon-derived indicator (memory-indicator.tsx)
  reading serverCapabilities.memory/user_model — On/Off/Unknown pill with a
  read-only popover listing both stores and a link to Settings → Memory; the
  mobile "+" sheet shows the same rows. Capability-gated by construction,
  hides outside RuntimeStatusProvider.
- mcp-tool-titles: MCP tool ids render as TUI-style titles
  ("mcp__github__issue_write" → "GitHub · Issue write") via the new pure
  display port src/lib/tool-names.ts (render.go parity); friendlyToolName
  delegates to it, so activity rows, tool panel header, approval card/detail
  badges, chat-view panel title and delegation surfaces all pick it up. Raw
  names stay byte-exact everywhere else; the approval destructiveness regex
  now also matches the humanized title (delete_branch classified destructive).
- soul-persona-viewer: wave 10 had already shipped the Settings → Agent
  persona card; this wave verifies it and adds the "trusted" badge for a
  loaded project soul plus naming the not-found files when no persona was
  selected, both pinned by tests.

Integration: un-exported four module-internal helpers (memoryStateLabel,
useMemoryStatus, MCP_SERVER_NAMES, MCP_TOOL_NAMES) that knip flagged as
unused exports; biome, tsc, knip, vitest (340 files / 3513 tests) and
test:server (14/14) are green.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- daemon-options-controller: managed-mode Settings → Tools page, "MCP
  discovery" card (Settings → MCP tools) and "Memory stores" card
  (Settings → Memory), all over ONE persisted daemon-options.json the
  local controller composes into mecated spawn flags (GET|PUT
  /daemon-options, pure module src/lib/daemon-options.mjs; directories
  confined to workspace / default root / mecatl config dir; refused start
  rolls back). Daemon-reported bash/skills/slash_commands/memory/user_model
  status rows in both modes; external mode 409s and renders the managed
  note. Skills page banner when the daemon reports skills: false.
- skill-change-notices: after any observed run ends (own prompt stream or
  the durable watch), Studio re-lists learned-skill lifecycle receipts via
  the daemon's receipt-id cursor and toasts "N learned-skill change
  receipt(s) available" with an "Open Skills" action to
  /workspace/skills?view=learned; the Learned view reloads on the same
  page-local run-finished signal and on tab visibility.
- gate: removed three dead exports knip flagged (a duplicate Skill-tool-off
  string on the Tools card, and DEFAULT_REVIEW_INTERVAL /
  TOOLHIVE_GROUP_PATTERN re-exports nothing imported); biome, tsc, knip,
  vitest (3592) and test:server (14) green.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
SDK:
- RecheckMcpAuthorization / CancelMcpAuthorization post with no request
  body over HTTP; the daemon's correlation-only controls reject any body
  (HTTP 400), so the `{}` an empty frame encoded to always failed. The Go
  route-parity test now derives "none" for handlers that enforce an empty
  body via controlRequestBodyEmpty.
- AttachOptions.onReconnect reports each watch reconnect attempt and its
  backoff delay; a throwing listener never breaks the reconnect loop.
- Export PLAN_APPROVAL_TOOL and PLAN_APPROVED_PROCEED_TEXT so clients share
  the daemon's plan-approval strings instead of mirroring them.
- Tests, CHANGELOG, API Extractor reports and generated reference docs.

Studio:
- Drop the fetch-level workaround that stripped the authorization-control
  body now that the SDK posts none.
- plan-ask imports the plan constants from the SDK (re-exported for the
  feature's callers).
- Connection banner comment reflects the SDK's reconnect callback.

Docs: PRODUCTION-READINESS Studio row records the parity program outcome
and its deferrals.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
jtenniswood and others added 8 commits September 17, 2026 12:23
Removals (per product review of the running app):
- Draft chat: welcome card, mascot and capability hints; the greeting and
  starter prompts stay.
- Sidebar: Chats / Runs / Scheduled / Drafts tabs (chats only list; runs
  and fires stay reachable through the read-only transcript dialog);
  agent rows lose their colour tint and model label.
- Top nav: "Trusted project" posture chip and "Connected" indicator.
- Chat header: placement badge; the status strip now renders only for
  AI-debug sessions — its facts (id, model, mode, server, safety level)
  live in ⋯ → Session details, and the ⋯ menu is regrouped (view ·
  conversation · Copy submenu · details · rename/delete) without the
  shortcuts item.
- Composer: the Memory pill; the model list drops the header text, the
  auto row, provider prefixes and capability data, and groups models by
  provider (a stale browser default stays clearable).
- Settings: Status line, Tools and Persona surfaces, the Custom palettes
  card and the Welcome card preference; Diagnostics moves under Support
  and "Help & about" becomes "About".

Additions:
- The composer's Mode menu offers "Safety level" (the operator posture,
  Strict/Trusted/Auto/Yolo) writing the same permissions document as
  Settings → Permissions, with the Auto/Yolo confirmation rendered outside
  the menu so it survives the menu closing.
- Tools (All tools / No filesystem) is its own pill next to Mode.
- Permissions and Agent behaviour cards rewritten in plain language.
- The model catalogue reloads when the active provider changes, so a
  composer mounted before a provider switch no longer shows a stale list.

Tests, e2e and studio/CLAUDE.md updated for every removal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Studio-owned, browser-local folders (the daemon has no notion of projects):
`src/lib/chat-folders.ts` keeps `{folders, assignments}` under one
localStorage key, validated and bounded (50 folders, 60-char names), with
a useSyncExternalStore hook shared by every consumer and cross-tab sync.

Sidebar: chats filed in a folder list under the folder's heading (creation
order, empty folders kept so they can be renamed or deleted), the rest keep
the recency groups. Each chat row's menu gains "Move to folder" (folders,
No folder, New folder…); a folder heading's ⋯ offers Rename / Delete
(delete only unfiles — chats stay). The Labs mock group is untouched.

Excluded from the preferences file export: assignments are keyed by daemon
session id and mean nothing on another daemon.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The long-press bottom sheet gains "Move to folder": a one-tap picker
(folders with the current one marked, No folder, New folder…) with a Back
row, so phones can file chats too. Same gates as the desktop menu.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every settings page rewritten for office users: one-sentence card
descriptions, "the agent" instead of daemon/controller vocabulary, one
restart sentence ("Changes restart the agent. Anything running will
stop."), and only the controls that audience needs.

Removed pages: Keyboard (rebinding), Model router, Workspace (path).
Removed cards: Preferences file import/export, performance/pprof, posture
(lives on Permissions), store location, retention, storage migration,
MCP discovery flags, the daemon-defaults knobs beyond the default model
(effort, context window, timeouts, caching, base URLs, gateway toggles,
aliases/slots, key file), the About page's configuration reference and
technical rows. Their controller documents and routes stay at defaults.

Kept and simplified: You, Personalise (theme, palette, text size, sidebar
side, chat behaviour, notifications), Agent, Permissions, Memory (two
switches + consolidate), Learning, Providers (list, add/remove, sign-in,
default model), MCP tools (connect + tool list), Storage (health +
clean-up), Diagnostics (logs, usage statistics, restart), Labs, About.

Shared strings (save notices, offline/external notes, MCP panel, sign-in
remedies, shortcuts reference) lose the last daemon/env-var wording;
studio/CLAUDE.md records the removals. Gates: biome, tsc, knip, vitest
3336, server 14, Playwright 36.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
"Take messages while working — Let the agent take your messages while it
is still working." replaces the two-sentence on/off explanation.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Product decision: the settings' "Save and restart"-style buttons read as
pills, so the shared Button family switches from rounded-md to rounded-full
across default, sm and lg sizes (the action variant already was).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The default Button variant now uses the --btn-primary tokens (green)
instead of the black bg-primary fill, matching the action variant.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… storage grant

Studio, for non-technical users:
- Settings cards lose the subtitle under their title and every restart
  sentence. Single-switch cards (memory stores, agent behaviour, usage
  statistics, default model) apply on the flip and the agent restarts in
  the background ("Applying…" while busy; errors inline). The Consolidate
  memory selector and the Default model picker fill their row.
- The composer gets three pills: Mode (modes only), Tools (All / None,
  value in grey) and Safety (Strict/Trusted/Auto/Yolo, its own menu, wide
  enough for one-line descriptions); the Model pill reads "Model" plus the
  model the agent actually uses by default (the saved default for the
  active provider), not "Default model".
- The sidebar no longer lists the agent roster (@-mentions still work);
  the MCP tools card renders nothing when there is nothing to list.
- Every button is pill-shaped and primary buttons use the brand green.

mecated: new `--local-storage-management` flag (Storage help group,
serve-only) exposing the embedded-server storage grant for a private
loopback daemon; refused with OIDC ownership. Studio's controller passes
it on every managed spawn so Settings → Storage can read health and clean
up old runs. Documented in the configuration reference.

Gates: cmd/mecated flag tests, biome, tsc, knip, vitest 3334, server 14,
Playwright 36, docs/link gate.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
jtenniswood and others added 4 commits September 17, 2026 14:41
…default-model footer

- The chat list no longer ends in a "Storage & maintenance →" link; the
  Storage page is reached from Settings like everything else.
- The Effort menu loses "Picking an effort continues this chat in a copy
  on it."; the Model menu loses the "Set … as my default" footer and its
  Shift+Enter shortcut (the browser-local default is still set from the
  mobile picker's star and still marks its row).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…fety level

The footer status line (model id · bar · used / window · token counts) is
gone. The composer's control bar gets a compact context pill instead: a
short bar and the percentage of the context window in use, exact figures
on hover. With it go the templated header/footer lanes and the status-line
template library nothing else used.

Settings → Permissions shows the Auto/Yolo warning directly under the
Safety level row rather than after the other rows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Settings → Permissions keeps the "Trust this project" switch and loses the
remembered-trust row (badge, Forget trust, Trust again); the workspace
trust banner still offers the grant when a project asks for it.

The composer's mode pill reads "Mode" followed by the current mode in grey,
matching the Tools and Safety pills.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tate hint

The chat shows no context-window usage at all now: the composer bar's
context pill (bar + percentage, added with the status-line removal) is
gone, together with everything that existed only to feed it — the
per-turn occupancy state in use-agent-chat, the chat-view/chat-workspace
plumbing, the ContextPill/meterOccupancy halves of context-meter, the
pressure-band helpers in turn-stats, and the Shortcuts page's "The
context meter is approximate" sentence. The fraction helper survives for
the Agents panel's per-member meter; token figures live only in the ⋯
menu's Token usage rows.

The Skills page's empty state loses its "Drop a SKILL.md into
.mecatl/skills…" paragraph and reads just "No skills here yet".

studio/CLAUDE.md's removed-surfaces list names both, and no longer
claims the status-line templates still render (that library went with
the strip).

Gates: biome, tsc, knip, vitest 3277, Playwright 36.

Co-Authored-By: Claude Fable 5.1 <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