Skip to content

C11-248: workspaces → areas → tabs vocabulary, with hidden aliases (phase 1: UI, CLI, socket, docs, tests) - #472

Merged
BenevolentFutures merged 27 commits into
mainfrom
c11-248-vocab
Oct 1, 2026
Merged

BenevolentFutures merged 27 commits into
mainfrom
c11-248-vocab

Conversation

@BenevolentFutures

Copy link
Copy Markdown
Contributor

C11-248, phase 1: the visible layer of the vocabulary rename. c11's model is workspaces → areas → tabs: a pane is an area, a surface is a tab, a sidebar entry is a workspace (never a tab).

What changes

  • UI: 88 English strings plus 528 translations across 6 locales (xcstrings keys unchanged). The Settings section "Surfaces" becomes "Tabs & Areas", and "Show Surface IDs in Tab Titles" becomes "Show Tab Numbers in Tab Titles". Bonsplit's own strings ("Close Pane" etc.) come in through a submodule bump (Localization: say area and tab instead of pane and surface bonsplit#9, merge commit).
  • CLI: 18 canonical commands (new-tab, list-areas, focus-area, close-tab, move-tab, …), flags --tab/--area/--before-tab/--after-tab/--in-tab, refs tab:N/area:N, and human output (OK tab:7 area:3 workspace:1). Help and usage list only the new names.
  • Socket v2: canonical tab.*, area.*, notification.create_for_tab. system.capabilities lists only new names.
  • JSON: new keys plus the old keys for one release, through one central table (Sources/SocketHandlers/LegacyWireAliases.swift). Old keys keep old-format values (surface_ref: "surface:5" beside tab_ref: "tab:5").
  • Other surfaces:
    • Env: C11_TAB_ID is canonical and C11_TAB_NUM is new.
    • Metadata: flag_caller_tab_id is written beside flag_caller_surface_id.
    • Mailbox: tab: addresses are accepted.
    • AppleScript: the class that means a workspace is now workspace.
  • Docs and skills: skills, docs, README and CLAUDE.md (one line states the vocabulary and the alias rule).
  • Tests: the Python suites move to the new names. A new tests_v2/test_vocabulary_aliases.py covers every alias, the dual JSON keys, and the rule that free text is never rewritten.

Hidden aliases (old names keep working, never shown)

  • Old CLI commands, flags (--surface, --panel, --pane) and refs (surface:N, pane:N).
  • Old socket methods (surface.*, pane.*) and param names.
  • Old env vars (C11_SURFACE_ID, CMUX_*).
  • AppleScript tab terminology, as sdef synonyms.
  • The CLI falls back to legacy method and param names when it talks to an older app (method_not_found), so a new CLI keeps working against the shipped app during rollout.

Deliberately unchanged

  • tmux-compatible commands and their flags.
  • The v1 text protocol wire.
  • v2 error codes.
  • Event schema v1 (types, envelope fields, scope values). Additive: caller_tab_id.
  • Persisted formats: session snapshots, plan/blueprint keys, UserDefaults and theme keys.
  • Vendor identifiers.

Later phases of C11-248

  • Swift internal identifier renames, as scripted, compiler-checked passes in their own PRs.
  • External consumers' skill prose, held until a release ships these names.
  • The web/ site, test class names, debug keys, the Go remote CLI table, and the educational-materials repo.

Validation

  • Debug build is green. A tagged build was driven live: every new command beside its alias, both JSON key families, and old and new methods, params and refs, plus the new CLI against the old app.
  • CI covers the compile, c11-logic and c11-unit gates.

🤖 Generated with Claude Code

Atin and others added 16 commits September 30, 2026 22:34
English UI copy now says area (was pane) and tab (was surface/panel); xcstrings
keys unchanged. Six locales re-translated for 88 values. Settings section
'Surfaces' becomes 'Tabs & Areas'. UT description for the workspace reorder
drag no longer calls a workspace a tab.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…> tabs

Rewrites the c11, c11-browser, c11-markdown, lattice-orchestrator and peer skills,
live reference docs, README, PHILOSOPHY, PROJECTS, CONTRIBUTING and welcome.md to
the new vocabulary (commands, flags, refs, env vars, socket methods, JSON keys).
Adds the one-line vocabulary statement to CLAUDE.md. Historical plans, review
packs and AARs are left untouched.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…er metadata, mailbox tab:, sdef workspace

Canonical tab.*/area.* methods with surface.*/pane.* as hidden aliases,
central LegacyWireAliases shim (methods, params, legacy JSON keys), area:/tab:
ref prefixes, flag_caller_tab_id written beside the legacy key, C11_TAB_NUM,
AppleScript workspace class with hidden tab synonyms.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ary alias tests

Client lib and tests_v2 string literals move to the canonical wire names
(tab.*/area.* methods, tab_id/area_id params and keys, tab:N/area:N refs,
new-tab/list-areas commands, --tab/--area flags, C11_TAB_ID). tmux-compat tests
stay on tmux names; plan-schema keys and debug layout keys are untouched. The
claude-teams tmux fake server answers both vocabularies. New
test_vocabulary_aliases.py proves every old method, param, ref prefix, command,
flag and env var still resolves, and that responses carry both key families.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…xt with hidden aliases

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… help hierarchy, focus-intent canonicalization

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Error codes stay as the code emits them, flag.raised documents caller_tab_id,
metadata.changed scope is the literal surface/pane, apply results document
tabRefs/areaRefs, settings path is Tabs & Areas.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Tighten text assertions to the new wording (tree words area/tab, report
header, No tabs/No areas, send requires --tab), keep browser.tab.* on
surface_id, drop the tolerances for error text that B left unchanged, treat
`ref` as having no legacy twin in the tree check, and add a test that
flag-looking free text sent to a tab arrives literally.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…elpers (free text never rewritten), canonical v2 methods in the CLI, size-policy vocabulary, env -u lists, test strings, tree report words, hotload note

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The repair commit 74b7233 staged the stale submodule checkout and reverted the bump from 09725ff.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the drawbridge:review Drawbridge: routed to maintainer review label Oct 1, 2026
…us args)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Deep review — PR #472: workspaces → areas → tabs vocabulary (phase 1)

Thank you for this — it's a big, carefully-staged rename and the discipline is visible on every line. The hidden-alias strategy (old commands, flags, refs, methods and env vars keep working but never surface in help/skills/docs), the dual JSON keys held for exactly one release, and especially the CLI-side version-skew fallback so a fresh CLI keeps talking to an older app mid-rollout are exactly the right shape for a rename that must never break an agent in flight. The legacyParams/modernResult asymmetry — canonical keys filled on the way back, user text (metadata/value/title/prompt/…) protected from ref rewriting on the way out — is thoughtful, and the flagAliasGroups + canonicalCommandName + canonicalHandle seams keep the alias surface in one readable place.

Scope of this review (please read first)

The diff I was handed contains only 3 of the 226 changed files — CLAUDE.md, CLI/ConfigCommand.swift, and CLI/c11.swift. I read c11.swift in full and checked its socket calls against the trusted main checkout, so the CLI findings below are high-confidence. Everything else the PR body describes — the app-side tab.*/area.* handlers, Sources/SocketHandlers/LegacyWireAliases.swift, the Localizable.xcstrings translations, the skills/** updates, tests_v2/, and the bonsplit submodule bump — is not visible here and has not been reviewed. Treat the other ~223 files as still needing eyes; see the checklist at the end.

One correction to the earlier pass: GitHub now reports this PR as mergeable: true with mergeable_state: unstable — the merge conflict appears resolved, so "unstable" is a pending/failing or non-required check rather than a dirty tree. Please confirm the required checks (workflow-guard-tests, remote-daemon-tests, web-typecheck, build) are all green before landing.

Alignment

In scope: a CLI/socket ergonomics + agent-facing vocabulary change, correctly routed to deep review rather than the autonomous lane. It's a refactor (not a bug fix), far over the size cap (6528/4021, 226 files), and touches deny-listed paths (CLAUDE.md, the vendor/ submodule bump, almost certainly *.pbxproj). Any one of those forces human review by policy, so the routing is right. The one-line vocabulary statement added to CLAUDE.md matches the project's own framing, and the mechanism respects "unopinionated about the terminal" — env resolution is read-only, no tenant-config writes. No prompt-injection or instruction-bearing content in the visible diff.

Findings

1. (major) area-confirm cannot fall back to an older app — the skew map has no panel_id route. [CONFIRMED] CLI/c11.swift, area-confirm case (diff ~L683, L701) + CLIVersionSkew.keyPairs (L176–203).

area-confirm sends method area.confirm with param tab_id (the panel handle, L683–684, L701). Against an app that predates the rename, probeServerIsLegacy is true, so the client rewrites the request: legacyMethod("area.confirm") → pane.confirm, and legacyParams maps tab_id → surface_id via the first keyPairs row. But the legacy pane.confirm handler hard-requires panel_id — I confirmed this on main:

  • Sources/SocketHandlers/PaneHandlers.swift:962 → guard let panelId = v2UUID(params, "panel_id") else { return .err(code: "invalid_params", message: "Missing or invalid panel_id", …) }.
  • v2ResolveTabManager (Sources/TerminalController.swift:2950,2958) does accept surface_id/tab_id to locate the owning TabManager, so the manager resolves — but line 962 still needs panel_id to identify the target panel, and the skew path never sends it.

So the one command that historically keyed on panel_id is exactly the one the generic tab_id → surface_id mapping can't rescue. During the one-release skew window, a fresh CLI calling area-confirm (or its hidden alias pane-confirm, which hits the same code) against the shipped app fails with invalid_params, with no CLI escape hatch — silently breaking the PR's own "a new CLI keeps working against the shipped app during rollout" guarantee. And area-confirm gates destructive-action confirmations, so a broken confirm mid-rollout is worse than cosmetic. I spot-checked the siblings: pane.confirm is the only hard panel_id requirement reachable from the CLI (DebugHandlers.swift:688 accepts surface_id ?? panel_id; trigger-flash, tab.close, tab.health, area.resize/swap/break/join all used surface_id/pane_id on the legacy side, which the generic mapping handles).

Fix: special-case the pane.confirm retry so the value sent as tab_id goes out as panel_id. A generic keyPairs row can't express this (adding ("tab_id","panel_id") globally would corrupt every other command), so key it on the method inside legacyParams/legacyMethod, or branch at the area-confirm call site when the client is legacy. Please also add a skew case to test_vocabulary_aliases.py — it would have caught this. (Separately, confirm the modern area.confirm handler reads tab_id; if the renamed handler still expects panel_id, the modern path breaks too, independent of skew — that's app-side and not in this diff.)

2. (minor) Every CLI invocation pays an extra system.capabilities round-trip for one release. CLI/c11.swift, probeServerIsLegacy / sendV2 (diff L303–329).

serverIsLegacy is cached on the SocketClient, but each c11 <cmd> is a fresh process with a fresh client, so the probe fires once per command — an extra request on top of the real one for every CLI call until the fallback is removed. Over a local unix socket this is sub-ms-to-low-ms and it's time-boxed to one release, so it's acceptable; just confirm it's intended and that the latency-sensitive callers (send, send-key, hot agent loops) were measured. The probe logic itself is sound: a legacy system.capabilities returns a methods array containing surface.list but not tab.list, so detection is reliable, and a failed/empty probe correctly fails open to the modern path. system.capabilities itself is exempted from the rewrite, so no recursion.

3. (nit) On the legacy skew path, refTokens emits no ref tokens. CLI/c11.swift (diff L340–348).

refTokens now reads only tab_id/area_id, but on the legacy path it runs inside sendV2Raw after legacyParams has rewritten those keys to surface_id/pane_id, so [c11-trace] and timeout messages lose their ref context during skew. Trace-only and skew-only — mention-level, not worth blocking on.

Clean spot-checks: canonicalCommandName dispatches old command names (new-surface → new-tab, list-panes → list-areas, …) while help/usage advertise only the new ones; flagAliasGroups wire --tab/--area beside the hidden --surface/--panel/--pane/--before-surface/--after-surface/--in-surface spellings through parseOption/optionValue/hasFlag, and tmux-compat flags (--target-pane, %N) are left out of the groups as intended; callerTabEnv resolves C11_TAB_ID → C11_SURFACE_ID → CMUX_TAB_ID → CMUX_SURFACE_ID in order; canonicalHandle normalizes surface:N→tab:N / pane:N→area:N only for numeric suffixes and is applied at every ref-vs-app-handle comparison (workspace/tab/area lists, tmux resolvers, default-agent matcher); the browser tab switch/close path deliberately keeps surface_id as the browsing-context key (with an explanatory comment) while renaming target_surface_id→target_tab_id; and the dual-key emission (canonical key beside legacy key, marked "remove after one release") is consistent — surface_id beside tab_id in the notify list, surfaces/panes beside tabs/areas in the tree JSON, surface beside tab in identify.browser. The for (key, child) in dict mutation inside modernValue is safe under Swift value semantics (the iterator snapshots the original storage). v1 wire (report_agent_activity … --tab=… --panel=…) is correctly left untouched.

What still needs review (not in this diff)

  • App handler side. The CLI now sends tab_id/area_id and methods like tab.create, area.focus, notification.create_for_tab. Confirm the app handlers read the new keys — in particular a modern area.confirm reading tab_id (finding c11mux fork: harden release pipeline on Stage-11-Agentics infra #1), and that LegacyWireAliases.swift maps inbound legacy keys the way each old handler expected (including area.tabs → pane.surfaces returning a surfaces/panes array the CLI's modernResult can lift to tabs/areas).
  • Skill contract (HARD RULE). CLAUDE.md states a CLI/socket change is incomplete until skills/c11/** matches and the installed copy is synced (scripts/sync-installed-skills.sh). The body says skills were updated — confirm in the diff, and sync the live copy; stale installed skills have bitten this project before.
  • Localization. Validate the edited xcstrings with jq, not plutil, and assert every interpolation token (%@, %lld, …) survives in all six locales (ja/uk/ko/zh-Hans/zh-Hant/ru). xcstrings keys are said to be unchanged — good — but verify no English defaultValue dropped a token.
  • Submodule discipline. The bonsplit bump must have its submodule commit pushed to the fork's main before the pointer lands: git -C vendor/bonsplit merge-base --is-ancestor HEAD origin/main. This is a deny-listed path regardless.
  • Tests. Confirm test_vocabulary_aliases.py exercises behavior (socket round-trips), not source text, per the test-quality policy — and extend it to cover the area-confirm skew path from finding c11mux fork: harden release pipeline on Stage-11-Agentics infra #1.

Recommendation

Merge after fixes — contingent on a maintainer reviewing the ~223 files not in this diff, and all required checks green (mergeable_state is currently unstable). Fix #1 (the area-confirm/panel_id skew gap) before landing, since it breaks the PR's own rollout guarantee on a destructive-confirm path; #2 is a confirm-and-note, #3 a trace cosmetic. The quality of the CLI layer I could read is high — but a rename spanning 226 files can't be signed off from three of them.


🌉 Drawbridge deep review (lane 3) — an automated pass; a maintainer has been notified and makes the final call.


🌉 Drawbridge deep review (lane 3) — an automated pass; a maintainer has been notified and makes the final call.

Atin and others added 10 commits September 30, 2026 23:05
…x test

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… grid detection, explicit tab target for send tests

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ction help, report token)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…fy panels, config --area, CLI JSON twins, capability-probe skew mode, sidebar --tab semantics, tmux help/output revert, UI/CLI vocabulary leftovers

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…t vars and capability-probe skew; remove vacuous assertions

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…id-format both)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…er identity (tests/fake_server_env.py); tmux sequence fake answers system.capabilities

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Carries #469's destructive-target guards onto the new vocabulary: tab.close and tab.action reject unresolved refs, rejectEmptyTargetFlags checks the --area/--tab alias groups, and the guard's error text names canonical keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@BenevolentFutures
BenevolentFutures merged commit 5726254 into main Oct 1, 2026
15 checks passed
This was referenced Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drawbridge:review Drawbridge: routed to maintainer review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant