Repository navigation
feat(layout,app-shell,i18n): the runtime sidebar menu is not user-reorderable; the Pinned section keeps the user's order (objectui#12059) - #12066
Conversation
… pinned section keeps the user's order (objectui#12059) The runtime sidebar drew a drag grip on every menu row and kept a private per-user menu order in localStorage. Arranging an app's navigation is Studio's job, so the menu reorder is retired and personal ordering moves to the pinned section, persisted with the pins in the favorites store. - layout: NavigationRenderer retires enableReorder / onReorder and every menu sortable path; adds pinnedOrder and onPinnedReorder. A pinned row is dragged by the row itself (no grip, cursor-grab, insertion line), and from the keyboard through dnd-kit's KeyboardSensor (Space / arrows / Space, Escape cancels). AppSchemaRenderer stops forwarding the retired props. - app-shell: UnifiedSidebar drops useNavOrder (objectui-nav-order-APP is no longer read or written) and passes the pinned order. The favorites store appends a new nav pin and gains reorderNavPins; useNavPins gains reorderPins. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
…sidebar's wiring (objectui#12059) The sidebar draws no menu grip and ignores a stored objectui-nav-order key; a keyboard reorder of pinned rows is saved through the favorites adapter and the next load draws it; a new pin joins the end, an unpin leaves the order. The store pins the slot-filling reorder and the cap. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
…(objectui#12059) The sidebar menu grip it labelled is gone, and a pinned row is dragged by its own link with no grip, so nothing asks for the key. Removed from all ten packs; view.dragToReorder is a different key and stays. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
…tui#12059) The user-state guide and the app-shell README describe the pinned order stored with the pins, reorderNavPins / reorderPins, and that the app's menu is not reorderable in the console. The changeset declares minor for layout, app-shell and i18n with the breaking notes. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
…-built MouseEvent (objectui#12059) object-ui/no-synthetic-event-trigger refuses dispatching a constructed MouseEvent; the instrument now reads fireEvent.click's answer and whether the click reached the window. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
…r is retired before release (objectui#12059) Its body would have published that drag-to-reorder works on a grouped sidebar menu and that the reorder props keep their types; objectui#12059 retires both in the same release window. Prose only; the front matter is untouched. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
…(objectui#12059) Measured in Chromium: a keyboard drag moved the dragged row onto the row it was over, so the two labels overlapped. A pointer drag still carries the row under the pointer; a keyboard drag dims it in place and the insertion line alone shows where it lands. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Inputs, and nothing else: card #12059 (body and all six comments: the amendment link ① Derived judgmentsEach change the diff implies, judged against the ruling (card body), the scope addition, the claim and its amendment. Public surface
Runtime sidebar (the ruling) Pinned section (the scope addition) Persistence (the claim's stop condition) Mobile and touch Tests and docs Nothing in the diff is judged wrong. ② Semver level
The PR body's second line reads ③ Boundary flagsDev flags (report
Implemented-by: VERDICT: PASS |
Fixes #12059
Clause-②: yes
The runtime sidebar no longer offers drag-to-reorder on the app's menu; the Pinned section is the one place a user orders entries. This reverses objectui#11626, which had extended menu drag-to-reorder to grouped sidebars; that card's pins move here (its two test files are deleted and replaced, its pending changeset is corrected, see below).
Maintainer's ruling (card body, verbatim): 「右侧浏览器,左侧菜单在用户界面不应该显示拖动按钮,拖动设计是studio的工作。」 Scope addition
6081436048: 「补到 12059 上,按置顶区来做」, 「暂时不考虑编辑模式」.What changed
Runtime sidebar (
UnifiedSidebar, app-shell). No drag grip and no menu reorder on any viewport: it stops passingenableReorder/onReorder, anduseNavOrderis removed, so theobjectui-nav-order-APPkey is neither read nor written. A key left from before changes nothing (pinned below). Clearing it was allowed, not required; it is not cleared. The menu renders in the app's authored order.Pinned section (the scope addition).
cursor: grab, and a 2px insertion line marks where the row will land while dragging.KeyboardSensorwithsortableKeyboardCoordinates: Space picks up, arrows move, Space drops, Escape cancels. Enter is deliberately NOT a start key (dnd-kit binds it by default): it still follows the link. No extra buttons, no extra focus stop: the link stays a link (dnd-kit'srole="button"/tabIndexare not applied) and carriesaria-describedbyto dnd-kit's instructions; the live region announces pick-up, move and drop.Linknever sees it, but nothing prevents its default).type: 'nav'favorites in theUserDataAdapter-backed favorites list (nav:NAVID), so it syncs exactly as the pins do. No server-side change:createObjectStackUserStateAdapterstores the whole list as one JSONvalueinsys_user_preferenceand hands it back as stored.Published surface (Clause-②: yes)
@object-ui/layout—NavigationRendererProps: removedenableReorder,onReorder; addedpinnedOrder?: string[],onPinnedReorder?: (pinnedIds: string[]) => void.AppSchemaRendererProps: removedenableReorder,onReorder(a pass-through only; no in-repo caller passes them, only its own test did). Retired under the zero-pull rule;RETIREDcomments mark both places.@object-ui/app-shell—useFavorites()return gainsreorderNavPins(navIds);useNavPins()return gainsreorderPins(orderedIds), and itspinnedIdsis in the user's order.useNavPins.ts/ the favorites store) but did not enumerate, andFavoritesProvider.tsxis a file beyond the claim's list; both are named in the report for the seat to amend the claim.@object-ui/i18n—console.nav.dragToReorder(the menu grip's label, zh 「拖动以重新排序」) removed from all ten packs: its only reader was the retired grip.view.dragToReorderis a different key and stays..changeset/12059-sidebar-no-reorder.md:minorfor layout, app-shell and i18n, with the breaking notes.Mechanism readings (dispatch zone 2)
FavoriteItem[];pinnedNavIdsis built by walking it, so its iteration order is the array order. The write iscommit→saveFavorites(localStorage) + the debouncedadapter.save(items), which the ObjectStack adapter upserts as the whole list in onevalue. No new field on the server record.NavigationRendereritself (itsfavoritesSection, labelledconsole.nav.favorites), fromcollectPinnedItemsover the tree. No separate component; it now goes through the newPinnedNavigationListwhenonPinnedReorderis passed.enableReorder/onReorder—UnifiedSidebar(app arm) andAppSchemaRenderer(pass-through). Studio's navigation editor does reorder, and not throughNavigationRenderer:AppNavCanvas(the Studio app designer's nav canvas) reorders the root list with native HTML5 drag-and-drop (draggable+onDrop);plugin-designeralso hasNavigationDesigner(a drag-and-drop tree builder) andAppCreationWizard(up/down buttons). No Studio edit.objectui-nav-order-*— read and written only byUnifiedSidebar'suseNavOrder, now removed. The one other tracked spelling is the auth testsessionUserChangePurge-5664.test.tsx, which usesobjectui-nav-order-crmas a stand-in for an un-namespaced key in a generic purge; it reads nothing from the sidebar and is untouched.console.nav.dragToReorder; pinned rows do not reuse it (no grip, the link keeps its own name), so it became dead and left all ten packs.Mobile:
UnifiedSidebarpassesenablePinning={!isMobile}(unchanged, pin/unpin is out of scope), so a phone draws no Pinned section at all and there is nothing to reorder there; no touch sensor was added.Tests
packages/layout/src/__tests__/NavigationRenderer.pinnedOrder-12059.test.tsx(new, 11): no menu row carries a grip,aria-roledescription="sortable", the drag instructions or the grab cursor (grouped and group-free); the four retired props are pinned absent by@ts-expect-error;pinnedOrderorders the section (control: none ⇒ menu order); a pinned row is its own handle (one link + its pin button, no grip; control: noonPinnedReorder⇒ plain row); keyboard reorder down and up through the realKeyboardSensorwith the insertion line on the right edge of the right row and the dragged row held in place; Escape cancels and Enter never picks up (lit control: Space does); a pointer drag released over its own link does not let the browser follow it (control: a plain click does reach the window unprevented).packages/app-shell/src/layout/__tests__/UnifiedSidebar.pinnedOrder-12059.test.tsx(new, 4; realNavigationRenderer, realuseNavPinsover the realFavoritesProvider, an adapter standing in for the server row): no grip in the real sidebar; a storedobjectui-nav-order-crmvalue (every level reversed) changes nothing; a keyboard reorder is saved throughadapter.savewith the unnamed pin and the starred record in place, and a fresh mount with nothing local draws it back from the adapter; a new pin goes to the end, an unpin leaves the order, a re-pin goes to the end.packages/app-shell/src/context/__tests__/FavoritesProvider.navPinOrder-12059.test.tsx(new, 4): append vs prepend, the slot-filling reorder and its persistence, an unknown id and a no-op order, the cap.NavigationRenderer.groupedReorder-11626.test.tsxandUnifiedSidebar.groupedNavOrder-11626.test.tsxdeleted (they pinned the reversed behaviour);NavigationRenderer.listNesting-11690.test.tsxkeeps its axe pin over the new sortable pinned list instead of the menu reorder; theAppSchemaRenderer.test.tsxreorder case is removed; a stale bullet inAppSchemaRenderer.mobileTabOrder-11395.test.tsx's header is updated.Reverse verification (predictions written before each run; every restore proven by blob hash = HEAD and an empty
git diff HEAD)2a48bd40NavigationRenderer.tsxback to2a48bd40NavigationRenderer.tsx+AppSchemaRenderer.tsxback to base, layouttype-checkablation-replace(replacement text was a substring of the anchor, so its count could not rise; nothing ran), redone with a distinct markerAll test imports resolve
@object-ui/layoutthrough the vitest alias tosrc, so nodistrebuild sits between a mutation and its run.Browser (Chromium via Playwright, a temporary dev-only console page, never committed; server stopped by PID)
At rest: 0 grips, 0
aria-roledescription, 0 insertion lines; pinned linkcursor: grab, menu linkcursor: pointerwith noaria-describedby. Keyboard: Space / ArrowDown drew one 2px line after the next row, the live region read the move, Space dropped, focus stayed on the moved link. Pointer: dragging the last pinned row to the top reordered it, no navigation, no reload. A drag released on its own internal link: no navigation; on its own external link: the page stayed (control: a plain click on that link navigated). No page errors.First-load bytes
pnpm check:eager-closureon a console built at HEAD: exit 0, "Console eager closure is 3167.8 KB gzipped" (3,243,862 B; ceiling 3,281,467 B, ruling6070754914). The same build over the base sources: 3,244,066 B. Delta −204 B gzipped (−1,604 B raw).Gates (final reading at
79d36886a)pnpm --filter @object-ui/layout type-check,pnpm --filter @object-ui/app-shell type-check,pnpm --filter @object-ui/i18n type-check: exit 0 (each script name echoed).pnpm exec vitest run packages/layout/+ the app-shell sidebar / pins / favorites suites (src/layout,src/context,src/hooksand every other test namingUnifiedSidebar/useNavPins/FavoritesProvider/useFavorites, console included): 182 files, 1512 tests passed.pnpm exec vitest run packages/i18n/: 82 files, 1316 passed / 13 skipped. The full app-shell suite is CI's.pnpm exec eslinton the 21 touched TS files: 0 errors, 41 warnings, none on an added line.check:control-bytes,check:test-path-roots,check:changeset-claims(one pending changeset namespackages/app-shell/README.md; read, still true),check:pending-changeset-literals,check:new-line-citations(0 new),check:i18n-keys,check:i18n-drift,check:i18n-dead-keys,check:i18n-designer-parity,check:readme-exports,check:doc-fences,check:doc-types,check:doc-snippets,check:phantom-deps,check:unused-deps,check:eager-locale-catalogues,check:icon-record-names,check:unreferenced-sources,check:self-import,check-changeset-presence,check-changeset-no-major,check-changeset-overwrite(report-only; reports the correction below),check-doc-links,check:eager-closure.check-governed-queue-guard --testover the diff: NOT GOVERNED.Docs
content/docs/guide/user-state-persistence.md(reference rows, the pinned-order rules, an "Ordering the sidebar" paragraph) andpackages/app-shell/README.md(user-scoped state). No guide page described dragging menu entries.Acceptance notes
check-changeset-overwritereports that modification, as designed.aria-roledescriptionon pinned links, on purpose: it would replace "link" in what a screen reader announces. The rows are described by dnd-kit's instructions instead.useFavorites()reference row also omitsrefreshLabel.6082337150): platform-checklist: retire the drag-reorder step and acceptance clause of platform-core.shell-nav-personalization — end users do not reorder the runtime sidebar (maintainer ruling 2026-10-09) objectstack#22487 must land no later than the objectstack console pin bump that carries this merge; the seat names the merge commit there after landing.Session:
https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8Generated by Claude Code