From b2a4ab115472b99ebb48e9e8973481fc19ff97e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Oct 2026 17:31:53 +0000 Subject: [PATCH] docs(qa): shell-nav-personalization judges the pinned order, and the authored menu as not reorderable The runtime sidebar's menu is no longer user-reorderable; personal ordering lives only in the Pinned section (objectui 8a55f0ccda). The item's drag-reorder step and clause, which judged the retired useNavOrder localStorage order, are replaced by a desktop-only pinned-order clause (drag the row or Space/arrows/Space; persists with the nav favorites) and a negative clause on the authored menu (no grip, no sortable role, no grab cursor; authored order whatever objectui-nav-order key is stored). Revision 2, with its history entry. Claude-Session: https://claude.ai/code/session_01LYXc6ckoWuZyVZpWYizdMh Co-authored-by: Claude --- .../areas/platform-core.json | 44 +++++++++++++------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/docs/qa/platform-checklist/areas/platform-core.json b/docs/qa/platform-checklist/areas/platform-core.json index af93fef3e71..f21261bf6a1 100644 --- a/docs/qa/platform-checklist/areas/platform-core.json +++ b/docs/qa/platform-checklist/areas/platform-core.json @@ -962,27 +962,29 @@ }, { "id": "platform-core.shell-nav-personalization", - "title": "Shell nav personalization: sidebar collapse, pin/reorder, favorites, recents and header breadcrumbs each persist and stay navigable across reload", + "title": "Shell nav personalization: sidebar collapse, pins and the user's pinned order, favorites, recents and header breadcrumbs each persist and stay navigable across reload; the authored menu keeps the app's order", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", "surface": "browser", "personas": ["seeded admin (admin@objectos.ai / admin123)"], "fixtures": { "app": "showcase", "requires": [ - "stock showcase nav (multiple objects/dashboards/reports so there is something to pin, reorder, favorite, and visit as recents)" + "stock showcase nav (multiple objects/dashboards/reports so there is something to pin — at least three entries, so the pinned order can change — favorite, and visit as recents)", + "a desktop viewport, at least 768px wide, for the Pinned section: below it useIsMobile is true and UnifiedSidebar passes enablePinning={!isMobile}, so a phone draws no Pinned section and has no pinned order to judge" ] }, "steps": [ "sidebar collapse: toggle the sidebar to icon (collapsed) mode; confirm nav items still NAVIGATE in icon mode; reload — the collapsed state persists (cookie sidebar_state, packages/components/src/ui/sidebar.tsx SIDEBAR_COOKIE_NAME)", "nav pin/unpin: pin a nav item (UnifiedSidebar → useNavPins.togglePin → favorite id nav:, backed by FavoritesProvider/UserDataAdapter); it appears in the Pinned section; reload — the pin persists (syncs via UserDataAdapter; migrates the legacy objectui-nav-pins key); unpin removes it", - "drag-reorder: reorder nav items by drag (UnifiedSidebar useNavOrder, enableReorder / onReorder=handleReorder); reload — the order persists (localStorage per-app key)", + "pinned order (desktop only): with at least three entries pinned, reorder them inside the Pinned section — the group NavigationRenderer draws above the menu, headed console.nav.favorites ('Favorites' in en, 「收藏」 in zh, a star icon), not the content-favorites group UnifiedSidebar draws below Recent. By pointer: drag a pinned row by the row itself (no grip at rest; the row's link carries cursor-grab; a press becomes a drag only after 5px of travel; a 2px insertion line, data-drop-indicator, marks where it lands). By keyboard: focus a pinned row's link, Space picks it up, the arrow keys move it, Space drops it, Escape cancels; Enter is not a drag key, it follows the link. Reload — the pinned order persists: NavigationRenderer onPinnedReorder → useNavPins.reorderPins → FavoritesProvider reorderNavPins stores it as the order of the type 'nav' favorites (nav:) in the UserDataAdapter-backed favorites list. A new pin joins the end; an unpin leaves the others in order", "favorites star/unstar: star an object/record as a favorite (useFavorites / FavoritesProvider); it appears in favorites; reload — persists; unstar removes it", "recents rail: visit several records/objects/dashboards; the sidebar Recent section (collapsed by default, top 5 — useTrackRouteAsRecent → RecentItemsProvider, localStorage objectui-recent-items scoped by userId, hydrates UserDataAdapter) lists them; reload — recents survive", "header record-trail breadcrumbs: on a record detail the AppHeader breadcrumb shows the app/section/record trail; click a crumb to navigate back to its level; reload — the trail rebuilds from the route", - "record which persistence each feature uses: pins/favorites/recents are UserDataAdapter-backed (cross-device sync), reorder + collapse are local (localStorage / cookie)" + "authored menu (any viewport): the app's own menu — every row outside the Pinned section — offers no reorder: no drag grip, no aria-roledescription=\"sortable\", no aria-describedby=\"nav-pinned\" (the pinned list's DndContext id, which names dnd-kit's drag instructions) and no cursor-grab on its links, and dragging a menu row moves nothing (a browser's native link-drag ghost is not a reorder). Its order is the app's authored navigation order: each level sorted by order, lower first, ties in authored array order (objectui packages/layout/src/navOrder.ts byNavOrder). Then plant the retired per-user key — localStorage objectui-nav-order- holding a JSON map of __root__ and each group id to that level's ids reversed, the shape the retired useNavOrder read — and reload: the menu order does not change, because UnifiedSidebar no longer reads the key", + "record which persistence each feature uses: pins and their order, favorites and recents are UserDataAdapter-backed (cross-device sync; pins, their order and favorites are one sys_user_preference row, key ui.favorites), collapse is local (cookie sidebar_state)" ], "acceptance": [ { @@ -998,10 +1000,10 @@ "evidence": "pre/post-reload DOM" }, { - "clause": "drag-reorder persists: the reordered nav order is restored after reload (useNavOrder localStorage)", + "clause": "pinned order (desktop): a pinned entry is reordered by dragging its row and by keyboard (Space, arrows, Space), and the Pinned section's order is restored after reload (the order of the nav: favorites — useNavPins.reorderPins → FavoritesProvider reorderNavPins, UserDataAdapter-backed)", "oracle": "dom", - "verify": "the nav item order before reload equals the order after reload", - "evidence": "order before/after reload" + "verify": "after a screenshot confirms render, at a viewport at least 768px wide: read the Pinned section's link order; move one row by a pointer drag on the row itself (with intermediate pointer moves — a press must travel 5px before it is a drag) and another by keyboard (focus its link, Space, ArrowUp/ArrowDown, Space) — each drop leaves the section in the dropped order; pick a row up and press Escape — the order is unchanged; reload — the order read after reload equals the order read before it. Control that the probe sees a sortable row: each pinned link carries aria-describedby=\"nav-pinned\" and cursor-grab, and no grip element is drawn", + "evidence": "the Pinned section's order before the drags, after each drop, after the Escape, and after reload" }, { "clause": "favorites star/unstar persists across reload", @@ -1020,25 +1022,39 @@ "oracle": "screenshot", "verify": "breadcrumb screenshot + a crumb navigation that routes back + the trail present after reload", "evidence": "breadcrumb screenshot + the crumb navigation" + }, + { + "clause": "the authored menu is not user-reorderable (any viewport): no row outside the Pinned section draws a drag grip or carries aria-roledescription=\"sortable\", aria-describedby=\"nav-pinned\" or cursor-grab, and the menu renders in the app's authored order even with a stored objectui-nav-order- key", + "oracle": "dom", + "verify": "after a screenshot confirms render: the sidebar holds zero [aria-roledescription=\"sortable\"] elements; every menu link outside the Pinned section has no aria-describedby=\"nav-pinned\" and no cursor-grab class (control, on desktop with a pin: the Pinned section's links DO carry both, so the probe can see them); the rendered order of each menu level equals the app's authored navigation order (by order, ties in array order); plant objectui-nav-order- with every level reversed, reload, and read the menu order again — unchanged. Repeat the attribute probe at a phone viewport (under 768px) with the mobile sidebar open", + "evidence": "the attribute-probe counts with the pinned-row control; authored vs rendered menu order, before and after planting the stored key" } ], "negative": [ "a collapsed sidebar whose icon items no longer navigate (dead icon mode) is a FAIL", - "a pin / favorite / reorder / recent that does NOT survive reload is a FAIL of its persistence contract", + "a pin, the pinned order, a favorite or a recent that does NOT survive reload is a FAIL of its persistence contract", + "on the authored menu (every row outside the Pinned section, any viewport) a drag grip, an aria-roledescription=\"sortable\" element, dnd-kit's drag-instructions aria-describedby, a grab cursor, a drag that moves a row, or an order other than the app's authored order (with or without a stored objectui-nav-order- key) is a FAIL: arranging the menu is Studio's work, not the end user's", + "a phone viewport (under 768px) draws no Pinned section (UnifiedSidebar enablePinning={!isMobile}): its absence there is expected, not a FAIL of the pinned order", "a breadcrumb crumb that is inert (does not navigate) is a FAIL", "transitional emptiness right after navigation (empty nav/recents) must be ruled out by the screenshot-first protocol before any persistence FAIL is recorded" ], - "traps": ["hydration-race", "shared-browser-tab"], + "traps": ["hydration-race", "shared-browser-tab", "stale-console-bundle", "automation-input"], "source": [ - "objectui packages/app-shell/src/layout/UnifiedSidebar.tsx (useNavOrder drag-reorder localStorage; Recent section; applyPins)", - "objectui packages/app-shell/src/hooks/useNavPins.ts (togglePin → nav: favorite, MAX_PINS 20, UserDataAdapter-backed, legacy objectui-nav-pins migration)", - "objectui packages/app-shell/src/hooks/useFavorites.ts + context/FavoritesProvider.tsx (favorites state)", + "objectui packages/app-shell/src/layout/UnifiedSidebar.tsx (no menu reorder: useNavOrder is gone and a stored objectui-nav-order- key is not read; the app arm passes NavigationRenderer enablePinning={!isMobile}, pinnedOrder={pinnedIds}, onPinnedReorder={reorderPins}; Recent section; applyPins)", + "objectui packages/layout/src/NavigationRenderer.tsx (enableReorder / onReorder RETIRED; the Pinned section, headed console.nav.favorites, is PinnedNavigationList: its own DndContext id nav-pinned, PointerSensor after 5px, KeyboardSensor start and end Space and cancel Escape, the row's link as the drag activator with cursor-grab and aria-describedby, DropIndicator data-drop-indicator; orderPinnedItems by pinnedOrder; menu levels sorted by byNavOrder only)", + "objectui packages/layout/src/navOrder.ts (byNavOrder — the authored order: order ascending, ties in array order)", + "objectui packages/app-shell/src/hooks/useNavPins.ts (togglePin → nav: favorite, MAX_PINS 20, UserDataAdapter-backed, legacy objectui-nav-pins migration; pinnedIds in the user's order; reorderPins)", + "objectui packages/app-shell/src/hooks/useFavorites.ts + context/FavoritesProvider.tsx (favorites state; reorderNavPins and withNavPinOrder — the pinned order is the order of the type 'nav' favorites; withAddedFavorite appends a new nav pin)", + "objectui packages/components/src/hooks/use-mobile.tsx (useIsMobile, MOBILE_BREAKPOINT 768 — the phone/desktop line for the Pinned section)", + "objectui packages/app-shell/src/console/ConsoleShell.tsx (createObjectStackUserStateAdapter key ui.favorites — the favorites list, nav pins and their order included, is one per-user row)", + "packages/platform-objects/src/identity/sys-user-preference.object.ts#sys_user_preference (the per-user row that favorites list is stored in)", "objectui packages/app-shell/src/context/RecentItemsProvider.tsx + hooks/useTrackRouteAsRecent.ts (recents — objectui-recent-items localStorage + UserDataAdapter hydrate)", "objectui packages/app-shell/src/layout/AppHeader.tsx (breadcrumb record-trail)", "objectui packages/components/src/ui/sidebar.tsx (SIDEBAR_COOKIE_NAME sidebar_state — collapse persistence)" ], "history": [ - { "revision": 1, "date": "2026-08-08", "change": "initial — sidebar collapse/icon-mode, nav pin+reorder, favorites, recents rail, header breadcrumbs, each with its real persistence layer (cookie/localStorage/UserDataAdapter)", "ref": "claude/platform-test-checklist-ocwugl" } + { "revision": 1, "date": "2026-08-08", "change": "initial — sidebar collapse/icon-mode, nav pin+reorder, favorites, recents rail, header breadcrumbs, each with its real persistence layer (cookie/localStorage/UserDataAdapter)", "ref": "claude/platform-test-checklist-ocwugl" }, + { "revision": 2, "date": "2026-10-09", "change": "end users no longer reorder the runtime sidebar's menu. Maintainer ruling (#22487): 「右侧浏览器,左侧菜单在用户界面不应该显示拖动按钮,拖动设计是studio的工作。」 Scope note: personal ordering survives only in the Pinned section. Landed in objectui 8a55f0ccda (objectui PR #12066, objectui#12059), and every new text cites that code. Step 3 and acceptance[2], which drag-reordered the menu (useNavOrder, localStorage objectui-nav-order-), are REPLACED, not only removed: they now judge the Pinned section's order (desktop only, since a phone draws no Pinned section), dragged by the row or reordered by keyboard (Space, arrows, Space), and persisting across reload as the order of the nav favorites in the UserDataAdapter-backed favorites list. A new step and acceptance[6] judge the negative: no grip, sortable role, drag instructions or grab cursor on the authored menu, and the authored order whatever objectui-nav-order key is stored. Title, fixtures.requires, the persistence step (now 'collapse is local'), the reload negative and source are re-pointed to the landed code. Traps gain stale-console-bundle (the console pin f0268ad784 predates 8a55f0ccda, so a console built at it still draws the menu grip) and automation-input (a one-jump synthetic pointer move never crosses dnd-kit's 5px activation distance and reads as a dead drag)", "ref": "#22487" } ] }, {