Codes menu — focus on a code group

A proposal. Introduces a focus cursor for code groups and codes on the codebook lens, mirroring the signal-card focus already shipped on Analysis — which is what gives every Codes command a target and makes the menu live again.

12 Sep 2026 · proposal, not a decision · supersedes the 12 Sep retirement of four of six commands

1 — Focus is not selection

The retirement earlier today argued these commands had no target, citing the 29 Aug pin: “selection is single, it lives in the master list … no second place a thing can be ‘current’.” That reasoning was too strong. The pin governs selection — which codebook the detail pane renders. A focus cursor inside that pane is a different axis, and the codebase already models the two separately in two places.

Precedent 1 — FocusContext, the quote grid. Its own docstring lists them as separate concerns: “Focus (keyboard cursor): at most one quote focused at a time” and “Selection (multi-select): zero or more quotes selected for bulk actions”. The Quotes lens has had both since the React migration, and nobody calls the focused quote a second selection.
Precedent 2 — AnalysisSignalStore.focusedKey, the signal cards. A module-level store holding one nullable key, read by the card (bn-selected → blue wash), the sidebar and the inspector. 40 lines. This proposal is that store again, with two keys instead of one.
Doc obligation. The pin’s wording — “no second place a thing can be ‘current’” — is what invited the over-reading, twice now. If this proposal lands, the pin says selection is single and names focus as a separate axis, or a future session re-retires these commands on the same misreading.

2 — The focus ring

One click on a group card’s background focuses it. Same gesture as a signal card, same bn-selected wash, same clearing rules (click elsewhere, or Escape). Focus is a cursor, not a mode: every existing direct-manipulation affordance keeps working untouched.

Onboarding✕
first runsignup empty state
+ Add code
Trust & safety✕
privacyconsent data sharingaudit
+ Add code
Pricing✕
too expensivetiers
+ Add code

Focus on a code, and the rename field

Trust & safety✕
privacy consent ✕ data sharingaudit
+ Add code
Trust & safety✕
privacy consent data sharingaudit
+ Add code

3 — The menu, in four contexts

Menus dim, never hide. Every row below is present in all four; only the enablement changes.

Why the four rename/delete rows carry no icon. The icon audit already flags pencil ×4 and trash ×3 as the thinning candidates. Giving these rows the obvious glyphs takes the app to pencil ×6 and trash ×5 — the menu bar’s two most repeated symbols, on rows whose labels are already unambiguous. Tier 2 said the payoff of an icon falls as its repeat count rises; this is that rule applied before the fact rather than after.

4 — Enablement

Menu itemTwin on the pageTargetEnabled when
New Code Groupthe New Group buttonnone — creation floor page open
New Codea group’s + Add code rowfocused group floor + group focused
Rename Code Groupclick the group titlefocused group floor + group focused
Delete Code Groupthe ✕ top-right of the cardfocused group floor + group focused
Rename Codeclick the chip’s namefocused code floor + code focused
Delete Codehover the chip, click ✕focused code floor + code focused
incl. while renaming
Browse Codebooksthe Browse Library buttonnone — navigation always, on the lens
Install / Uninstall Codebookthe page’s own Install ⇄ Uninstall button the open codebook detail page open
not floor, not Sentiment
The verb swaps, it does not gain a checkmark. Same idiom as Turn On / Turn Off Agent Access in the sidebar (§3.6a). And the exclusions are not new rules — canInstall on both the browse card and the detail page already excludes the floor and Sentiment (D20). The menu mirrors that predicate rather than restating it.

5 — Three things the proposal has to decide

6 — What stays retired

Correction carried by this mockup. Four of the six commands retired earlier today — Rename/Delete Code Group, Rename/Delete Code — were retired on the reasoning that no target could exist. Focus is that target, and it was already shipped twice elsewhere in the app. The retirement of the other two stands on separate arguments.