Skip to content

Make sidebar Search and resizing accessible - #4601

Open
3mdistal wants to merge 5 commits into
mainfrom
codex/content-sidebar-controls-sb01
Open

Make sidebar Search and resizing accessible#4601
3mdistal wants to merge 5 commits into
mainfrom
codex/content-sidebar-controls-sb01

Conversation

@3mdistal

@3mdistal 3mdistal commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Problem

People using Content's collapsed sidebar cannot reliably start a search: the Search button toggles an inline filter that is hidden in that layout. The icon-only New Page and Settings controls also lack accessible names, and changing the sidebar width requires a pointer.

Approach

Search now opens the existing command picker on the first click or keyboard activation. Dismissing it returns focus to the control that opened it. Reusing that picker gives collapsed, expanded, and mobile navigation the same search entry point and removes the sidebar's separate filter state.

This covers the sidebar controls work (SB-01–03). Richer search filters and results remain separately scoped. Creation and Retry behavior, deletion, Trash, unpin semantics, and personal ordering are outside this change.

What changed

  • Give collapsed New Page and Settings their localized accessible names; New Page includes its destination space.
  • Make the desktop resize divider a focusable, named separator with a visible focus indicator and an announced current width. Left/Right adjust by 10px; Home/End select the existing 240–480px bounds. Keyboard and pointer resizing use the same width handler.
  • Remove command-picker stacking overrides so it inherits the shared dialog layer. Mobile testing exposed the picker rendering behind the navigation drawer; this correction keeps it visible above the drawer. Because the component is shared across apps, the patch includes a core changeset and updated component expectations.
  • Translate the resize label in all 11 configured locales, add a Content changelog entry, and record the bounded controls evidence without marking the entire sidebar capability verified. The changelog CLI is disabled for this app, so the entry uses its existing dated-file format.

Verification

Interface checks exercised runtime commit c774d3eaa. The subsequent documentation update and head 1052cd39f preserve that behavior: the latest repair removes a redundant overlay color class while retaining the same inline background color. Existing interface evidence is retained; the implementation owner reran the 13 command-menu tests, including the rendered background assertion, plus the raw-color guard, formatting, and diff checks. All passed. Broader local builds and checks below precede this narrow repair.

  • Passed: 31 focused Content tests covering sidebar structure, root revalidation, and command search; 13 shared command-menu tests covering the component, including dialog stacking expectations.
  • Passed: Content TypeScript, four prerequisite workspace package builds, the final core build, formatting, and resize-label coverage across all 11 locales.
  • Passed, implementer-run interface checks: desktop 1280×720 and mobile 390×844. Search opened with focused input, Escape returned focus, control names were exposed, keyboard and pointer resizing respected the bounds, width survived reload, and selecting a mobile search result closed the drawer. Mobile and desktop Search were repeated after the stacking repair. This is bounded keyboard/focus coverage, not a complete screen-reader audit.
  • Passed with setup qualification: the product-docs guard passed after local CRLF-to-LF normalization with no semantic diff. Final browser checks used an isolated loopback host and disposable local data after initial development reload errors and cookie collisions; the final session survived reload without new console errors.
  • Local limitations: the full localization guard reports 2,537 workspace findings outside the changed strings. The changed-copy guard inspected zero bundled-copy surfaces. Nine product-impact tests failed on Windows fixture line endings and extensionless tsx subprocess invocation. No guard or baseline was weakened.
  • CI on 1052cd39f: Security guards now passes after removal of the redundant overlay color class. Other checks are still queued or running; a fully green CI result is not yet established.

Review focus

  • Does focus return remain correct when dismissal and navigation overlap, particularly while the mobile drawer closes?
  • Is inheriting the shared dialog layer appropriate for command pickers in other apps?
  • Do the separator's announced width, keyboard bounds, and pointer behavior stay consistent?

This PR is open for review and awaits approval from Steve. Remaining CI checks must pass before merge.

content_product_impact:
  lane: contract_repair
  features:
    - content.feature.find-your-place-again
  capabilities:
    - content.navigation.sidebar
  record_change: included
  proof:
    - pnpm --filter content exec vitest run app/components/sidebar/DocumentSidebar.layout.test.ts app/root.revalidation.test.ts app/lib/content-command-search.test.ts
    - pnpm --filter content exec tsc --noEmit
    - pnpm --filter @agent-native/core exec vitest run src/client/CommandMenu.spec.tsx
    - pnpm guard:content-product-docs
  rationale: Repairs the existing sidebar controls without changing personal navigation, access, or object structure.

@3mdistal
3mdistal marked this pull request as ready for review September 9, 2026 16:53
@3mdistal
3mdistal requested a review from steve8708 September 9, 2026 16:59
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Here's a visual recap of what changed:

Visual recap

Open the full interactive recap

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder reviewed your changes and found 2 potential issues 🟡

Review Details

Code Review Summary

PR #4601 routes sidebar Search through the shared command picker, adds accessible names for icon-only controls, makes the desktop resize divider keyboard-operable, and aligns the shared picker with the dialog stacking layer so it remains above mobile navigation drawers. The overall approach is sound: it removes the hidden inline-search state, reuses the existing search surface, exposes separator value semantics, and keeps pointer and keyboard resizing on the same callback. The shared z-index change is covered by updated core tests and is appropriate for the toolkit dialog stack.

Risk assessment: Standard. The key concerns are edge cases at the boundaries between focus management, navigation/unmounting, and RTL layout direction:

  • 🟡 MEDIUM: Mobile search-result navigation can unmount the stored trigger before the focus-restoration frame runs, leaving focus on the body instead of a live navigation control.
  • 🟡 MEDIUM: In RTL, keyboard ArrowLeft/ArrowRight resizing is opposite to the physical direction of pointer dragging on the sidebar's end-0 divider.

The implementation has good localized labeling and bounded Home/End semantics. Focus and RTL behavior should be corrected before approval.

🧪 Browser testing: Could not verify — the dev server was healthy, but Chrome automation tools were unavailable across all 17 planned test cases and retries.

Comment on lines +580 to +585
useEffect(() => {
if (cmdkOpen || !commandTrigger.current) return;
const trigger = commandTrigger.current;
commandTrigger.current = null;
const frame = window.requestAnimationFrame(() => {
if (trigger.isConnected) trigger.focus();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Preserve focus when mobile navigation unmounts the trigger

When Search is opened from the mobile drawer and a result is selected, the picker closes while navigation also causes the drawer (and its trigger) to unmount. The scheduled callback then sees trigger.isConnected === false, so focus is left on the body/removed focus scope. Restore focus to a still-mounted navigation target or coordinate drawer closure and picker dismissal so keyboard users retain a valid focus destination.

Additional Info
Reported by 1 of 2 parallel review agents; matches the PR's mobile drawer focus-risk area.

Fix in Builder

Comment on lines +2546 to +2567
onKeyDown={(event) => {
let nextWidth: number;
switch (event.key) {
case "ArrowLeft":
nextWidth = width - 10;
break;
case "ArrowRight":
nextWidth = width + 10;
break;
case "Home":
if (minWidth === undefined) return;
nextWidth = minWidth;
break;
case "End":
if (maxWidth === undefined) return;
nextWidth = maxWidth;
break;
default:
return;
}
event.preventDefault();
onResize(nextWidth);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Keep keyboard resize direction consistent in RTL

The keyboard handler always makes ArrowLeft decrease width and ArrowRight increase it. In the supported RTL layout, this sidebar's end-0 divider is on the physical left edge, so pointer-dragging left increases width while ArrowLeft decreases it. Derive the arrow delta from document direction (or use logical inline-direction semantics) so keyboard and pointer resizing agree.

Additional Info
Reported by 1 of 2 parallel review agents; independently confirmed against the RTL layout and pointer handler.

Fix in Builder

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants