The grouped, searchable, filterable palette (ADR-0005 decisions 8, 10, 13).
The entry's icon
An entry renders the tile the card it produces will carry, resolved by the
same seam and through the same icon assign - Editor hands this
component and Editor.BlockNode the identical value. The tile was declared
here and rendered nowhere for the whole of the graduated editor's life
(sb-jja), so a host could not put an icon on a palette row at all; a
palette that showed no icons above a canvas that did was the visible half
of that.
Everything it renders comes from palette_entry/0 through
StatifierBlocks.ViewModel, which already applied decision 10's defaults -
label to the type name, group to "Other", description to "",
icon to nil, keywords to [], order to 0. A block type that
implements none of the callback still renders here, and ADR-0002 decision
5 promised it would.
Two filters compose, and they are different in kind:
- Search is the author's, over label, description, type name and
keywords. Purely presentational. - Acceptance is the slot's. When the palette was opened from a
specific gap,
allowedcarries the type names that slot will take, computed byStatifierBlocks.Editorwith the same predicate a drag uses -Edit.Targets.droppable_slots_for/3against a probe block of each candidate type. Decision 8 is explicit that the filter uses the same predicate as decision 5, not a parallel implementation, and this component deliberately computes none of it: it is handed a set.
The strip, below 780 (7A)
The 2026-08-29 shell amendment gives the palette a second shape: below a container width of 780 it collapses to a strip - a label and a "+" - that opens as a sheet over the canvas, so the inspector gets the full row. Both shapes are always in the markup and the stylesheet decides which one is on screen, because the breakpoint is a container query and the server does not know how wide the host gave the editor. What the server owns is whether the sheet is open, which is one boolean and one event; selecting a block or picking an entry closes it, since a sheet left open covers the thing the author just chose.
Picking an entry emits an :insert at exactly the position the "+" named,
which is the identical command a successful drop would produce. That is
what makes the whole insertion path exercisable in LiveViewTest without
simulating a drag, and it is why decision 8 is not only an accessibility
affordance - though it is that, drag-and-drop being unusable by keyboard
and hostile on touch.
Summary
Functions
The groups a query and an acceptance set leave visible, with empty groups dropped. Pure, so the palette's filtering is asserted directly rather than through markup.
The palette: a search box, then a section per entry.group.
Functions
@spec filter( [StatifierBlocks.ViewModel.PaletteGroup.t()], String.t(), MapSet.t(String.t()) | nil ) :: [ StatifierBlocks.ViewModel.PaletteGroup.t() ]
The groups a query and an acceptance set leave visible, with empty groups dropped. Pure, so the palette's filtering is asserted directly rather than through markup.
The palette: a search box, then a section per entry.group.
Attributes
groups(:list) (required)query(:string) - Defaults to"".allowed(:any) - MapSet of accepted type names, or nil for unfiltered. Defaults tonil.target(:any) (required)icon(:any) - The host's icon component, or nil forStatifierBlocks.Editor.Icons. The same value the canvas cards get, so a type looks the same before and after the pick that puts it in the document.Defaults to
nil.class(:string) - Defaults tonil.sheet_open(:boolean) - Whether the narrow-layout sheet is open (7A). Ignored above 780. Defaults tofalse.