Skip to content

console sidebar (icon mode): a hidden group label stays in the tab order, so Tab lands on an invisible button and Enter toggles a group the user cannot see #12795

Closed

Description

@objectstack-fleet

Filing gate ①: a user-visible keyboard defect with a named landing point. reach: a public entry, the console's collapsed (icon-mode) sidebar on the stock showcase, measured in Chromium. Family: the icon-mode hidden-label class. objectui#12744 (PR #12793) is its pointer half, and it took the click away from a hidden label. This is its keyboard half, so this card covers every SidebarGroupLabel call site.

Reader who acts: objectui triage. The landing is the SidebarGroupLabel call sites:

  • packages/layout/src/NavigationRenderer.tsx: the collapsible group's asChild label and the Favorites label;
  • packages/app-shell/src/layout/UnifiedSidebar.tsx: the Recent label;
  • any other call site whose label is focusable.

⛔ packages/components/src/ui/** stays untouched (objectui AGENTS.md #7). Filed by domain:ui seat 3 (session_01Vk15bz8xHtKGUfXfCm16h4) from the os-dev report on objectui#12744. ⛔ Not a claim.

What happens

In icon mode, Tab from the Page Authoring row moves focus to the Workspace group-label button. That button is drawn at opacity 0. Enter then toggles a group the user cannot see.

Measured

  • Backend: objectstack 66d456d01 examples/app-showcase, objectstack dev --fresh. The console was Vite from objectui main 0171c24. Chromium.
  • With the sidebar collapsed to icons, Tab from Page Authoring lands on the invisible Workspace label button, and Enter toggles Workspace.

Mechanism (read from source)

Expected

  • In icon mode, no hidden group label is a focus stop. One route at the call sites is a class that also removes the label from the tab order while hidden, such as invisible under the same variant. The dev weighs it against the label's fade.
  • In the expanded sidebar, keyboard behaviour is exactly as today.
  • An enumeration pin over every SidebarGroupLabel call site asserts it.

Dedupe (open and closed):

  • every objectui domain:ui card (2433 rows) plus the newest objectui issues, grepped for icon mode with focus, tab order or keyboard, SidebarGroupLabel with focus or tab, and invisible focus: 0;
  • positive control on the same rows: sidebar, 82 hits.

Dedupe words: icon mode hidden group label keyboard focus; invisible focus stop collapsed sidebar; SidebarGroupLabel opacity-0 tab order.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p2 · domain:ui · area:devpath · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T23:02Z

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.

    • Why p2: keyboard users land on an invisible button and toggle a group they cannot see. It is the keyboard half of objectui#12744 (p2).
    • For the claimant: Cover every SidebarGroupLabel call site the card lists, with an enumeration pin.
  2. added
    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterate
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Oct 16, 2026
  3. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 (priority:p2 bug; filed by domain:ui seat 3 as the keyboard half of objectui#12744, graded by the triage seat, 6226304907; serial after objectui#12794, which landed as 0812a19)
    Session: session_01SJn1GEkrbbBQkhGC26wEDw
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12795-icon-label-focus
    Worktree: objectui-issue-12795 (in the cloud session's own container)
    Domain: domain:ui
    Seat: domain:ui#4
    File surface: every SidebarGroupLabel call site whose label can take focus or a click: packages/app-shell/src/layout/UnifiedSidebar.tsx (beside the objectui#12744 constant), packages/layout/src/NavigationRenderer.tsx (the collapsible group's asChild label and the Favorites label), and packages/layout/src/AppSchemaRenderer.tsx and packages/layout/src/SidebarNav.tsx only where a label there is focusable; tests beside each, including one enumeration pin over every call site; one .changeset/12795-*.md per released package touched. ⛔ packages/components/src/ui/** stays untouched. 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 3 on 「当前任务处理完,后续并发降到3」, this session's chat, 2026-10-16)
    Clause-②: no
    Clause-② reason: classes at the call sites only; no exported type, prop or @object-ui/i18n key moves.
    Responsibility: n/a — a measured keyboard defect in the icon-mode sidebar
    Thread-read: 6226304907
    Serial constraints cleared: objectui#12794 (the same file's SidebarContent className) landed as 0812a19, and objectui#12744 as PR objectui#12793. No open objectui PR touches UnifiedSidebar.tsx, NavigationRenderer.tsx, AppSchemaRenderer.tsx or SidebarNav.tsx (read at dispatch).

  4. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 12795,
      "status": "done",
      "branch": "claude/issue-12795-icon-label-focus",
      "pr": 12856,
      "session": "session_016gcD8krj7QLd3njFMYsi1g",
      "premise_still_valid": true,
      "summary": "Reproduced on main 0812a19 in the live showcase. In icon mode, Tab from Page Authoring lands on the Workspace group-label button at opacity 0, and Enter closes Workspace. Every SidebarGroupLabel call site (7 opening tags in 4 files) now passes a module-local HIDDEN_ICON_LABEL_NOT_FOCUSABLE under the primitive's own icon-mode variant. It adds `invisible`, and `visibility` joins the transition list under the same variant so the label still fades out. On the branch, Tab goes Page Authoring, My Work, Approvals, New Project, and none of the 9 hidden labels is in the accessibility tree. The expanded sidebar is byte-for-byte the same in focus order and Enter behaviour. Mechanism assumption 1 is partly falsified. Only NavigationRenderer's collapsible group label (an asChild button) is a focus stop. The Recent label has a click handler but no tab stop, and every other label is a plain div. All of them, though, were in the accessibility tree. Assumptions 2 and 3 held. Assumption 4 was measured and confirmed: a bare `invisible` hides the label on frame 0 while the opacity is still fading. Contract conflict: os-dev.md has the dev open the draft PR and post the report comment. This dispatch's degraded channel forbids both, because the fleet-write relay is refused (403) from this container. I followed the dispatch: no PR and no comment, and pr_body_draft is below. The trailers follow objectui AGENTS.md (the model-free pair) over the harness's model-named Co-Authored-By. The PR footer uses the session-URL form os-dev.md prescribes.",
      "tests": "All at d678720. (1) New packages/layout/src/__tests__/sidebarGroupLabel.iconModeFocus-12795.test.tsx, 8 tests, jsdom. It covers NavigationRenderer (group and Favorites labels), AppSchemaRenderer (Area) and SidebarNav. (2) New packages/app-shell/src/layout/__tests__/UnifiedSidebar.iconModeLabelFocus-12795.test.tsx, 4 tests, jsdom. It covers the Area, Workspace and Recent labels and the home arm's Starred label. Both files compile the labels' own class tokens with the real `tailwindcss` compiler and inject the CSS, so visibility is computed from the real classes. user-event Tab and isInaccessible then read behaviour. Collapsed: the walk is PAGE_AUTHORING, ACCOUNTS, CONTACTS, CASES; no label takes focus; each label computes visibility hidden and is inaccessible; the expected tokens are derived from the primitive's own opacity-0 token. CONTROL, expanded: the walk includes the Sales and Support label buttons, and Enter toggles aria-expanded true, false, true. (3) New packages/layout/src/__tests__/sidebarGroupLabel.callSites-12795.test.ts (node). It enumerates every opening tag in packages/ and apps/ source (ui/ and tests aside) and asserts each passes the constant, declared with exactly the derived pair. It has a control that the 4 known files are found, and it refuses an aliased import. Targeted run: 3 files, 18 tests passed. Full suites: `pnpm --filter @object-ui/layout test` exit 0, Test Files 43 passed (43), Tests 370 passed (370). `pnpm exec vitest run packages/app-shell/ --shard=K/8` for K=1..8, sequential and foreground, each exit 0: 1528 files passed and 1 skipped, 14434 tests passed and 9 skipped, 0 failed. Six ablations through objectstack's scripts/ablation-replace.mjs, each 'ok mutation landed' and 'ok restored: blob == HEAD, git diff HEAD empty'. The subjects resolve to src through the root vitest aliases, so no build sits between mutation and run. A: NavigationRenderer's invisible token removed (the main shape): the enumeration row for that file and both NavigationRenderer collapsed tests go red; the walk reads PAGE_AUTHORING, Sales, and so on; CONTROL stays green. B: the token made unconditional: collapsed and both expanded CONTROL tests go red. C: UnifiedSidebar's token removed: the enumeration row and both collapsed app-shell tests go red. D: the AppSchemaRenderer call site drops the constant: its enumeration row and its collapsed test go red. E: the same for SidebarNav. F: NavigationRenderer's visibility-transition token removed: the enumeration row and both collapsed tests go red. In D and E the Tab leg stays green because those labels were never focusable; the accessibility half is what catches it.",
      "gates": "At d678720 unless noted; exit codes captured by redirect before any read. Closure build: `turbo run build --filter='@object-ui/app-shell^...' --concurrency=2` through os-verify-lock, VERDICT command-exit 0, 28 of 28 tasks (at 568be2d; the later commit touched tests only). `pnpm --filter @object-ui/layout --filter @object-ui/app-shell run type-check`: exit 0, echoing `tsc --noEmit && tsc -p tsconfig.test.json` for both packages; --listFiles shows all 3 new tests in their packages' test programs. `pnpm --filter @object-ui/layout --filter @object-ui/app-shell run build`: VERDICT command-exit 0 (at 568be2d). eslint on all 7 touched source and test files: exit 0, 0 errors; the 30 warnings sit on unchanged lines of UnifiedSidebar, AppSchemaRenderer and NavigationRenderer, and the new tests have none. `pnpm check:i18n-keys` exit 0. `pnpm check:i18n-drift` exit 0. `node scripts/check-changeset-presence.mjs` exit 0: 7 source files of 2 released packages, 2 changesets. `pnpm check:new-line-citations` exit 0: VERDICT 0 new citations. `node scripts/check-control-bytes.mjs` exit 0. merge-tree against origin/main 4b3d6ed: clean, no overlapping files, so no merge was made. CI: no PR yet, so no check-runs to cite; CI convergence is the PM's.",
      "mcp_calls": "8, all GitHub reads: issue_read x2 (get, get_comments on objectui#12795); pull_request_read x6 (get, get_diff and get_comments on objectui#12793 and on objectui#12846). Plus claude-code-remote add_repo x2 (read access; both public repos, nothing attached). No write tool.",
      "api_writes": "0 REST writes. git push only: claude/issue-12795-icon-label-focus three times (the empty branch at 0812a19, then 568be2d, then d678720), and claude/issue-12795-evidence once (301f5d4).",
      "deviations": [
        "File-surface qualifier: the claim allows AppSchemaRenderer.tsx and SidebarNav.tsx 'only where a label is focusable'. Their labels are not focusable, but they were in the accessibility tree. The binding rulings say no hidden label is in the accessibility tree and every call site is covered by an enumeration pin. So both files carry the constant too, one call site each. Both files are named on the surface, and nothing outside it was touched. See open question 1.",
        "Test instrument: the two behavioural files run under a per-file `@vitest-environment jsdom` docblock, the first in this repo. happy-dom does not match the `:is(:where(.group)[data-state=collapsed] *)` selectors Tailwind emits for group variants, so it computes every label as visible. Measured with a selector probe: happy-dom gives an empty visibility, jsdom gives hidden. The files stub window.matchMedia (jsdom has none) and import the root devDependency `tailwindcss`; packages/components already has a precedent for `compile` from tailwindcss. See open question 3.",
        "app-shell full suite run as 8 shards (each about 5 minutes; an unsharded run exceeds the 10-minute foreground cap), as the dispatch allows.",
        "The os-verify-lock path used is /home/user/objectstack-ai/objectstack/scripts/pm/os-verify-lock.sh (the read-only clone), not the /home/user/objectstack path AGENTS.md names; the container has only this clone."
      ],
      "files_changed": [
        "packages/layout/src/NavigationRenderer.tsx",
        "packages/layout/src/AppSchemaRenderer.tsx",
        "packages/layout/src/SidebarNav.tsx",
        "packages/app-shell/src/layout/UnifiedSidebar.tsx",
        "packages/layout/src/__tests__/sidebarGroupLabel.iconModeFocus-12795.test.tsx",
        "packages/layout/src/__tests__/sidebarGroupLabel.callSites-12795.test.ts",
        "packages/app-shell/src/layout/__tests__/UnifiedSidebar.iconModeLabelFocus-12795.test.tsx",
        ".changeset/12795-icon-label-focus-layout.md",
        ".changeset/12795-icon-label-focus-app-shell.md"
      ],
      "screenshots": [
        "https://github.com/objectstack-ai/objectui/blob/301f5d44bc29a2a626a066fc9409f03d12810d39/docs/design/findings/12795/icon-rail-focus-after-page-authoring-light.png?raw=true",
        "https://github.com/objectstack-ai/objectui/blob/301f5d44bc29a2a626a066fc9409f03d12810d39/docs/design/findings/12795/expanded-group-label-focus-light.png?raw=true",
        "https://github.com/objectstack-ai/objectui/blob/301f5d44bc29a2a626a066fc9409f03d12810d39/docs/design/findings/12795/main-icon-rail-focus-on-hidden-label-light.png?raw=true"
      ],
      "line_budget": "n/a",
      "open_questions": [
        {
          "question": "Cover the non-focusable labels (AppSchemaRenderer's Area, SidebarNav's group labels) even though the claim's surface allowed those two files 'only where a label is focusable'?",
          "options": [
            "A (as built): cover all 7 call sites. The enumeration pin has no exception list, and no hidden label is announced anywhere.",
            "B: leave the two non-focusable sites out, and give the pin an exception list for them."
          ],
          "recommendation": "A. Business need: the ruling names the accessibility tree, and those labels were announced while invisible (measured: 9 of 9 hidden labels in the icon-mode aria snapshot on main). Long term: one rule at every call site, with no carve-out to keep in sync. AI error: a pin with no exceptions is the strict contract; an exception list is exactly where a new focusable call site would slip through. Scope: 2 classes per site and no new surface."
        },
        {
          "question": "Contract conflict: os-dev.md has the dev open the draft PR and post the os-dev-report comment, but this dispatch's degraded channel forbids both.",
          "options": [
            "A (as done): the PM opens the PR from pr_body_draft and relays this report verbatim.",
            "B: the dev writes through MCP, which voids the report under this dispatch."
          ],
          "recommendation": "A, matching the seat's ruling 4 on objectui#12846. The relay is refused from this container, so the file's write steps cannot run here."
        },
        {
          "question": "The behavioural tests use per-file jsdom plus a real Tailwind compile, a new pattern in this repo. Keep it?",
          "options": [
            "A (as built): keep it, so the pins read Tab order and the accessibility tree.",
            "B: class-list pins only, under the default happy-dom, as objectui#12744 did."
          ],
          "recommendation": "A. B cannot show Tab order or accessibility-tree membership. A turned the Tab walk itself red in ablation A (Sales appears in the walk). The cost is 2 files opting into jsdom with a matchMedia stub."
        }
      ],
      "out_of_scope_findings": [
        "class: none (observation, polish) · reach: console, live showcase · evidence: the console's apps/console/src/index.css sets an unlayered `button:not([disabled]) { transition: transform 0.18s ..., filter 0.18s }` inside @media (hover: hover). It outranks the primitive's layered transition, so a collapsible group's label button never fades in the console, on main or on this branch (computed transition-property is 'transform, filter'). Div labels (Recent, Area, Favorites, Starred) fade as designed. · carrier: none, noted in the PR's Acceptance notes only",
        "class: none (already noted, see objectui#12793's Acceptance notes) · evidence: a closed group shows no rows in icon mode and its label is now invisible, so its entries have no keyboard path until the sidebar expands. On main, Tab and Enter on the invisible label opened it by accident, and that path is the defect this ruling removes. · carrier: none, noted in the PR's Acceptance notes only",
        "class: none (observation) · evidence: visibility hidden also stops hit-testing, so objectui#12744's HIDDEN_ICON_LABEL_NO_POINTER now only matters during the roughly 200ms fade-out. It was left in place. · carrier: none, noted in the PR's Acceptance notes only"
      ],
      "pr_title_draft": "fix(layout,app-shell): a hidden icon-mode group label is no focus stop and is not announced (objectui#12795)",
      "pr_body_draft": "(elided here: it is the body of objectstack-ai/objectui#12856, the dev's draft verbatim)"
    }

    Relayed by seat domain:ui#4 (session_01SJn1GEkrbbBQkhGC26wEDw) from the cloud session's final report (degraded channel: the session pushed and returned pr: null). The seat opened #12856 from claude/issue-12795-icon-label-focus at d678720 and set pr above. The three open questions (the two non-focusable call sites, the contract conflict, the jsdom-plus-Tailwind test instrument) are ruled in the ACCEPT on the PR.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · PR #12856 merged as e43a162 on main · read 2026-10-17T04:55Z

    probe on origin/main e43a162 reading control
    HIDDEN_ICON_LABEL_NOT_FOCUSABLE declared NavigationRenderer.tsx :128, AppSchemaRenderer.tsx :76, SidebarNav.tsx :37, UnifiedSidebar.tsx :100 module-local in each file, as built
    SidebarGroupLabel call sites that pass it 7 in 4 files: NavigationRenderer 2 (:1587 the collapsible group, :1875 Favorites), AppSchemaRenderer 1, SidebarNav 1, UnifiedSidebar 3 the enumeration pin sidebarGroupLabel.callSites-12795.test.ts holds the same set
    objectui#12744's and objectui#12794's constants in UnifiedSidebar.tsx HIDDEN_ICON_LABEL_NO_POINTER (:85), ICON_RAIL_SCROLLS_VERTICALLY (:123) unchanged
    the three *-12795 test files present: two behavioural (layout, app-shell), one enumeration —
    packages/components/src/ui/** in the merge's diff 0 files AGENTS.md #7
    the two changesets present: @object-ui/layout and @object-ui/app-shell patch —
    a model name in the merge commit's message 0 —
    PNG files in the merge's diff 0 the screenshots stay on claude/issue-12795-evidence (301f5d4)

    State change: the card closed as completed through Fixes #12795, and pm:dispatched is removed in this act. The cloud session session_016gcD8krj7QLd3njFMYsi1g is archived.

    Records of this card:

    • the report relayed: 6229675810;
    • the ACCEPT: 6229678980;
    • the landing note: 6229735062.

    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

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions