Repository navigation
Navigation edit mode keeps the 240 px rail and shows the remove control without hover #12519
Description
Source
Measured on objectui main d4bc81eab, 2026-10-15, Interfaces pillar of a writable package: pressing Edit turns the rail into "TICKET DESK · NAVIGATION" with an "Unpublished" chip, a "NAVIGATION · 1 item · Add nav item" header and one row per entry with a drag handle. The rail grows from 240 px to about 300 px, so the content column and the aside shift right while editing and back on Done. Each row's "Remove nav item" trash button (engine.inspector.appNav.remove, AppNavInspector.tsx / the edit list) exists only while the pointer hovers the row; a keyboard user tabbing through the rows never sees it.
Target
- Edit mode keeps the rail at
STUDIO_RAIL_WIDTH(240 px): a 16 px handle, the label, the kind chip and a 24 px trash fit a 32 px row; the api name moves to the row'stitle. - The trash is always rendered at reduced opacity and full on hover or focus; it is reachable by keyboard.
- The content column does not move when entering or leaving edit mode.
File surface
packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (InterfacesPillar edit mode), packages/app-shell/src/views/metadata-admin/inspectors/AppNavInspector.tsx, StudioRail.tsx, tests beside.
Acceptance
- Tests: the rail's width class is the same in and out of edit mode; the remove button is in the document without hover.
- Screenshots in and out of edit mode at 1280 px, light and dark, with the content column at the same x.
pnpm --filter @object-ui/app-shell testgreen.
Constraints
- The ten-kind picker on "Add nav item" is Interfaces pillar: the rail is the app's navigation tree (groups, metadata icons, kind tags), "+" offers the ten nav kinds, and the canvas follows the kind: page regions designer, object runtime list preview, dashboard canvas #12455's; this card does not touch what Add creates.
- Existing
data-testids stay.
Filed by the epic PM session session_cdb7a717-def7-4e71-9988-f089fbaa18ff from the 2026-10-15 Studio test pass: objectui main d4bc81eab, HotCRM 63db83e2, the vite console on :5180 against objectstack serve --dev on :4001, viewport 1482 × 1119, dark theme with a light-theme check.
Evidence
Screenshots from the test pass, kept on the temporary branch claude/studio-test-pass-evidence-20261015 of objectstack-ai/objectui (commit 72115588c, never merged: the maintainer ruled that screenshots do not enter main, and asked for a temporary branch referenced from the card). The branch's docs/design/studio-redesign/findings/2026-10-15/README.md maps every file to its card. Pane captures, 800 × 604 of a 1482 × 1119 viewport, dark unless named light.

docs/design/studio-redesign/findings/2026-10-15/12519-nav-edit-item-added.jpg — nav edit item added.

docs/design/studio-redesign/findings/2026-10-15/12519-nav-edit-mode-wider-rail.jpg — nav edit mode wider rail.

docs/design/studio-redesign/findings/2026-10-15/12519-nav-normal-new-menu.jpg — nav normal new menu.
Parent: #12446
Body refreshed 2026-10-15T15:19Z by the epic PM session: an Evidence section with the test-pass screenshots added.
Activity
objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 1 (domain:ui lane queue)
Session:
session_01SJn1GEkrbbBQkhGC26wEDwAccount:
os-sales(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12519-nav-edit-railWorktree:
objectui-issue-12519(in the cloud session's own container)Domain:
domain:uiSeat:
domain:ui#4File surface:
packages/app-shell/src/views/metadata-admin/previews/AppNavCanvas.tsx(the edit-mode row: grip, label, kind chip, api name, trash; measured onmain9231820as the real producer of the hover-only trash, about :717, which the card's list did not name),packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx(theInterfacesPillarrail's edit-modewidthoverride only, about :4443),studio-design/StudioRail.tsxandstudio-tokens.ts(only if the width needs them),metadata-admin/inspectors/AppNavInspector.tsx(only if the api name's move needs it),metadata-admin/i18n.ts(new keys only), new tests beside them, the screenshotsdocs/design/studio-redesign/after/12519-*.png, one.changeset/12519-*.md(stop on breach; explain in the report)Container & model:
M,mode:cloud,model: opus(cloud on the maintainer's stance 「可以的话,ui 后续开发尽量用云 session,并且要求截图给我看」; lane scope on 「你也可以接ui车道的卡」; batch 5 on 「上限提到 5,提速」, this session's chat, 2026-10-15)Clause-②: no
Responsibility:
n/a — not a defect cardThread-read: none
Serial constraints cleared: objectui#12455 (
InterfacesPillar) landed as PR objectui#12544 (b541694), so the Interfaces rail is free. No open PR touchesAppNavCanvas.tsx,AppNavInspector.tsxorStudioRail.tsx. In flight onStudioDesignSurface.tsx, all this seat: objectui#12453 (the read-only banners and notes, near the Interfaces header but not its edit toggle), objectui#12508 (theclient.layeredload sites), objectui#12507 (the flow canvas). Any textual overlap is resolved by mergingorigin/main. objectui#12522 and objectui#12531 (same rail and inspector) stay queued behind this card.objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{
"issue": 12519,
"status": "done",
"branch": "claude/issue-12519-nav-edit-rail",
"pr": null,
"session": "session_01G4FxFLFyuUkMHJGFLChY8S",
"premise_still_valid": true,
"summary": "In navigation edit mode the Interfaces rail now stays at STUDIO_RAIL_WIDTH (240 px), so the content column stays at x 240 on Edit and on Done. Before, edit mode set
width={editNav ? 'w-72' : undefined}, which made the rail 288 px. That override is gone, and StudioRail'swidthprop, which nothing passed any more, is retired too. The AppNavCanvas row (the real producer of the hover-only trash, as the claim said; AppNavInspector was not touched) is now a 32 px line: a 16 px grip, the label, the kind chip and a remove control that is always drawn. The control is a fixed 24 px, sits at opacity-50, and goes to full opacity on hover or focus. The api name moved onto the row's title as 'LABEL · TARGET'. Nested rows indent with classes instead of the inline paddingLeft style. In a narrow rail the canvas header lets the Add button take a line of its own. The compact row applies to both mounts (Studio and the metadata-admin app designer) with no new prop. The premise holds with two corrections: the edit rail measured 288 px, not about 300 px; and the trash was already Tab-reachable on main (objectui#11776), so the defect left was that it was hidden (w-0 opacity-0) until hover or focus, which also shifted the label when it appeared.","tests": "All at HEAD 14a7dd4. (1) Acceptance item 3, the full suite:
pnpm --filter @object-ui/app-shell testunder the os-verify-lock gave VERDICT command-exit 0, Test Files 1398 passed and 1 skipped (1399), Tests 13382 passed and 9 skipped (13391). (2) Targeted run, root form, vitest run over the AppNavCanvas*, AppPreview*, AppNavInspector*, studio-locale.i18n, InterfacesPillar*, StudioDesignSurface.interfaces* and StudioDesignSurface.nav* files: 35 files, 275 tests. One existing pin failed (navDraftLabels-11862, which read the api name inside the row). After updating it, the six affected files are 72 of 72 green. (3) New pins: InterfacesPillar.editRailWidth-12519.test.tsx (2 tests) checks that the rail's w-* class is exactly STUDIO_RAIL_WIDTH before Edit, during edit mode and after Done; that the content column stays the rail's next sibling; and that in edit mode each top-level row has a remove button without hover (w-6, never w-0 or opacity-0) which Tab focuses right after its row. AppNavCanvas.compactRow-12519.test.tsx (3 tests) checks that the row is h-8, has no api name in its text, has title 'Support Overview · support_overview' (and just 'Queues' for a group), that the remove control is h-6 w-6 shrink-0 opacity-50 and reachable by Tab, and that a nested row has pl-4 and no style attribute. (4) Ablation: each mutation went through objectstack scripts/ablation-replace.mjs in wrap mode (anchor x1 to x0, replacement x0 to x1, blob changed). Each restore was proven with blob == HEAD and an emptygit diff HEAD. A1, which putsclassName={editNav ? 'w-72' : undefined}back on the rail, turned the width pin red ('expected [ w-72 ] to deeply equal [ w-60 ]'). The first A1 attempt was refused by the tool because the replacement contained its own anchor; nothing ran, and I re-anchored. A2 (trash back to w-0 opacity-0): 3 red. A3 (api name code chip back in the row): 3 red. A4 (inline paddingLeft style back): 1 red. A5 (title removed): 6 red. Unmutated, the same six files are 72 of 72 green. No dist was involved: the tests import the source by relative path.","gates": "All local, on 14a7dd4, exit codes captured before any pipe. Dependency closure build:
pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build, VERDICT 0.pnpm --filter @object-ui/app-shell type-check, VERDICT 0 (tsc --noEmit plus tsconfig.test.json, which compiles the tests).pnpm --filter @object-ui/app-shell build, VERDICT 0, with dist completeness at 1104 files.pnpm --filter @object-ui/app-shell test, VERDICT 0 (counts in tests). eslint on the 9 touched ts/tsx files: exit 0, 0 errors and 16 warnings, all on untouched pre-existing lines (the two in AppNavCanvas are the(draft as any)[rootKey]line and the setDraft effect).pnpm check:i18n-keysexit 0.pnpm check:i18n-driftexit 0 ('No en value changed in this range').check:i18n-designer-paritywas not owed because no keys were added.node scripts/check-changeset-presence.mjsexit 0 (9 source files in 1 released package, 1 changeset declared).node scripts/check-changeset-no-major.mjsexit 0.pnpm check:new-line-citationsexit 0 (0 new). The product-neutral-renderer ratchet test: 32 passed. NOT MEASURED:scripts/check-nul-bytes.mjsdoes not exist in objectui (MODULE_NOT_FOUND, so the gate never ran). Instead, a control-byte grep over the diff's files found nothing. objectui has no dispatch-gates.mjs, so there was no --ran reconciliation; the gate list above is hand-derived from the dispatch. CI check-runs: none yet, because the PR is not open.","mcp_calls": "5 GitHub MCP reads, no writes: issue_read get #12519, issue_read get_comments #12519, issue_read get #12446, issue_read get_comments #12446, pull_request_read get #12544. Plus 2 claude-code-remote add_repo calls (read access, nothing attached: objectstack and hotcrm are public anonymous reads).",
"api_writes": "0 REST writes. Only git pushes: the feature branch claude/issue-12519-nav-edit-rail (empty probe at 9231820, then 14a7dd4), and the evidence branch claude/issue-12519-evidence at a813acb. No label writes, no PR, no card comment.",
"pr_title_draft": "fix(app-shell): navigation edit mode keeps the 240 px rail and draws the remove control without hover (objectui#12519)",
"pr_body_draft": "(elided by the seat: opened verbatim as the body of PR #12566, read back identical)",
"files_changed": [
"packages/app-shell/src/views/metadata-admin/previews/AppNavCanvas.tsx",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (one line: the edit-mode width override)",
"packages/app-shell/src/views/studio-design/StudioRail.tsx (width prop retired, docblock)",
"packages/app-shell/src/views/metadata-admin/previews/AppNavCanvas.compactRow-12519.test.tsx (new)",
"packages/app-shell/src/views/studio-design/InterfacesPillar.editRailWidth-12519.test.tsx (new)",
"packages/app-shell/src/views/metadata-admin/previews/AppNavCanvas.navPayload-11776.test.tsx (pin updated)",
"packages/app-shell/src/views/metadata-admin/previews/AppPreview.test.tsx (pin updated)",
"packages/app-shell/src/views/metadata-admin/previews/AppPreview.docEntry-11197.test.tsx (pin updated)",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.navDraftLabels-11862.test.tsx (pin updated)",
".changeset/12519-nav-edit-rail.md"
],
"screenshots": [
"claude/issue-12519-evidence@a813acb: docs/design/studio-redesign/findings/12519/interfaces-view-light.png",
"claude/issue-12519-evidence@a813acb: docs/design/studio-redesign/findings/12519/interfaces-edit-light.png",
"claude/issue-12519-evidence@a813acb: docs/design/studio-redesign/findings/12519/interfaces-view-dark.png",
"claude/issue-12519-evidence@a813acb: docs/design/studio-redesign/findings/12519/interfaces-edit-dark.png",
"claude/issue-12519-evidence@a813acb: docs/design/studio-redesign/findings/12519/app-designer-nav-canvas-light.png",
"claude/issue-12519-evidence@a813acb: docs/design/studio-redesign/findings/12519/README.md"
],
"deviations": [
"File surface: four existing test files outside the claim's 'new tests beside them' were updated, because they read the api name in the row or the hidden trash classes: AppPreview.test, AppPreview.docEntry-11197, AppNavCanvas.navPayload-11776, StudioDesignSurface.navDraftLabels-11862. Each keeps its subject.",
"StudioRail.tsx: the
widthprop was removed, not just left unused, because no caller remains. This is inside the claim's 'only if the width needs them'.","The edit row drops the kind icon before the label. The card's target lists the row as handle, label, kind chip and trash, and at 240 px the icon would cost the label about 20 px. The toned chip still names the kind.",
"The row title is 'LABEL · TARGET' rather than the api name alone, because the label can truncate at 240 px. The browse rail of objectui#12455 uses 'kind · name' on its tooltip; the edit row already shows its kind as the chip.",
"Bounded additions in AppNavCanvas: the inline paddingLeft style became DEPTH_INDENT classes (ruling: no inline style), and the canvas header now wraps so Add nav item does not fold onto three lines at 240 px. Both were needed for target 1's fit.",
"Evidence path: the claim names docs/design/studio-redesign/after/12519-*.png. The dispatch rules that screenshots go to a separate never-merged branch under findings/12519/, so I followed the dispatch.",
"No new before screenshots were taken; the card's evidence branch has them. Before is given as numbers measured on 9231820: rail 288 px in edit mode, content column x 288, labels 0 to 6 px.",
"Commit trailers use the model-free pair from objectui AGENTS.md (Claude-Session plus 'Co-authored-by: Claude'), not the harness's model-bearing trailer. That follows the repo's precedence rule.",
"PR body footer: I used the os-dev session-URL footer form. The harness's separate PR footer line was not added, because two footers would collide with the footer rule.",
"The shared /home/user/objectui checkout was moved to a detached HEAD at 9231820. A stale local branch claude/issue-12519-nav-edit-rail with no commits was checked out there, and it had to be deleted before the worktree could be created, as the dispatch instructs.",
"AppNavInspector.tsx was not touched; the api name's move did not need it."
],
"line_budget": "n/a",
"open_questions": [
{
"question": "Assumption 3 scoping: should the compact row apply to the Studio only (a new prop) or to both AppNavCanvas mounts?",
"options": [
"A: both mounts, no new prop (built)",
"B: a Studio-only compact prop, leaving AppPreview's row as it was"
],
"recommendation": "A. On long-term soundness it keeps one row and one look; on startup focus it adds no prop and retires StudioRail.width; on business need the only pull for the shape is the 240 px rail, and the api name is still one hover away; on AI-error resistance it is neutral, since no metadata key moves. The app designer screenshot shows the result."
},
{
"question": "Assumption 1 measured: the rail was w-72 (288 px), not about 300 px. Dropping the override was the whole width fix, and target 3 followed from it (column x 240 in both modes and both themes).",
"options": [
"recorded"
],
"recommendation": "No action."
},
{
"question": "Assumption 2 measured: the producer is AppNavCanvas, as the claim said. The card's 'a keyboard user never sees it' was already stale on main, because objectui#11776 had made the trash focusable and shown on focus. What remained was the hover-only drawing and the label shift.",
"options": [
"recorded"
],
"recommendation": "No action."
},
{
"question": "Assumption 4 measured: HotCRM's deepest nesting is 1 (crm.app.ts, the only *.app.ts; six groups with leaf children). At depth 1 a row is 189 px wide, its label gets about 104 px and it does not overflow. Child rows carry no trash because they are edited in the inspector.",
"options": [
"recorded"
],
"recommendation": "No action."
}
],
"out_of_scope_findings": [
"carrier: none · The rail heading 'APP · NAVIGATION' truncates at 240 px beside the Edit/Done toggle in both modes. This was the same before this change and is cosmetic. Noted, not filed.",
"carrier: none · PR objectui#12544 (#12455) committed screenshots into main under docs/design/studio-redesign/after/, against the maintainer's ruling the card body cites. Read only, not filed."
]
}
Relayed verbatim by seat
domain:ui#4(sessionsession_01SJn1GEkrbbBQkhGC26wEDw) from the cloud session's final message, the degraded write channel. The seat opened the draft PR #12566 frompr_title_draftandpr_body_draft, with assigneeos-sales."pr": nullabove is the session's own value at report time.Generated by Claude Code