Skip to content

Commit ba1b623

Browse files
alicodingclaude
andauthored
feat: design wave 3 — palette IA + Configure tab conformance (goal 0001) (#49)
The second PR of the same design pass wave 2 opened, from the same full-app audit's §5 (palette IA) and follow-on consistency findings. 1. Node palette regrouped from 6 domain Kinds to 9 frontend display groups (composition/paletteGroups.ts, all 29 registered NodeType IDs mapped, unknown-ID fallback + dev warn, never a crash). Fixes the audit's finding that 14 of 29 node types shared one Kind (process), putting AI/List/Integration/MCP/code-exec/child- workflow/human-review/ruleset in one undifferentiated palette bucket. Domain Kinds themselves (composition/nodeKind.ts) are untouched -- this is a frontend display map only. 2. Palette label shortening fixed: a generic strip-first-"word: "- prefix transform, not a Kind-derived one -- wave 1's original version silently failed to shorten most labels, since their real prefixes ("AI:", "Code:", "List:", "MCP:") don't match their Kind's own label word. Canvas node cards and saved-workflow step chips keep the full label -- only the palette display shortens. 3. Palette search added (previously none) -- matches both the shortened display text and the full underlying label. 4. Configure > Attributes conforms to its 6 sibling tabs' own pattern: InventoryList rows (each row IS a workflow, ENTITY_ICON.workflow) + VisuallyHidden heading, replacing a bare <Select> workflow picker. Supersedes wave 1's earlier exception for this one tab. 5. Configure row-action icon-button consistency: PencilIcon replaces the text "Edit" button across the 5 Configure DataTable tabs that still had one (AI Providers/MCP Servers/Lists/Decisions/ExecEnv); CopyIcon for Decisions' Duplicate, matching RequestSummary.tsx's existing precedent. Proofs: composition/paletteGroups.test.ts (every registered NodeType ID maps to a group, the Kind-fallback path, the label-shortening transform across every real label); e2e/node-palette.spec.ts (9 groups with expected membership, label shortening, search matching short + full names, canvas cards keeping their full label after a real HTML5-drag drop); composition-canvas-interactions.spec.ts and realtime-cross-surface.spec.ts updated from the old <Select>-based Attributes assertions to the new row-based ones. Full local gate green (tsc/eslint/boundaries/vitest/check-loc/ golangci-lint/go vet/go build desktop+server/go test/ls_lint) plus a full Playwright e2e run (200/200 including 3 pre-existing timing-flake tests that passed clean on Playwright's own automatic retry, none touching the palette, Configure icon-buttons, or ConfigureAttributes). docs/SPEC.md §3.8-area, goal 0001, and BACKLOG annotated; goal stays OPEN pending the owner's own live sign-off (both design-wave PRs are now delivered). Claude-Session: https://claude.ai/code/session_018pkViCNAuZp2vBv2K9AbUh Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent fb6858f commit ba1b623

18 files changed

Lines changed: 712 additions & 152 deletions

docs/SPEC.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4554,6 +4554,70 @@ surface can already express.
45544554
Decision output keys — a class, not a component, since the only
45554555
thing every call site needs is one CSS property.
45564556

4557+
- **Node palette information architecture, `LOCKED`, built (goal 0001,
4558+
design wave 3, 2026-08-13) — grouped by frontend display group, not
4559+
domain Kind.** Same full-app design audit's §5 finding: the palette
4560+
grouped strictly by the 6 domain Kinds (`composition/nodeKind.ts`,
4561+
a real, LOCKED backend concept), but 14 of Mill's 29 node types share
4562+
the single `process` Kind (every AI node, both List nodes, the
4563+
Integration/MCP/code-exec actions, `child-workflow`, `human-review`,
4564+
`ruleset`), so a Kind-grouped palette put all of those in one
4565+
undifferentiated 14-item bucket.
4566+
- `composition/paletteGroups.ts`: a frontend-only display map (9
4567+
groups — Triggers/Capture/Transform/AI/Data/Actions/Flow/
4568+
Guardrails/Apply) keyed by NodeType ID, entirely separate from the
4569+
domain Kind registry it's built from — a node's Kind (what the
4570+
canvas/execution engine reasons about) is unchanged. Every one of
4571+
the 29 registered NodeType IDs has an explicit entry (vitest
4572+
asserts this); an unmapped future ID falls back to its Kind's
4573+
nearest group and warns to the dev console — never crashes, never
4574+
hides the item.
4575+
- Palette labels shorten under their group's own header ("AI:
4576+
Classify" → "Classify") via a generic strip-the-first-`"word: "`-
4577+
prefix transform, not a Kind-derived one — wave 1's original
4578+
version computed the prefix from the item's Kind label, which
4579+
silently failed for most of the app's node types (their real
4580+
prefixes — "AI:", "Code:", "List:", "MCP:" — don't match their
4581+
Kind's label word, e.g. Kind `process`'s "Process: " prefix never
4582+
matched "AI: Classify"). Canvas node cards and saved-workflow step
4583+
chips keep the node's full, self-contained label — a card has no
4584+
surrounding group context to shorten against; only the palette
4585+
display shortens.
4586+
- A search box (new — the palette previously had none) filters
4587+
across all groups, matching either the shortened display text or
4588+
the full underlying label.
4589+
- Group-header icons are a themed glyph per group (`ShieldIcon` for
4590+
Guardrails, `SparkleFillIcon` for AI, etc.), not a Kind-colored
4591+
square: two of the 9 groups (`flow`: `child-workflow` is Kind
4592+
`process`, `decision-route` is Kind `decision`; `guardrails`:
4593+
`human-review`/`ruleset` are Kind `process`, `decision-outcome` is
4594+
Kind `terminal`) mix domain Kinds, so a single Kind-derived color
4595+
for the whole group would misrepresent some of its own members.
4596+
- **Configure → Attributes conforms to its sibling tabs' own pattern,
4597+
`LOCKED`, built (goal 0001, design wave 3).** Was the one Configure
4598+
tab still shaped as a bare `<Select>` workflow picker + inline
4599+
editor, instead of the `InventoryList`-rows + `VisuallyHidden`
4600+
heading structure its 6 siblings all share. The rows here ARE
4601+
workflows (there's no separate "Attributes" resource to list, so
4602+
each row uses `ENTITY_ICON.workflow`, the same entity icon every
4603+
other workflow-referencing surface uses) — row click opens the same
4604+
schema editor this tab always had. Supersedes wave 1's earlier
4605+
exception for this one tab (a visible, not hidden, heading, since it
4606+
had real subtitle copy no sibling did) — consistency with the other
4607+
6 tabs' structure now wins over that per-tab distinction.
4608+
- **Configure row-action icon-button consistency, `LOCKED`, built
4609+
(goal 0001, design wave 3).** 5 of 6 Configure tabs with a `DataTable`
4610+
view (AI Providers, MCP Servers, Lists, Decisions, Execution
4611+
Environments) rendered their row's Edit action as a text `Button`,
4612+
inconsistent with `WorkflowsTable.tsx`/`ConfigureRequests.tsx`'s own
4613+
established icon-`IconButton` convention (Export/Delete already used
4614+
icons on all 6 tabs — only Edit, and AI Providers' Export/Delete too,
4615+
were the outliers). Now `PencilIcon` everywhere Edit appears in a
4616+
table row, `CopyIcon` for Decisions' Duplicate action (matching
4617+
`RequestSummary.tsx`'s own existing Duplicate-as-`CopyIcon`
4618+
precedent) — no bespoke icon invented for MCP Servers' "List tools"
4619+
action, which has no established equivalent elsewhere in the app.
4620+
45574621
## 11. Enterprise / regulated deployment readiness
45584622

45594623
`OPEN` throughout — this section is Research only. Nothing here is

docs/goals/0001-authoring-surface-overhaul.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,61 @@ tab conformance) is the second PR of this same design pass, and the
191191
owner's own live sign-off (this goal's stated Acceptance) hasn't
192192
happened yet.
193193

194+
## Design wave 3 — delivered (2026-08-13)
195+
196+
The second and final PR of the same design pass wave 2 opened, driven
197+
by the same audit's sections 5 (palette IA) and its own follow-on
198+
consistency findings. Full detail in `docs/SPEC.md` §3.8-area's three
199+
new bullets — summary here:
200+
201+
1. **Node palette regrouped from 6 domain Kinds to 9 frontend display
202+
groups** (`composition/paletteGroups.ts`, all 29 registered
203+
NodeType IDs mapped, unknown-ID fallback + dev warn). Fixes the
204+
audit's finding that 14 of 29 node types shared one Kind
205+
(`process`), making the old Kind-grouped palette put AI/List/
206+
Integration/MCP/code-exec/child-workflow/human-review/ruleset all
207+
in one undifferentiated bucket.
208+
2. **Palette label shortening fixed**: the generic strip-first-prefix
209+
transform (not a Kind-derived one) — the original wave-1 version
210+
silently failed to shorten most labels, since their real prefixes
211+
("AI:", "Code:", "List:", "MCP:") don't match their Kind's own
212+
label word. Canvas cards and step chips keep the full label
213+
unchanged (no group context to shorten against there).
214+
3. **Palette search added** (previously none) — matches both the
215+
shortened display text and the full underlying label.
216+
4. **Configure → Attributes conforms to its 6 siblings' pattern**:
217+
`InventoryList` rows (each row IS a workflow) + `VisuallyHidden`
218+
heading, replacing the bare `<Select>` picker — supersedes wave 1's
219+
earlier exception for this one tab.
220+
5. **Configure row-action icon-button consistency**: `PencilIcon`
221+
replaces the text "Edit" button across all 5 Configure `DataTable`
222+
tabs that still had one; `CopyIcon` for Decisions' Duplicate
223+
(matching `RequestSummary.tsx`'s existing precedent).
224+
225+
Proofs: `composition/paletteGroups.test.ts` (every registered NodeType
226+
ID maps to a group, the Kind-fallback path for an unknown ID, the
227+
label-shortening transform across every real label in the app);
228+
`e2e/node-palette.spec.ts` (9 groups with the expected membership,
229+
label shortening, search matching both short and full names, canvas
230+
cards keeping their full label after a real drag-drop); two existing
231+
specs (`composition-canvas-interactions.spec.ts`,
232+
`realtime-cross-surface.spec.ts`) updated from the old `<Select>`-based
233+
Attributes assertions to the new row-based ones. Full local gate green
234+
(tsc/eslint/boundaries/vitest/check-loc/golangci-lint/go vet/go build
235+
desktop+server/go test/ls_lint) plus a full Playwright e2e run
236+
(200/200 including 3 pre-existing timing-flake tests that passed clean
237+
on Playwright's own automatic retry, none touching the palette,
238+
Configure icon-buttons, or ConfigureAttributes). Screenshots:
239+
`scratchpad/audit/*-after.png` (the palette open + mid-search, both
240+
themes; Configure Attributes' row list and its open editor, both
241+
themes; a Configure table showing the new icon-button row actions).
242+
243+
Goal stays OPEN — both design-wave PRs are delivered; what remains is
244+
the owner's own live sign-off against the goal's stated Acceptance
245+
(a judgment call, not a checklist), plus the goal's original
246+
authoring-surface items (typed payload visibility on cards, the
247+
spacing audit) that predate both waves and were never in their scope.
248+
194249
## Live-review additions (2026-08-10)
195250
- Hotkey recorder vs native menu accelerators (owner hit ⌘⇧W while
196251
recording — the window closed, and since Mill exits on last-window-

docs/goals/BACKLOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ live-review material, interleaved during owner reviews, not a lane.**
208208
11. [ ] 0030 second-pass linters (gocritic/prealloc/contextcheck/sqlclosecheck — named future work in goal 0028) + `.ls-lint.yml` gains a root `node_modules` ignore (gap found 2026-08-12: a stray root node_modules broke root-file-naming; tiny, rides this or any PR).
209209

210210
**Owner-needed lane (parallel, never blocks the queue)**
211-
- [ ] [0001 — Authoring-surface overhaul](0001-authoring-surface-overhaul.md) (spacing audit + §3.8 prototype elements — live-review material, needs the owner driving; design wave 1 DELIVERED 2026-08-12 — 7 app-wide convention/bug fixes from a full-app audit screenshot pass, zero taste calls; design wave 2 DELIVERED 2026-08-13 — identity tokens: Mill's own accent scale layered over Primer, node-kind canvas colors decoupled from status semantics, one StatusStamp component replacing 7 pill families, a shared mono-font utility; wave 3 (palette IA regroup, Configure Attributes conformance) next, goal stays open pending the owner's live sign-off)
211+
- [ ] [0001 — Authoring-surface overhaul](0001-authoring-surface-overhaul.md) (spacing audit + §3.8 prototype elements — live-review material, needs the owner driving; design wave 1 DELIVERED 2026-08-12 — 7 app-wide convention/bug fixes from a full-app audit screenshot pass, zero taste calls; design wave 2 DELIVERED 2026-08-13 — identity tokens: Mill's own accent scale layered over Primer, node-kind canvas colors decoupled from status semantics, one StatusStamp component replacing 7 pill families, a shared mono-font utility; design wave 3 DELIVERED 2026-08-13 — palette IA: 6 domain Kinds regrouped into 9 frontend display groups, label-shortening bug fixed, palette search added, Configure > Attributes conforms to its sibling tabs, Configure row-action icon-button consistency; both design-wave PRs now shipped, goal stays open pending the owner's live sign-off)
212212
- [ ] Owner hands-on pass over the week's shipped features (Quick Panel focus fix, view-mode hardening, inline hotkey hints, seed reset/restore)
213213

214214
**Delivered**

frontend/e2e/composition-canvas-interactions.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,11 @@ test('Running a workflow with declared Attributes shows an auto-filled test-inpu
339339

340340
await page.getByRole('link', { name: 'Configure' }).click()
341341
await page.getByRole('tab', { name: 'Attributes' }).click()
342-
await page.getByTestId('attributes-workflow-select').selectOption({ label: 'E2E attributes workflow' })
342+
// Design wave 3: ConfigureAttributes conforms to its sibling tabs'
343+
// InventoryList-row pattern -- row click opens the schema editor,
344+
// replacing the old bare `<Select>` dropdown.
345+
await page.locator('[data-testid="inventory-row"][data-entity="workflow"]', { has: page.getByText('E2E attributes workflow', { exact: true }) }).click()
346+
await expect(page.getByTestId('attributes-editor')).toBeVisible()
343347
await page.getByRole('button', { name: 'Add attribute' }).click()
344348
await page.getByPlaceholder('key').fill('urgent')
345349
await page.getByPlaceholder('label').fill('Urgent')

frontend/e2e/node-palette.spec.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { test, expect } from './fixtures/server'
2+
3+
// Design wave 3 (goal 0001, audit §5): the palette's regrouping into 9
4+
// frontend display groups (composition/paletteGroups.ts) instead of
5+
// the 6 domain Kinds, plus the new search box. Real Go bindings over
6+
// HTTP (Wails3 server mode) -- NodeTypes() is the real backend
7+
// registry, not a fixture list, so a future node type landing without
8+
// a matching NODE_TYPE_GROUP entry would show up here as a stray
9+
// fallback-mapped item, not silently pass.
10+
11+
function activePanel(page: import('@playwright/test').Page) {
12+
return page.locator('[role="tabpanel"]:not([hidden])').last()
13+
}
14+
15+
async function openPaletteOnNewWorkflow(page: import('@playwright/test').Page) {
16+
await page.goto('/')
17+
await page.getByRole('link', { name: 'Workflows' }).click()
18+
await page.getByTestId('new-workflow').click()
19+
await activePanel(page).getByTestId('toggle-palette').click()
20+
}
21+
22+
// Native HTML5 drag events (dragstart/dragover/drop), not a raw mouse
23+
// down/move/up sequence -- CompositionCanvas's onCanvasDrop and
24+
// NodePalette's onPaletteDragStart are wired to the real HTML5 Drag
25+
// and Drop API, which mouse events alone don't trigger. Same pattern
26+
// e2e/ai-extract-fields-editor.spec.ts's own dragPaletteItemToCanvas
27+
// already established; duplicated here rather than shared since each
28+
// spec file already keeps its own small helper set.
29+
async function dragPaletteItemToCanvas(page: import('@playwright/test').Page, nodeTypeID: string) {
30+
await page.evaluate((id) => {
31+
const panel = document.querySelector('[role="tabpanel"]:not([hidden])')
32+
if (!panel) throw new Error('no active tabpanel')
33+
const palette = panel.querySelector(`[data-node-type-id="${id}"]`)
34+
const canvas = panel.querySelector('.react-flow__pane')
35+
if (!palette || !canvas) throw new Error(`drag setup failed: palette found=${!!palette} canvas found=${!!canvas}`)
36+
const dataTransfer = new DataTransfer()
37+
const rect = canvas.getBoundingClientRect()
38+
const clientX = rect.x + rect.width / 2
39+
const clientY = rect.y + rect.height / 2
40+
palette.dispatchEvent(new DragEvent('dragstart', { bubbles: true, cancelable: true, dataTransfer }))
41+
canvas.dispatchEvent(new DragEvent('dragover', { bubbles: true, cancelable: true, dataTransfer, clientX, clientY }))
42+
canvas.dispatchEvent(new DragEvent('drop', { bubbles: true, cancelable: true, dataTransfer, clientX, clientY }))
43+
}, nodeTypeID)
44+
}
45+
46+
test('the palette renders 9 groups with the expected membership', async ({ page }) => {
47+
await openPaletteOnNewWorkflow(page)
48+
const panel = activePanel(page)
49+
50+
const groups = panel.getByTestId('palette-group')
51+
await expect(groups).toHaveCount(9)
52+
53+
const expectedGroupIDs = ['triggers', 'capture', 'transform', 'ai', 'data', 'actions', 'flow', 'guardrails', 'apply']
54+
for (const id of expectedGroupIDs) {
55+
await expect(panel.locator(`[data-testid="palette-group"][data-group-id="${id}"]`)).toBeVisible()
56+
}
57+
58+
// Spot-check membership by node-type-id (stable, not display text --
59+
// see paletteGroups.ts's NODE_TYPE_GROUP for the full map).
60+
const membership: Record<string, string[]> = {
61+
ai: ['process-ai-classify', 'process-ai-completion', 'process-ai-extract-structured'],
62+
data: ['list-lookup', 'list-search'],
63+
guardrails: ['human-review', 'ruleset', 'decision-outcome'],
64+
flow: ['child-workflow', 'decision-route'],
65+
}
66+
for (const [groupID, nodeTypeIDs] of Object.entries(membership)) {
67+
const group = panel.locator(`[data-testid="palette-group"][data-group-id="${groupID}"]`)
68+
for (const ntID of nodeTypeIDs) {
69+
await expect(group.locator(`[data-node-type-id="${ntID}"]`)).toBeVisible()
70+
}
71+
}
72+
})
73+
74+
test('palette labels are shortened under their group (no repeated prefix)', async ({ page }) => {
75+
await openPaletteOnNewWorkflow(page)
76+
const panel = activePanel(page)
77+
78+
// "AI: Classify" -> "Classify" under the AI group; the palette item's
79+
// own text is the short form, never the full "<Group>: " prefix.
80+
const classifyItem = panel.locator('[data-node-type-id="process-ai-classify"]')
81+
await expect(classifyItem).toHaveText('Classify')
82+
83+
const httpItem = panel.locator('[data-node-type-id="integration-http"]')
84+
await expect(httpItem).toHaveText('HTTP call')
85+
86+
// Already-clean labels (no colon prefix) pass through unchanged.
87+
const childItem = panel.locator('[data-node-type-id="child-workflow"]')
88+
await expect(childItem).toHaveText('Run another workflow')
89+
})
90+
91+
test('canvas node cards keep their full, self-contained label after being dropped from the palette', async ({ page }) => {
92+
await openPaletteOnNewWorkflow(page)
93+
await dragPaletteItemToCanvas(page, 'process-ai-classify')
94+
95+
// A card on canvas has no surrounding group context, so it keeps the
96+
// full "AI: Classify" label -- only the palette display shortens.
97+
await expect(activePanel(page).locator('.react-flow__node').filter({ hasText: 'AI: Classify' })).toBeVisible()
98+
})
99+
100+
test('palette search matches both the shortened display name and the full underlying label', async ({ page }) => {
101+
await openPaletteOnNewWorkflow(page)
102+
const panel = activePanel(page)
103+
const search = panel.getByTestId('palette-search')
104+
105+
// "classify" matches the SHORT displayed text ("Classify").
106+
await search.fill('classify')
107+
await expect(panel.locator('[data-node-type-id="process-ai-classify"]')).toBeVisible()
108+
await expect(panel.getByTestId('palette-item')).toHaveCount(1)
109+
110+
// "AI:" only appears in the FULL underlying label ("AI: Classify"),
111+
// never in the shortened display text ("Classify") -- still matches.
112+
await search.fill('AI:')
113+
await expect(panel.locator('[data-node-type-id="process-ai-classify"]')).toBeVisible()
114+
await expect(panel.locator('[data-node-type-id="process-ai-completion"]')).toBeVisible()
115+
await expect(panel.locator('[data-node-type-id="process-ai-extract-structured"]')).toBeVisible()
116+
117+
// A query matching nothing shows the empty state, not a silently
118+
// blank tree.
119+
await search.fill('zzz-no-such-step')
120+
await expect(panel.getByTestId('palette-item')).toHaveCount(0)
121+
await expect(panel.getByTestId('palette-no-matches')).toBeVisible()
122+
123+
// Clearing the query restores the full 29-item palette.
124+
await search.fill('')
125+
await expect(panel.getByTestId('palette-item')).toHaveCount(29)
126+
})

frontend/e2e/realtime-cross-surface.spec.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,12 @@ test('a direct-UI workflow create in one window reaches a canvas picker open in
9696
await page.goto('/')
9797
await page.getByRole('link', { name: 'Configure' }).click()
9898
await page.getByRole('tab', { name: 'Attributes' }).click()
99-
const select = page.getByTestId('attributes-workflow-select')
100-
await expect(select).toBeVisible()
101-
99+
// Design wave 3: ConfigureAttributes conforms to its sibling tabs'
100+
// InventoryList-row pattern (each row IS a workflow), replacing the
101+
// old bare `<Select>` dropdown this test used to assert against.
102102
const label = 'E2E cross-surface workflow'
103-
await expect(select.locator('option', { hasText: label })).toHaveCount(0)
103+
const attributesRow = page.locator('[data-testid="inventory-row"][data-entity="workflow"]', { has: page.getByText(label, { exact: true }) })
104+
await expect(attributesRow).toHaveCount(0)
104105

105106
// A genuinely direct-UI create (the Workflows page's own "New
106107
// workflow" button + Save, no MCP involved at all) on the SECOND
@@ -115,7 +116,7 @@ test('a direct-UI workflow create in one window reaches a canvas picker open in
115116
// No page.reload() on page (the first page/window) -- ConfigureAttributes.tsx
116117
// now reads shared/store.ts's workflows store, refreshed by
117118
// App.tsx's mill-data-changed{entity:"workflow"} handler.
118-
await expect(select.locator('option', { hasText: label })).toHaveCount(1, { timeout: 10_000 })
119+
await expect(attributesRow).toHaveCount(1, { timeout: 10_000 })
119120

120121
await page2.getByRole('link', { name: 'Workflows' }).click()
121122
const row = page2.locator('[data-testid="inventory-row"][data-entity="workflow"]', { has: page2.getByText(label, { exact: true }) })

frontend/src/composition/CompositionCanvas.module.css

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -311,14 +311,21 @@
311311
.canvasNodeSelected {
312312
box-shadow: var(--xy-node-boxshadow-selected);
313313
}
314-
.paletteItemIcon {
314+
/* Design wave 3: the palette's group-header icon is now a plain themed
315+
glyph (paletteGroups.ts's PALETTE_GROUP_ICON), not a Kind-colored
316+
square -- muted to match .paletteGroupLabel's own color, since a
317+
display group can mix domain Kinds and a single square color would
318+
misrepresent some of its own members. */
319+
.paletteGroupIcon {
320+
color: var(--fgColor-muted);
315321
flex: 0 0 auto;
316-
width: 26px;
317-
height: 26px;
318-
border-radius: var(--borderRadius-medium);
319-
display: flex;
320-
align-items: center;
321-
justify-content: center;
322+
}
323+
.paletteSearch {
324+
margin-bottom: var(--base-size-8);
325+
}
326+
.paletteNoMatches {
327+
color: var(--fgColor-muted);
328+
padding: var(--base-size-8) 0;
322329
}
323330

324331
/* Nothing-hidden guardrail badge (docs/adr/0022's Update): marks a step

0 commit comments

Comments
 (0)