Skip to content

fix(layout,app-shell): in the icon-mode sidebar a hidden group label no longer takes the click on the row above it (objectui#12744) - #12793

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-12744-icon-sidebar-label-click
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-12744-icon-sidebar-label-click

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #12744
Clause-②: no (a class at the call sites; if the fix adds an exported wrapper to @object-ui/components, the dev reads the built declaration closure and this line is corrected)

What was wrong

In the collapsed (icon-mode) sidebar, the SidebarGroupLabel primitive hides a label with opacity-0 and -mt-8 and nothing else. The invisible label stays hit-testable. -mt-8 moves it over the lower half of the row just above its group, and opacity-0 makes it a stacking context that paints, and hit-tests, above that row. So a centre click on that row's icon landed on the label:

  • a collapsible group's label (an asChild button) toggled its group;
  • the Recent label (a div with a click handler) toggled Recent;
  • either way the row did not navigate.

A note on the card's wording: the label covers the row above its group, not the group's first item. In the stock showcase that row is the top-level Page Authoring, under the Workspace group's label. The card's own evidence (Page Authoring's centre hits the Workspace label) is the same reading.

The fix

The labels measured to intercept now carry group-data-[state=collapsed]:group-data-[collapsible=icon]:pointer-events-none. That variant is the primitive's own hiding condition, so a label stops taking pointer events exactly while it is hidden. It rides on the className each call site passes; packages/components/src/ui/sidebar.tsx is Shadcn-synced and is not touched (AGENTS.md #7).

  • @object-ui/layout, NavigationRenderer: the collapsible group label, and the Favorites label.
  • @object-ui/app-shell, UnifiedSidebar: the Recent label.

The class sits in one module-local constant per file. It is not exported: both files are re-exported whole by their package entries. cn keeps the class, because the primitive sets no pointer-events class (hypothesis 2). The pins read it on the rendered class list of the real primitive, and the browser reads computed pointer-events: none (below).

The expanded sidebar is unchanged: a group's label still toggles its group by mouse and keyboard, and the Recent label still opens and closes Recent. Nothing is added to either package entry.

Which call sites intercept (hypothesis 1, measured)

Every SidebarGroupLabel outside ui/** is hit-testable while hidden. It intercepts only when a row sits directly above its group inside the same sidebar content.

Call site Label Intercepts in the console? Done here
NavigationRenderer, collapsible group asChild button yes: every group below a row (showcase readings A and B, reading C's People group) fixed
NavigationRenderer, Favorites div yes, under the Area switcher of a multi-area app: it covered the Money area row (reading C) fixed
UnifiedSidebar, Recent div with a click handler yes: the last row of the navigation above it (readings B and C) fixed
UnifiedSidebar, Area div no: first in the content; the context selectors above it hide when collapsed. Reading C: the Work row has 0/31 dead rows left
UnifiedSidebar, Starred (home arm) div not reachable: no console route mounts the home arm (see Acceptance notes) left
AppSchemaRenderer, AreaSwitcher div not measured in a browser: the console does not mount AppSchemaRenderer. Its label is first in its content, the same position as UnifiedSidebar's Area label. Its NavigationRenderer labels get this fix left

SidebarNav (@object-ui/layout) also renders SidebarGroupLabel. It is not on the claim's file surface, and the repo has no mount of it (Acceptance notes).

Shape 3: a shared fix was considered, not taken

The one fix shared by every call site that needs no ui/** edit and no new export is a plain-CSS rule. It would go in packages/components/src/sidebar-fixes.css, the sidebar override sheet src/index.ts already imports, keyed on [data-sidebar="group-label"] under the collapsed icon .group. It was not taken for two reasons:

A custom/ wrapper would have to be exported from @object-ui/components, because layout and app-shell import it by package name. That would widen a published surface. So the class goes at three call sites, and Clause-② stays no. The report puts the CSS route to the seat as an open question.

Browser readings (before = main 0171c24, after = this branch)

The console ran on Vite from this worktree, against objectstack 66d456d01 examples/app-showcase (objectstack dev --fresh, backend only, no edits). Chromium was at 1280 px wide, with the sidebar collapsed to icons. For every visible sidebar icon the probe read three things:

  • document.elementFromPoint at its centre;
  • the dead rows: 1 px rows along the icon's vertical centre line whose hit lands outside the row;
  • a real mouse click at the centre: which element received it, and whether the route moved to the row's target.

The probe is a one-off script, not committed.

A. Showcase, groups as authored. The spec defaults expanded to false, so every group starts closed and the rail shows 2 icons.

Icon Before After
Capability Map own centre, navigates own centre, navigates
Page Authoring centre = Workspace group-label button, 16/31 dead rows, click toggled Workspace open, route unchanged own centre, 0/31, navigates

B. Showcase, every group opened in the expanded sidebar, then collapsed through the app bar's toggle: 49 icons. Before: 40 navigate and 9 do not. After: 49 of 49 have their own centre and 0 dead rows, and all navigate. These are the 9 that failed before, each with the label that took its click:

Icon (the row above the group) Centre hit before Dead rows before
Page Authoring Workspace group label 16/31
Settings Data Model group label 16/31
Cascading Select Data Slices (filters) group label 24/31
In-Review Tasks Analytics group label 16/31
Task Overview Authoring · Structured group label 24/31
Active Projects Authoring · Visualizations group label 16/31
Work Map Authoring · HTML group label 16/31
Command Center Authoring · React group label 16/31
Renewals Pipeline · rollups … Recent label (div) 16/31

The 9 hidden labels read computed pointer-events: auto before and none after, with opacity: 0 both times.

C. A two-area app, with one row pinned: 7 icons. The app was created at runtime in the --fresh temporary database with PUT /api/v1/meta/app/probe_areas_app, and no file was edited. Its areas are Work and Money. Work holds Projects, Tasks and a People group (Accounts, Contacts). Contacts was pinned through its row menu.

Icon Before After
Work (area row) own centre own centre
Money (area row) centre = Favorites label, 16/31 dead rows, click did not reach the row own centre, the click reaches it
Contacts (pinned) navigates navigates
Projects navigates navigates
Tasks centre = People group label, 16/31, route unchanged navigates
Accounts navigates navigates
Contacts (in People) centre = Recent label, 16/31, route unchanged navigates

Area label: pointer-events: auto before and after; it covers no row.

Expanded sidebar, after. Workspace label: a mouse click opens the group (My Work shows), a second click closes it, and focus plus Enter opens it again. Space closes it. The label reads pointer-events: auto, opacity: 1. A mouse click on the Recent label opens Recent: 0 rows, then 5.

Tests

Two new pin files. Each renders the real SidebarProvider / Sidebar / SidebarGroupLabel, which are source-aliased in the root vitest config:

  • packages/layout/src/__tests__/NavigationRenderer.iconModeLabelHitTest-12744.test.tsx
  • packages/app-shell/src/layout/__tests__/UnifiedSidebar.iconModeRecentLabel-12744.test.tsx

Each file holds two pins:

  1. Collapsed to icons. The fixed labels carry pointer-events-none under the variant the primitive hides them with. The expected token is derived from the primitive's own …:opacity-0 token, so a primitive that changes its hiding condition turns the pin red. The test DOM computes no Tailwind, so the pin reads the class list; the browser readings above are the behavioural proof.
  2. Expanded. No label carries an unconditional pointer-events-none. The group label toggles its group by user-event click and by Enter. The Recent label opens and closes Recent. This pin guards against a regression: it is green on main too, by design, since the expanded behaviour must not change.

Ablations ran through ablation-replace.mjs from ../objectstack. Every mutation was confirmed on disk (anchor count 1 to 0, blob changed), and every restore was proven (blob equals HEAD, git diff HEAD empty). The subjects are imported from src, so no build sits between the mutation and the run.

Mutation Pin 1 (collapsed) Pin 2 (expanded)
NavigationRenderer: the class constant emptied (the main shape) red green
NavigationRenderer: the class made unconditional pointer-events-none red red
UnifiedSidebar: the class constant emptied red green
UnifiedSidebar: the class made unconditional red red

Local gates (all on 6a571d0)

  • Closure build: turbo run build --filter='@object-ui/layout^...', then --filter='@object-ui/app-shell^...', --concurrency=2. Exit 0 both times; the second run: 28 of 28 tasks successful.
  • pnpm --filter @object-ui/layout --filter @object-ui/app-shell run type-check: exit 0. Both scripts echoed tsc --noEmit && tsc -p tsconfig.test.json. --listFiles shows each new test in its package's test program.
  • pnpm exec vitest run --maxWorkers=2 packages/layout/ packages/app-shell/src/layout/: exit 0, Test Files 132 passed (132), Tests 982 passed (982).
  • eslint on the four changed source and test files: exit 0, 0 errors. Every warning sits on an unchanged line; the new test files have none.
  • node scripts/check-changeset-presence.mjs: exit 0, "4 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)".
  • pnpm check:new-line-citations: exit 0, 0 new citation(s).

The rest of the farm is CI's.

Acceptance notes

These are not filed from this PR. The two that read as defects are in the report for the seat to judge.

  • Icon mode cannot scroll. At 1280×800 with every showcase group open, 29 of 49 rows sit below the fold. The primitive's SidebarContent is overflow-hidden in icon mode, and a mouse wheel over the rail left scrollTop at 0, so those rows cannot be reached until the sidebar expands.
  • Invisible focus stop. Keyboard Tab in icon mode stops on a hidden group label: from Page Authoring, Tab lands on the Workspace label button at opacity: 0, and Enter then toggles a group the user cannot see. This fix leaves keyboard behaviour as it was, as the seat's shape asks.
  • Closed groups are out of reach in icon mode. A closed group shows no rows and its label is invisible, so in icon mode its entries stay unreachable until the sidebar expands. The spec defaults expanded to false, so the stock showcase rail shows 2 icons. On main, a stray click on the row above happened to open a group; that accident is gone.
  • The home arm of UnifiedSidebar has no mount in the console. That arm holds the Starred label. HomeLayout, OrganizationsLayout and AiChatPage set the navigation context to home but render no UnifiedSidebar. ConsoleLayout, its only mount, sets app. Its Starred label therefore has no reachable reading.
  • SidebarNav (@object-ui/layout). It defaults to collapsible="icon", and labels after the first sit under a separator below the previous group's last row. Reading the geometry, the overlap would be the lower part of that row, short of its centre. It has no mount in this repo, and nothing here measured it.

The work was done in session https://claude.ai/code/session_01Vk15bz8xHtKGUfXfCm16h4.


Generated by Claude Code

…events in icon mode (objectui#12744)

In the collapsed icon sidebar the SidebarGroupLabel primitive hides a label
with opacity-0 and -mt-8 only, so the invisible label stays hit-testable over
the lower half of the row just above its group. A centre click on that row's
icon toggled the group (or Recent) instead of navigating.

The labels measured to intercept now carry the primitive's own hiding
condition as a pointer-events-none variant, through the className each call
site passes (the primitive is a synced Shadcn file): NavigationRenderer's
collapsible group label and its Favorites label, and UnifiedSidebar's Recent
label.

Claude-Session: https://claude.ai/code/session_01Vk15bz8xHtKGUfXfCm16h4
Co-authored-by: Claude <noreply@anthropic.com>
…ts variant (objectui#12744)

Collapsed to icons, NavigationRenderer's collapsible group label and Favorites
label and UnifiedSidebar's Recent label carry pointer-events-none under the
variant the primitive hides them with (derived from its opacity-0 token). In
the expanded sidebar none carries an unconditional pointer-events-none, a
group label still toggles its group by mouse and keyboard, and the Recent
label still opens and closes Recent.

Claude-Session: https://claude.ai/code/session_01Vk15bz8xHtKGUfXfCm16h4
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 74 chunks) 3128.0 KB 3744.1 KB
Main entry chunk (gzip) 75.6 KB 350 KB
Entry file index-BLMDW-bG.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 21.42KB 7.85KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 41.15KB 10.86KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 602.20KB 145.57KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.96KB 69.45KB
fields (index.js) 291.36KB 74.46KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.79KB 11.90KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 54.94KB 16.05KB
plugin-charts (index.js) 84.97KB 23.36KB
plugin-chatbot (index.js) 201.89KB 48.13KB
plugin-dashboard (index.js) 146.23KB 39.59KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 263.88KB 69.75KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 180.28KB 46.60KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 270.84KB 75.07KB
plugin-kanban (index.js) 59.42KB 18.48KB
plugin-list (index.js) 132.61KB 34.08KB
plugin-map (index.js) 27.07KB 8.96KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 94.68KB 24.36KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.23KB 39.46KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 — ACCEPT · domain:ui seat 3 · session_01Vk15bz8xHtKGUfXfCm16h4 · 2026-10-16T21:11Z

Head reviewed: 6a571d032cd18784e53f10c87fcbbd40a262fe2f, one round.

  • Card objectui#12744; claim 6223638218, Clause-②: no (read off the body's line 2 as no).
  • The seat's answer to the report's open question is 6224665301: A.
  • No export, prop or type changes, and no contract face. So the gates are CI plus the seat's own read.

Implemented-by: claude/issue-12744-icon-sidebar-label-click
Reviewed-by: session_01Vk15bz8xHtKGUfXfCm16h4

1. Shape.

  • Draft PR against main. The body's first line is Fixes #12744, and the body has no other closing keyword (scanned in full).
  • 5 files, +356/−3, all inside the claim's surface:
    • packages/layout/src/NavigationRenderer.tsx (+24/−2): the collapsible group's asChild label and the Favorites label;
    • packages/app-shell/src/layout/UnifiedSidebar.tsx (+15/−1): the Recent label;
    • one new test file beside each;
    • changeset .changeset/12744-icon-sidebar-label-click.md: @object-ui/layout: patch, @object-ui/app-shell: patch.
  • Each file adds one module-local constant, group-data-[state=collapsed]:group-data-[collapsible=icon]:pointer-events-none. That is the primitive's own hiding condition, passed as className. Nothing is exported: NavigationRenderer's comment says why the constant is module-local, since index.ts re-exports the file whole.
  • packages/components/src/ui/** is untouched (AGENTS.md 构建一个 **Live Playground (实时演练场)** (用于展示引擎能力) #7).
  • No content/docs/releases/.

2. Premises measured (the dev's browser readings, audited by the seat in the run's transcript).

  • Setup: objectstack 66d456d01 examples/app-showcase --fresh as the backend. The console ran on Vite from the worktree, in Chromium with the rail collapsed to icons.
  • Before (main 0171c24):
    • the Page Authoring icon's centre hit button[data-sidebar=group-label] "Workspace", the click toggled Workspace, and the route did not move;
    • with every group opened and then collapsed, 9 of 49 icons failed the same way.
  • After: 49 of 49 icons hit their own row and navigate. So do 2 of 2 in the stock rail, and 7 of 7 in a two-area app the dev created at runtime in its temporary database.
  • The browser reads computed pointer-events: none on every hidden label after the fix, and auto before.
  • In the expanded sidebar, labels toggle by mouse, Enter and Space as before.
  • The card's wording is corrected by measurement: the hidden label covers the row ABOVE its group, not the group's first item.
  • Three call sites were measured to intercept, and those three are fixed. Three are left:
    • UnifiedSidebar's Area label: first in its content, 0 of 31 rows dead;
    • its Starred label: in the home arm, which no console route mounts;
    • AppSchemaRenderer: not mounted by the console.

3. Gates (check-runs pinned to this head).

  • 36 runs: 33 success, 3 skipped (Test (coverage), its matrix row, dependabot), 0 red.
  • Among the successes: Lint, Type Check, Repo Gates, Test, Test (shard 1/16) … (16/16), Test (dist pins), Build & E2E, Build Docs, Bundle Analysis (PASS, 6224601192: eager closure 3128.0 KB of 3744.1 KB), Live E2E (informational), the changeset checks, Line Citation Gate and Governed Surface Queue Guard.
  • The dev's local subset:
    • 132 files / 982 tests over packages/layout/ and packages/app-shell/src/layout/;
    • the closure build (through turbo, the same closures) and both type-checks;
    • eslint, with 0 errors;
    • changeset presence and new-line citations.
  • Four ablations, each restored and proven. Emptying each constant (the main shape) turns its collapsed pin red. Making it unconditional turns both its pins red. The expanded-mode pins are no-regression guards, green on main by design.

4. Accepted deviations.

  • A class at each of three call sites, not one shared rule. That is the seat's answer A: the call site's className is a route AGENTS.md 构建一个 **Live Playground (实时演练场)** (用于展示引擎能力) #7 names. A custom/ wrapper is the route to revisit if a new call site reintroduces the defect.
  • Keyboard behaviour is unchanged, as the dispatch asked. The keyboard half is filed separately.
  • Prose checked against the diff:
    • the changeset's "The invisible label stayed hit-testable and lay over the lower half of the row just above its group";
    • "In the expanded sidebar nothing changes";
    • "Nothing is added to either package entry".

5. Out of scope (where each went).

  • The collapsed rail does not scroll (SidebarContent's icon-mode overflow-hidden): 29 of 49 rows sit below the fold and cannot be reached. Measured. → filed objectui#12794.
  • A hidden label stays in the tab order, so Tab lands on an invisible button and Enter toggles a hidden group. Measured. → filed objectui#12795, the family card over every SidebarGroupLabel call site.
  • Three notes went to Acceptance notes:
    • a closed group shows no rows in icon mode;
    • the home arm is unmounted;
    • SidebarNav's geometry, read and not measured.

6. Landing.

  • Not governed: check-governed-queue-guard.mjs --test over the 5 paths reads NOT GOVERNED.
  • 359 lines changed, under the 3000-line threshold.
  • Clause-②: no. Every check is green or an expected skip.
  • ⇒ The seat lands it: relay pr_ready + automerge_enable, through the merge queue.
  • On MERGED, Fixes closes objectui#12744.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 16, 2026 21:12
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 16, 2026 21:12
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit f114263 Oct 16, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12744-icon-sidebar-label-click branch October 16, 2026 21:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants