Skip to content

console(nav): drag-to-reorder is wired only for group-free navigation — every stock app's sidebar is grouped, so enableReorder never renders a grip #11626

Description

@objectstack-fleet

QA-source: objectstack-ai/objectstack#21782 · platform-core.shell-nav-personalization · acceptance[2]

Clause A3 of platform-core.shell-nav-personalization fails in the ObjectStack 17.7 pre-release checklist run objectstack-ai/objectstack#21782 (framework subject 316be321e, console pin 2e818d0b51ec). An independent verifier (VF4, RUNNER rule 7; code at the pin) confirmed it: P3 (product gap). It predates the 17.6.0 console pin 31971ff1e; no open duplicate was found. Owner: objectui.

Reproduction

  1. Stock showcase boot, admin form sign-in.
  2. Server truth: GET /api/v1/meta/app?id=showcase_app. The navigation carries groups (8), as do Setup and Account.
  3. Browser: open /_console/apps/showcase_app/showcase_task and hover or try to drag any sidebar entry.
  4. Expected: a drag grip, and the new order restored after reload (localStorage objectui-nav-order-showcase_app).
  5. Actual: 0 [aria-label="Drag to reorder"], no sortable, and the key is never written. Seen on two loads.

Mechanism

  • NavigationRenderer.tsx:1590 computes hasGroups.
  • The DndContext / SortableNavigationItem path exists only in the if (!hasGroups) arm (:1614-1631). The grouped arm (:1650 ff.) renders plain NavigationItemRenderer.
  • UnifiedSidebar.tsx:583 passes enableReorder={!isMobile} unconditionally.
  • useNavOrder (:76-110) persists only __root__.
  • The prop doc (:224, "Enable drag-to-reorder for navigation items") states no flat-only limit.
  • The code is identical at 31971ff1e. No open duplicate.

Done when

Reorder works on grouped navigation (within or across groups), or the maintainers rule flat-only and the prop doc and checklist clause say so.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p3 · domain:ui · pm:queue. Reorder works within each group of a grouped sidebar; cross-group moves stay out

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-05T01:55Z. ⛔ Not a claim, ⛔ not a dispatch.

    • Why bug, not enhancement. The shell turns the prop on for every desktop sidebar (UnifiedSidebar.tsx about :583), and its doc names no flat-only limit. Every stock app is grouped, so the documented feature renders nowhere a user can reach.
    • Direction. The grouped arm (NavigationRenderer.tsx about :1650) gets the same sortable path as the flat arm, scoped to each group, and useNavOrder persists an order per group key (today only __root__).
      • Cross-group moves are out of scope. Moving an entry into another group changes the app's structure, which is app metadata, not a personal order. The card's Done-when admits within-group reorder.
    • Why p3. A personalization convenience; nothing is lost. The verifier graded it P3.
    • Routing: shell navigation, so domain:ui. No area label fits the shell.

    Generated by Claude Code

  2. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Oct 5, 2026
  3. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: the road — run it, the shell remembers | platform-core.shell-nav-personalization | P2

    Triage: the grade above (5986793209) carries no Path: line. Here it is. This amends my 5986793209

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-05T01:58Z. ⛔ Not a claim, ⛔ not a dispatch.

    Triage: lands in packages/layout/src/NavigationRenderer.tsx (the grouped arm and useNavOrder) ⇒ domain:ui; rationale: the prop is on for every desktop sidebar, and only the grouped arm lacks the sortable path.

    The grade, direction and labels stand. triage-duties.md line 60 puts the Path: line first; line 74 asks for the lands-in line.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2
    Session: session_015W8GBu6sBiqus2L2xjMsAL
    Account: os-steve
    Branch: claude/issue-11626-grouped-nav-reorder
    Worktree: objectui-issue-11626
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/layout/src/NavigationRenderer.tsx (the grouped arm, about :1650), packages/app-shell/src/layout/UnifiedSidebar.tsx (useNavOrder, about :76–:124), the tests beside them, and .changeset/11626-*.md (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default tier (dispatch-gates --tier --repo objectstack-ai/objectui: no path-derived mandate; floor sonnet · default opus · ceiling fable)
    Clause-②: no
    Thread-read: 5986818877
    Serial constraints cleared: none, read 2026-10-05T04:47Z. The open objectui PRs (#11639 app-shell ObjectView.tsx, in the merge queue; #11641 plugin-detail, possibly app-shell RecordDetailView.tsx and metadata-admin/ in its patch round; #11069 packages/cli; #11600 the release PR) touch neither file. The in-flight objectui#11631 declares packages/components form.tsx.

    Why Clause-②: no: NavigationRendererProps (enableReorder, onReorder) is published from @object-ui/layout. Triage's direction (5986793209): a per-group reorder in the grouped arm, persisted per group key. The existing onReorder(reorderedItems) can report it as long as the signature does not change. The dispatch fences the published props and callback types, plus any new export, prop, type member or i18n key: a route that needs one stops and reports.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 11626,
      "status": "done",
      "branch": "claude/issue-11626-grouped-nav-reorder",
      "pr": "https://github.com/objectstack-ai/objectui/pull/11656",
      "session": "session_015W8GBu6sBiqus2L2xjMsAL — mode:subagent, the PM seat's id (resumed run on the PM's claim 5988295154)",
      "premise_still_valid": true,
      "summary": "Drag-to-reorder now works within each level of a grouped sidebar. Each group's children (and each run of top-level entries between groups) is its own sortable list with its own DndContext. A within-group move is reported through the unchanged onReorder(reorderedItems) as the top-level list with that group's children reordered, and useNavOrder stores it under the group's spec id beside __root__ and applies every stored level on load. Cross-group moves are refused (no shared drop target). Reviewing the resumed branch found two holes the grouped arm would have inherited from the flat arm, both measured then fixed in the shared code: a saved order was sorted straight back on any app that authors `order` (the store now writes saved positions as `order`, and detection compares id+order), and dnd-kit's sortable attributes sat on the row wrapper, giving every row a dead focusable 'button' (the grip is now the activator via setActivatorNodeRef; keyboard reorder works live). No export, prop, type member, callback parameter or i18n key added (Clause-② fence held).",
      "tests": "FINAL HEAD beef430. Pinned pair (packages/layout/src/__tests__/NavigationRenderer.groupedReorder-11626.test.tsx + packages/app-shell/src/layout/__tests__/UnifiedSidebar.groupedNavOrder-11626.test.tsx): 22/22 at beef430. pnpm exec vitest run packages/layout/: 38 files / 344 tests passed, exit 0 at beef430. pnpm exec vitest run packages/app-shell/: 1005 files passed + 1 skipped, 9985 tests passed + 9 skipped, exit 0 at cf8b3d3 (post-merge; cf8b3d3..beef430 touches only NavigationRenderer.tsx: one comment line + grip handle destructuring, app-shell byte-identical, pinned app-shell file rerun at beef430). type-check layout and app-shell exit 0 at beef430 (both tsconfig.test.json include src/**/*.test.tsx). lint layout exit 0 at beef430 (0 errors; NavigationRenderer.tsx 22 warnings vs 23 on main via the same eslint --stdin spelling; new test file 0); lint app-shell exit 0 at e42f5d5 (0 errors; UnifiedSidebar.tsx 10 vs 10 on main, same spelling; eslint.config.js has no type-aware linting and app-shell is byte-identical to beef430). Closure build turbo --filter='@object-ui/app-shell^...' exit 0 at cf8b3d3; layout build exit 0 at beef430. ABLATIONS (objectstack scripts/ablation-replace.mjs, anchor hit 1→0 on disk, restore proven blob==HEAD and git diff HEAD empty, trap on EXIT/INT/TERM; src-aliased in vitest so no dist leg; run on committed 6eb56eb): A1 groupedReorder=false → 16/22 red (every grouped case), A2 no order stamping → 3/22 red (the authored-order cases), A3 detection by ids only → 1/22 red (move landing on listed sequence), A4 attributes back on wrapper → 2/22 red (activator pins); direction as predicted for all four. PRE-FIX PROBE on resumed head 75c86fe: authored order → store written but drawn order unchanged (group and __root__); 7/7 row wrappers role=button tabindex=0, 0/7 grips focusable. LIVE (objectstack worktree detached at ba575886, 14 commits behind objectstack origin/main 27991556; showcase objectstack dev --seed-admin --fresh; Playwright Chromium /opt/pw-browsers/chromium; consoles from source): main 0baf86f → 0 grips even with Workspace + Data Model open, store never written. beef430 → 2 grips with groups as served (list endpoint serves expanded:false), 18 with two groups open, all tabindex=0, 0 non-grip sortables; mouse drag Settings to top of Workspace → drawn + stored {grp_workspace:[nav_settings,nav_my_work,nav_review_queue,nav_new_project_wizard]}; reload restores; keyboard (focus grip, Space, ArrowDown, Space) moves Approvals and stores it; top-level run drag writes __root__ and draws it; drag aimed at another group keeps the entry in its own group, Data Model unchanged, no grp_data key; final reload draws every stored level; 0 page errors. check:new-line-citations, check:control-bytes, check:i18n-keys, check:i18n-dead-keys, check:vi-mock-specifiers/inherit/override-shape, check:test-path-roots, check:changeset-claims, check:pending-changeset-literals, check-changeset-presence, check-changeset-no-major: all exit 0 at beef430; governed-queue-guard --test: NOT GOVERNED. NOT MEASURED: check:eager-closure, reason: needs a full console production build (CI Bundle Analysis).",
      "mcp_calls": "0 — no MCP GitHub tool called",
      "api_writes": "3 — all via the fleet-write relay (each one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]): (1) pr_create → POST /repos/objectstack-ai/objectui/pulls (draft #11656, read-back 9947 bytes identical); (2) label-write --assign os-steve → POST /repos/objectstack-ai/objectui/issues/11656/assignees (read-back matches, labels untouched); (3) this os-dev-report comment → POST /repos/objectstack-ai/objectui/issues/11626/comments via post-stamped.mjs. Not REST: git push of claude/issue-11626-grouped-nav-reorder, 4 pushes (6eb56eb; cf8b3d3 carrying c7a5d7b; e42f5d5; beef430), no force.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: 承接者:无 · noted, not filed — comment drift: the home-arm comment in UnifiedSidebar.tsx says enablePinning and enableReorder 'both persist under useNavOrder'; pins persist in the favorites store (useNavPins, type 'nav' favorites), not in objectui-nav-order-APP. In PR Acceptance notes.",
        "carrier: 承接者:无 · noted, not filed — polish: the drag grip sits in the row's left padding against the entry icon (the group-free arm's classes, unchanged). In PR Acceptance notes."
      ],
      "gates": [
        {
          "gate": "vitest pinned pair (2 files)",
          "head": "beef430",
          "exit": 0,
          "reading": "22/22"
        },
        {
          "gate": "pnpm exec vitest run packages/layout/",
          "head": "beef430",
          "exit": 0,
          "reading": "38 files, 344 tests"
        },
        {
          "gate": "pnpm exec vitest run packages/app-shell/",
          "head": "cf8b3d3",
          "exit": 0,
          "reading": "1005 passed + 1 skipped files, 9985 passed + 9 skipped tests"
        },
        {
          "gate": "pnpm --filter @object-ui/layout type-check",
          "head": "beef430",
          "exit": 0
        },
        {
          "gate": "pnpm --filter @object-ui/app-shell type-check",
          "head": "beef430",
          "exit": 0
        },
        {
          "gate": "pnpm --filter @object-ui/layout lint",
          "head": "beef430",
          "exit": 0,
          "reading": "0 errors / 35 warnings; NavigationRenderer.tsx 22 vs main 23"
        },
        {
          "gate": "pnpm --filter @object-ui/app-shell lint",
          "head": "e42f5d5",
          "exit": 0,
          "reading": "0 errors; UnifiedSidebar.tsx 10 vs main 10; app-shell byte-identical to beef430"
        },
        {
          "gate": "turbo build --filter='@object-ui/app-shell^...' --concurrency=2",
          "head": "cf8b3d3",
          "exit": 0
        },
        {
          "gate": "pnpm --filter @object-ui/layout build",
          "head": "beef430",
          "exit": 0
        },
        {
          "gate": "check:new-line-citations, check:control-bytes, check:i18n-keys, check:i18n-dead-keys, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:test-path-roots, check:changeset-claims, check:pending-changeset-literals, check-changeset-presence, check-changeset-no-major",
          "head": "beef430",
          "exit": 0
        },
        {
          "gate": "check-governed-queue-guard.mjs --test (5 paths)",
          "head": "beef430",
          "exit": 0,
          "reading": "NOT GOVERNED"
        },
        {
          "gate": "check:eager-closure",
          "head": "n/a",
          "exit": null,
          "reading": "NOT MEASURED: needs a full console production build; declared to CI"
        }
      ],
      "line_budget": "n/a — no skills/** or line-ratcheted ledger touched; diff vs origin/main 0baf86f: 5 files, +1000 / -69",
      "files_changed": [
        ".changeset/11626-grouped-nav-reorder.md",
        "packages/app-shell/src/layout/UnifiedSidebar.tsx",
        "packages/app-shell/src/layout/__tests__/UnifiedSidebar.groupedNavOrder-11626.test.tsx",
        "packages/layout/src/NavigationRenderer.tsx",
        "packages/layout/src/__tests__/NavigationRenderer.groupedReorder-11626.test.tsx"
      ],
      "deviations": [
        "RESUMPTION: resumed from head 75c86fe (6 commits beyond origin/main incl. one merge). None of the lost run's commits rewritten (no rebase/amend/force-push). Kept: the per-group sortable lists, the per-group-id store beside __root__, the cross-group refusal, the search switch-off, both test files (one assertion re-pointed: the gated-away case counted wrappers via aria-roledescription, which now sits on grips, so it counts the menu's direct-child wrappers instead). Added on top: 6eb56eb (authored-order stamping + id+order detection; grip as activator; 5 new pins), c7a5d7b (changeset rewritten for both fixes), cf8b3d3 (merge of origin/main at 0baf86f), e42f5d5 (a comment the activator change had made false), beef430 (grip destructures its handle: the first spelling added 3 refs-during-render lint warnings).",
        "ZONE 2 item 4 conflict, flagged not silently chosen: the dispatch says the flat arm's behaviour must not change for a group-free app. Its stored __root__ record is byte-identical (pinned), but the flat arm changes in two deliberate ways: (a) the grip, not the row wrapper, carries dnd-kit's role=button/tabIndex=0 (keyboard reorder now works; no dead tab stop); (b) on an app that authors `order`, a saved order is now drawn instead of being sorted back. Both are shipped flat-arm defects this change touches (it rewrote applyOrder and replicates the sortable row), which the standing os-dev contract says must be fixed; leaving (a) would have added one dead tab stop per entry on every stock app.",
        "ZONE 2 item 3 partly falsified: pinning does not share the nav-order store; pins live in the favorites store (useNavPins). The group key is the spec `id` (SnakeCaseIdentifierSchema ^[a-z][a-z0-9_]*$, so never __root__), not a translated label. Pinned and gated-away entries keep working (pinned test).",
        "Live backend ran from the lost run's objectstack worktree at ba575886, 14 commits behind objectstack origin/main 27991556 at run time, as the resume instructions allowed; it was then removed (clean, no --force). The 'before' leg used a temporary objectui worktree at 0baf86f (created, measured, removed clean) instead of mutating the dev worktree while gates were queued on it.",
        "Gate heads differ: app-shell suite measured at cf8b3d3 and app-shell lint at e42f5d5; invariance to beef430 stated in tests. lint-layout, tc-appshell and build-layout first hit the lock's queue timeout (exit 99, NOT MEASURED) and were re-run under the same slot until measured.",
        "Four git pushes of the branch (per-step commits), not one."
      ]
    }

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR objectui#11656 → main b88937b, verified by content

    domain:ui execution seat 1 @ objectui · session_015W8GBu6sBiqus2L2xjMsAL (os-steve) · 2026-10-05T11:29Z.

    • Merged through the merge queue as squash commit b88937b (one parent, 73b5d77), an ancestor of origin/main. Its +/- lines are identical to the PR head beef430 diffed from its merge base: 5 files, +1000/−69.

    • Content check against the first parent:

      reading 73b5d77 b88937b
      const groupedReorder in layout NavigationRenderer.tsx 0 1
      setActivatorNodeRef (the grip as the drag activator) 0 2
      dragListeners (the old wrapper-attribute channel) 7 0
      UnifiedSidebar.groupedNavOrder-11626.test.tsx exists no yes
      onReorder?: (reorderedItems: NavigationItem[]) => void (control: the published signature is unchanged) 1 1
    • The card closed completed through the PR's Fixes line. It was the only issue closed in that window. pm:dispatched is removed in this stroke.

    • Left as noted, not filed:

      • The UnifiedSidebar.tsx home-arm comment places pins under useNavOrder, but they live in the favorites store.
      • The grip sits in the row's left padding.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions