From e54ea89b1ef201fad63468de57a8a3c8bc54ce61 Mon Sep 17 00:00:00 2001 From: Anders Olsson Date: Mon, 8 Jun 2026 05:50:00 +0200 Subject: [PATCH] =?UTF-8?q?docs(plans):=20token-styled=20select=20?= =?UTF-8?q?=E2=80=94=203-task=20plan=20(#51)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plans/2026-06-08-token-select.md | 288 ++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-08-token-select.md diff --git a/docs/superpowers/plans/2026-06-08-token-select.md b/docs/superpowers/plans/2026-06-08-token-select.md new file mode 100644 index 0000000..d552464 --- /dev/null +++ b/docs/superpowers/plans/2026-06-08-token-select.md @@ -0,0 +1,288 @@ +# Token-Styled Select Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the four raw ``, so the affected tests are rewritten from `userEvent.selectOptions` to open-trigger + click-option. + +**Tech Stack:** React 19 + TS + pnpm, Base UI (`@base-ui/react/select`, namespace `Select`), react-hook-form (object-form), lucide-react (Chevron/Check), Vitest + RTL + Storybook. Test runner: `pnpm test` (single pass). + +**Conventions:** pnpm; **no `any`/`eslint-disable`/`@ts-ignore`**; no codename; en/sv parity (no new keys expected); **ui/ files = no-semicolon** (match `ui/combobox.tsx`/`ui/menu.tsx`); app source = double-quote+semicolon; stories single-quote/no-semicolon; token classes only; this repo enforces `react-hooks/refs` + `react-refresh/only-export-components` — refactor cleanly, never disable. + +**Spec:** `docs/superpowers/specs/2026-06-08-token-select-design.md` + +**Key facts:** +- Base UI: `import { Select as SelectPrimitive } from "@base-ui/react/select"` — parts `Root, Trigger, Value, Icon, Portal, Positioner, Popup, List, Item, ItemIndicator, ItemText`. Mirror `ui/combobox.tsx`/`ui/menu.tsx` wrapper style. **Novel → validate by running.** +- Input className to match (`ui/input.tsx`): `h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 … focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:… aria-invalid:border-destructive dark:bg-input/30`. +- `object-form.tsx`: visibility ` + + + Apple + Pear + Plum + + + ) +} + +const meta = { component: Select, tags: ['ai-generated'], render: () => } satisfies Meta +export default meta +type Story = StoryObj + +export const Default: Story = { + play: async ({ canvas, userEvent }) => { + await userEvent.click(canvas.getByRole('combobox', { name: 'Fruit' })) + await userEvent.click(await within(document.body).findByRole('option', { name: 'Pear' })) + await expect(canvas.getByRole('combobox', { name: 'Fruit' })).toHaveTextContent('Pear') + }, +} +``` +(If the Base UI Select trigger isn't role `combobox`, adjust the query to what it actually is — discover by running. If `onValueChange`/`value` prop names differ, fix. The story passing IS the validation; report the final working API.) + +- [ ] **Step 4: Validate by running (vitest ONCE):** + `cd web && pnpm vitest run src/components/ui/select.stories.tsx && pnpm typecheck && pnpm lint` + Iterate the wrapper until the story play test passes. Report the FINAL working API (exports, the trigger role/accessible-name mechanism, value/onValueChange names, placeholder mechanism) — Tasks 2/3 depend on it. + +- [ ] **Step 5: Commit** +```bash +git add web/src/components/ui/select.tsx web/src/components/ui/select.stories.tsx +git commit -m "feat(web): ui/select Base UI Select wrapper matching Input + story (#51)" +``` + +--- + +# Task 2: object-form visibility → ui/Select + +**Files:** `web/src/objects/object-form.tsx`, `web/src/objects/object-form.test.tsx`. + +- [ ] **Step 1: Replace the visibility ` + + + + + {t("form.draft")} + {t("form.internal")} + + + )} + /> + +)} +``` +(Use the exact value/onValueChange/trigger-id API confirmed in Task 1. `form.control` is available from `useForm`; destructure `control` or use `form.control`. The default value stays `"draft"` from `defaultValues`.) Keep `