Skip to content

spec: 0030 combobox (single + multi-select with badges)#42

Merged
mattmaynes merged 3 commits into
mainfrom
spec/0030-combobox
Jul 8, 2026
Merged

spec: 0030 combobox (single + multi-select with badges)#42
mattmaynes merged 3 commits into
mainfrom
spec/0030-combobox

Conversation

@mattmaynes

Copy link
Copy Markdown
Contributor

Draft - spec for review, not a build. Per the Spectra protocol I stop here for your approval before planning/implementing.

Summary

Proposes docs/specs/0030-combobox.md: a Combobox component for @rogueoak/canopy supporting both single-select and, the priority, multi-select with removable Badge chips in the field.

Key decision (yours)

Built on the Radix Popover + cmdk stack - consistent with every existing canopy component and the 0005 recipe - rather than the Base UI combobox from the linked docs. Trade-off captured in the spec: multi-select chips are composed on Badge rather than native, in exchange for one primitive family across the system.

Two new canopy runtime deps: @radix-ui/react-popover and cmdk (the missing primitives). Architecture/security personas should weigh the new-dependency surface at review time.

Scope highlights

  • In: single + multi mode in one API, client-side filtering, badge chips with labelled remove + Backspace-removes-last, disabled/aria-invalid parity with Select/Input, full keyboard + ARIA, Storybook stories, tests.
  • Out (follow-ups): async/remote options, creatable free-text, standalone Command palette, grouping/virtualization.

Next step

Approve or leave comments on the spec. On approval I'll turn it into a plan (docs/plans/), build it in a worktree with tests, and run it through the engineer/tester/architect/designer personas before merge.

🤖 Generated with Claude Code

mattmaynes and others added 2 commits July 7, 2026 22:41
Radix Popover + cmdk stack, per developer decision. Spec only - build
awaits approval per the Spectra protocol.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxRv44W4ZyLWV4eyZUMZdD
Filterable select Seed built on @radix-ui/react-popover + cmdk, following
the 0005/Select recipe. Single-select commits and closes; multiple renders
removable Badge chips in the field, keeps the popover open across picks,
toggles on re-pick, and Backspace-removes-last on the empty search input.
One `multiple` prop discriminates the value shape (string vs string[]).

Adds plan 0030, component + 18 tests + 6 Storybook stories, barrel exports,
README. New deps @radix-ui/react-popover ^1.1.19 and cmdk ^1.1.1 (both
externalized in tsup). Full suite green: test / build / lint / format /
storybook.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxRv44W4ZyLWV4eyZUMZdD
@mattmaynes mattmaynes marked this pull request as ready for review July 8, 2026 02:59
@mattmaynes

Copy link
Copy Markdown
Contributor Author

Spec approved and implemented. This PR now carries spec 0030, the build plan, and the full implementation:

  • packages/canopy/src/seeds/Combobox.tsx - the component family (single + multiple modes)
  • Combobox.test.tsx - 18 tests; Combobox.stories.tsx - 6 stories under Seeds/Combobox
  • new deps @radix-ui/react-popover ^1.1.19 + cmdk ^1.1.1 (externalized in tsup)
  • barrel exports + README

Full suite green locally: pnpm test (243 passed), build, lint, format:check, storybook build.

Spectra personas (engineer, tester, architect, designer) to review inline next.

Comment thread packages/canopy/src/branches/Combobox.test.tsx
Comment thread packages/canopy/src/branches/Combobox.test.tsx
Comment thread packages/canopy/src/branches/Combobox.test.tsx
Comment thread packages/canopy/src/seeds/Combobox.tsx Outdated
Comment thread packages/canopy/src/seeds/Combobox.tsx Outdated
Comment thread packages/canopy/src/seeds/index.ts Outdated
Comment thread packages/canopy/src/seeds/Combobox.tsx Outdated
Comment thread packages/canopy/src/seeds/Combobox.tsx Outdated
Comment thread packages/canopy/package.json
Comment thread packages/canopy/src/seeds/Combobox.tsx Outdated
Comment thread packages/canopy/src/seeds/Combobox.tsx Outdated
Comment thread apps/storybook/src/Combobox.stories.tsx
…anches

Architect (major): Combobox owns interaction state + a portal and composes the
Badge seed, so it is Branch-shaped, not a Seed. Moved to
packages/canopy/src/branches/, exported on ./branches beside Dialog; trimmed the
public surface to the root + ComboboxOption + prop-type unions.

Engineer: cmdk item keyed by value (+ label keywords) so duplicate labels don't
collapse; narrowed emit deps; latched isControlled via ref.

Designer: scoped the multi-mode field focus ring to the trigger button; enlarged
the chip remove hit target to 20px with an offset ring; added multi-mode disabled
+ invalid stories and a disabled option.

Tester: added keyboard-nav (arrow/enter/escape), controlled-mode (single +
multiple), the isControlled latch case, and the Backspace non-empty boundary.
247 tests pass (was 243).

Reflection: feedback 0013 (tier placement) + 0014 (interactive test coverage),
two learnings, and features/architecture updated (Combobox Branch; cmdk as the
sanctioned first non-Radix primitive).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxRv44W4ZyLWV4eyZUMZdD
@mattmaynes

Copy link
Copy Markdown
Contributor Author

Review addressed - all 12 findings resolved

Thanks to the four personas. Summary of how each was handled (commit 775359a):

🏛 Architect

  • major - tier placement: moved Combobox from seeds/ to branches/ (it owns interaction state + a portal and composes the Badge seed - Branch-shaped, like Dialog). Exported on ./branches; Badge import now crosses tiers downward. Captured in docs/feedback/0013 + a learning.
  • minor - over-exposed API: barrel now exports only Combobox + ComboboxOption + the prop-type unions; parts are internal.
  • minor - cmdk precedent: recorded in architecture.md as the sanctioned first non-Radix primitive.

🧪 Tester

  • major - keyboard nav: added arrow/enter/escape test. major - controlled mode: added single + multiple controlled tests (+ the isControlled latch case). minor - Backspace boundary: added the non-empty-search case. Captured in docs/feedback/0014 + a learning. 22 Combobox tests (was 18); 247 total.

🛠 Engineer

  • cmdk item now keyed by value (+ keywords={[label]}) so duplicate labels don't collapse; emit deps narrowed; isControlled latched via ref.

🎨 Designer

  • multi-mode field ring scoped to the trigger (has-[>button:focus-visible]); chip remove target enlarged to 20px with an offset ring; added multi-mode disabled + invalid stories and a disabled option.

Full suite green: test (247) / build / lint / format:check / storybook build. Reflection (features + architecture + learnings + two feedback docs) is in this PR.

@mattmaynes mattmaynes merged commit 9210656 into main Jul 8, 2026
1 check passed
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.

1 participant