@magicblocksai/ui changelog
Format: Keep a Changelog · SemVer.
v4.13.0 — 2026-06-11
Agent Builder redesign Phase 4 — Templates + close-out (spec §4.8/§10; the four-phase redesign is complete). Lockstep with @magicblocksai/[email protected]. Additive — minor, no breaking changes.
Added
<TemplateCard>(§18.21) — one template in the new-agent gallery: name, pitch, the journey shape as a mini block chain, what it ships with;selectedaccent ring,blankdashed Start-blank variant, "MagicBlocks"/"Yours" badge.<TemplateGallery>(§18.21) — the creation-sheet grid (auto-fill, single-select via consumer state). Copy-on-create.<BlockLibraryDrawer>(§18.21) — the journey rail's block-template drawer composing<Drawer>: the MagicBlocks starter set + "Your blocks", per-row Insert. Copy-on-insert; exportsBlockTemplate.- §18.22 Channels tab — composition merging Channels + Design & Go Live + Contact Transfer ("where it talks, and where it hands off"), with the greyed-unavailable-channel pattern.
- §18.23 Settings tab — composition: Guardrails first, conversation defaults, about,
DangerZoneBlock(Pause · Duplicate-as-draft · Delete). docs/building-the-agent-builder.md— the app-team guide tying all four phases (4.10–4.13) into per-surface recipes.
Changed
- §17.12 retitled "Connections & tools — Tools & MCP" (id unchanged) — the shared-infrastructure home, formerly Library; the four shelves now live on the Agents HQ (§18.19).
v4.12.0 — 2026-06-11
Agent Builder redesign Phase 3 — Sage (spec §4.7/§10). Lockstep with @magicblocksai/[email protected]. Additive — minor, no breaking changes.
Added
<ProposalCard>(§17.17) — Sage's unit of change:pending(Accept · Adjust · Dismiss),applied(reversible, Undo),error(explains why nothing changed); plain-language title + summary, the exact change behind a closed-by-default "Show exactly what changed" disclosure,.proposal-old/.proposal-newfor old → new text. ExportsProposalCardState.SageDrawerproposal upgrade (opt-in) — the proposal message variant gainsstate/summary/diff/onUndo; setting any of them renders the row through<ProposalCard>(withonEdit→onAdjust). Legacy proposal messages keep the original.sage-proposalmarkup byte-for-byte.- §17.18 Sage dock — the builder's right dock: Test + Sage in one column (
.sage-dockchrome), the Sage tab streaming turns + proposal cards over the shipped.sage-*classes. docs/sage-proposal-contract.md— the NextGen wiring contract:agents.proposeDraftPatchreturning{ summary, detail?, patch, risk? }(JSON-Patch ops over the workflow document), the four hard rules (draft-only; substantive waits / low-risk applies with Undo; Recent-changes entry with inverse patch; validation gates every apply), UI mapping table, worked example.
v4.11.0 — 2026-06-11
Agent Builder redesign Phase 2 — Studio (spec §10). Lockstep with @magicblocksai/[email protected]. Additive — minor, no breaking changes.
Added
<LeadsToStrip>(§18.20) — the block editor's orientation strip: "Leads to → Discovery, once every key fact here is collected", with Reached from revealed on hover/focus. A block with no local route says "no local route yet — anytime actions still apply".<KeyFactBehaviourLine>(§18.20) — a key fact's asked-vs-listened behaviour as a sentence ("Asked in Greeting · listened for everywhere"), with the ask-when rule inline.RailBlockItemstart/hint— the Start pill on the journey's entry block, and a muted coaching line ("0 jobs · needs work") replacing the stats on empty blocks.JourneyGraphEdge.variant: "dashed"— the anytime-actions layer on the journey map; the attribute is emitted only when dashed, so existing trace-view DOM is unchanged. Plus a first properdocs/JourneyGraph.mdpage.ActionListtitle/description— retitle the workbench head ("Anytime actions") and add the framing line ("Rules that watch every block").
Chapter
- §18.20 Journey studio — the redesigned Journey tab: journey-only rail (List ⇄ Map toggle, blocks with start/hint, Add block · Block library, pinned Key facts + Anytime actions rows), the promoted map view with dashed anytime edges + selection card, and the two new primitives. §18.11/§18.12 descs cross-reference the new naming.
v4.10.0 — 2026-06-11
Agent Builder redesign Phase 1 — Frame (spec: docs/superpowers/specs/2026-06-11-agent-builder-redesign-design.md). Lockstep with @magicblocksai/[email protected]. Additive — minor, no breaking changes.
Added
<UsedByChip>(§18.18/18.19) — usage indicator for the Agents HQ shared shelves: "Used by N agent(s)" in blue, calm neutral "Not used yet" at zero. Wraps<Chip>.<SetupProgressChip>(§18.18) — "Setup 5 of 6 — connect a phone number" for the Overview status band; green "Setup complete" when done. Wraps<Chip>.<GoLiveCard>(§18.18/18.9) — the Overview card answering "how do people reach this agent, and how do I go live?": channel rows with live / not-yet states and a deep-linkactioninto the agent's Channels tab. ExportsGoLiveChannel.<RecentChangesList>(§18.18/18.9) — the Overview's accountability card: human and Sage edits to the working copy, timestamped; Sage entries (by: "sage") carry an Undo. ExportsRecentChange.WorkspaceShellactions[].accent(§15.27) — accent-tinted launcher treatment for a bottom-cluster action (the pinned Ask Sage entry on the slim rail). Renders.ws-nav-icon.is-accent.
Chapter compositions (no new API)
- §18.18 Agent frame —
AgentBuilderHead+ the five-tabLinkTabsrow (Overview · Journey · Knowledge · Channels · Settings) via the new.ab-frame-tabsclass; "View conversations" joins the head actions, testing moves to the right dock. - §18.19 Agents HQ shelves — the HQ tab row (All agents · Personas · Snippets · Forms · Goals) over
DataTableshelf compositions withUsedByChipusage cells; Snippets debut as the paste-anywhere{{token}}entity; empty-shelf state. - §18.9 Overview — the live agent's home — status band +
SetupProgressChip,GoLiveCard+ identity card, KPI strip, "Questions it couldn't answer" with *Teach it*, Guardrails glance,RecentChangesList.
v4.9.0 — 2026-06-10
KnowledgeLane.kind aligned to the retrieval-policy vocabulary. Lockstep with @magicblocksai/[email protected]. Additive — minor, no breaking changes.
Changed
<KnowledgeLane>kindnow usesalways | semantic | conditional— matching the DBretrievalPolicyandAttachLanePicker's lane values, so there's one vocabulary across the Knowledge components (no morekindrelevant/topicvs policysemantic/conditionalmismatch). Plain display names are unchanged. The previouskind="relevant"(→semantic) andkind="topic"(→conditional) are accepted as deprecated aliases — normalised internally to byte-identical output — and will be removed in the next major. Lane CSS classes renamed accordingly (.knowledge-lane-relevant→.knowledge-lane-semantic,.knowledge-lane-topic→.knowledge-lane-conditional).
v4.8.1 — 2026-06-10
No-op lockstep bump with @magicblocksai/[email protected] — a KnowledgeLane border fix lives in the CSS package; no @magicblocksai/ui API or rendered-DOM change.
v4.8.0 — 2026-06-10
Knowledge editor primitives (Phase A of the Knowledge redesign) — §17.13–17.16. Lockstep with @magicblocksai/[email protected]. Additive — minor, no breaking changes.
Added
<TopicChip>(§17.13) — maps the 12 conditional-retrieval topics (PlaybookChunkLabel) to plain labels (Pricing, Objections, …) in the kit's green tone. Wraps<Chip>. Also exportsKNOWLEDGE_TOPICS+ theKnowledgeTopictype.<KnowledgeItemRow>(§17.13) — one row in the collection editor: a source icon (manual/website/spreadsheet/file) + title + an adaptive one-line content preview (collapses to a single line when omitted, e.g. short "Always" facts) + optional<TopicChip>+ updated time. Composes<SourceRow>.<KnowledgeLane>(§17.14) — a tinted lane header (dot + plain name + helper + count) overKnowledgeItemRows. Three lanes — Always (accent), When it's relevant (info), When a topic comes up (success) — mapping to thealways/semantic/conditionalretrieval policies.<AddKnowledgeMenu>(§17.15) — the "Add knowledge" dropdown: write it yourself / import from a website / upload a spreadsheet / upload a file. Composes<Menu>;onChoosefires the chosen path.<AttachLanePicker>(§17.16) — attach a collection to an agent by picking which lanes (Checkbox) and which topics (FilterChipGroup) it gets, with a plain-language summary ("certain parts, out of those 3 things"). Controllable viavalue/defaultValue/onValueChange.
Plain language throughout (no RAG / semantic / intent jargon in UI copy); kit tokens + SVG icons only. NextGen wires the backend (scraping, CSV, Q&A generation, retrieval). Spec: docs/superpowers/specs/2026-06-09-knowledge-redesign-design.md.
v4.7.0 — 2026-06-08
<WorkspaceShell> now owns mobile navigation — zero wiring. Lockstep with @magicblocksai/[email protected]. Additive — minor, no breaking changes.
Added
<WorkspaceShell>mobile drawer (zero-wiring). At ≤960px the rail folds into the off-canvas<AppShell>drawer and the shell renders its own hamburger (.ws-mobile-trigger) to reveal it — previously a consumer had to hand-wiremobileNavOpen+ a trigger, so a drop-in usage silently lost its nav on mobile (the rail wasdisplay:nonewith nothing to open it). Tapping a nav item / action / settings closes the drawer; scrim + Esc close it too. Desktop is unchanged (trigger hidden ≥961px), preserving the sidebar-first, no-top-bar identity. NewdefaultMobileNavOpenprop;mobileNavOpen/onMobileNavOpenChange(inherited from<AppShell>) now drive the shell-owned state for controlled use.
v4.6.0 — 2026-06-08
Skeleton loading-placeholder primitives + one canonical shimmer kit-wide. Lockstep with @magicblocksai/[email protected]. Additive — minor, no breaking changes.
Added
<Skeleton>/<SkeletonText>/<SkeletonAvatar>(§9.8) — loading-placeholder primitives.<Skeleton>is one shimmer block (width/heightas number→px or CSS length,radiussm|md|pill|full);<SkeletonText lines>renders shimmer rows with a shortened last line;<SkeletonAvatar size>is a round shimmer. Token-driven gradient,prefers-reduced-motion-safe,aria-hidden. Resolves the Next-Gen "universal loading-UX sweep" request — consumers compose shaped fallbacks without re-deriving the shimmer.
Changed
- Internal:
DataTable,ChatTranscript, andSourceCardskeleton states migrated onto the sharedskel-shimmerkeyframe — one shimmer treatment kit-wide. No public API change; rendered DOM unchanged (ChatTranscript's loading bubbles adopt the gradient sweep in place of the prior opacity pulse).
v4.5.0 — 2026-06-07
Sessions live takeover console (Phase 2) + overview mobile cards. Lockstep with @magicblocksai/[email protected]. Additive — minor, no breaking changes.
Added
<TakeoverConsole>(§19.9) — the live human-takeover console (<SessionPage mode="live">'s body). Two control states:ai_driving(a "Take over" CTA) andhuman_in_control(a guardrails-off<ChatComposer>— "Guardrails off · sending as a human" — plus a<JourneyBlockPicker>hand-back). ComposesChatTranscript·ChatComposer·JourneyBlockPicker·KeyFactGrid; the transcript is a slot, and an operator-context sidebar carries the contact, key facts so far, a "Sage suggests" draft, and the journey position.<JourneyBlockPicker>— hand-back target menu (resume where the AI paused · jump to a Journey Block · End — no further automation). A thin compose over<DropdownMenu>.<SessionPage mode="live">(§16.28) — live mode renders aconsoleslot (a<TakeoverConsole>) under the shared hero, in place of the summary band + transcript. Newmode+consoleprops.
Changed
ChatMessage—fromgains"operator": an accent-tinted, right-aligned bubble with a "human" tag, for replies a person sent during a takeover (the contact staysfrom="user"). Additive union member; existing roles unchanged.<SessionPage>—keyFacts/memories/handoverare now optional (default[]) so live sessions needn't supply summary data. Non-breaking (looser).<SessionsOverviewPage>— the session table reflows to stacked cards on mobile (≤640px) with a full-width row action; desktop unchanged.
v4.4.0 — 2026-06-07
Sessions read-path — the operator Sessions experience (Phase 1). Lockstep with @magicblocksai/[email protected]. Additive — minor, no breaking changes.
Added
<SessionsOverviewPage>(§16.29) — the top-level Sessions table: the Contacts-page chrome (.list-screen+.tbl+.kpi-delta-tile) plus a date-range, a stat strip, six state tabs (All · Live · Human takeover · Needs attention · Follow-up queued · Completed), a filter slot (compose<FilterPopover>+<FilterChipGroup>per dimension), and rows with Human-takeover / Needs-attention flags + Take over / Review actions.<SessionPage>(§16.28) — a completed session, summary-led: hero (identity · channel · agent · Goals chips · stats) →<SessionSummaryBand>→ a collapsible transcript slot (a composed<ChatTranscript>) +<MemoriesDialog>.<SessionSummaryBand>(§16.27) — the completed-session summary header; composesKeyFactGrid+MemoryList+HandoverOutcome.<MemoriesDialog>(§16.26) — the expand-memories popup; composesModal+MemoryList.<HandoverOutcome>(§16.25) — webhooks fired + handover target, with CSS-drawn glyphs.
Changed
Memorygains optionaltype/sourceQuote/crossSession— rendered only when present, so existing<MemoryList>usage is unchanged.
v4.3.1 — 2026-06-06
<PipelineBar> restyle; lockstep with @magicblocksai/[email protected]. Patch — no API change.
Changed
<PipelineBar>now renders a thin segmented bar — a.pip-segper stage with the label below, unified with<LifecycleBar>: past stages ink-washed, the current stage accent + glow, future hairline (replacing the chunky filled-cell pill). Props are unchanged (stages/current/onStageClick/ per-stagemetric+ click-to-move all intact) — only the rendered markup + look change.
v4.3.0 — 2026-06-06
No @magicblocksai/ui changes — lockstep no-op bump with @magicblocksai/[email protected], which ships the §16.15 Contacts-list .ct-* chrome + the .tbl-check select-column primitive as operator CSS (app-team R4).
v4.2.0 — 2026-06-06
Three additive primitives for the MagicBlocks app team (NextGen); lockstep with @magicblocksai/[email protected]. Minor — new exports / props / types, no breaking change.
Added
<SystemThemeIcon>— a half-filled-circle "system / auto appearance" glyph, the companion to<SunIcon>/<MoonIcon>for a System / Light / Dark theme control. (The one deliberate, documented exception to the icon family's monoline rule: the right half is filled withcurrentColorso the glyph depicts both themes — still monochrome.)<WorkspaceSwitcher badge>+ the<WorkspaceShell>workspace.badgeslot — an unread / attention indicator on the workspace avatar (true= dot, a value = count), mirroringWorkspaceNavIcon.badge. When set, the avatar is wrapped in a non-clipping.ws-switcher-avatar-frameso the corner badge escapes the avatar'soverflow:hidden.change_stage+change_segmentaction types — two newActionTypemembers, theirAction-union variants (ChangeStageAction/ChangeSegmentAction), and config-lessactionRegistryentries (so they appear as<ActionPicker>rows). The stage / segment pickers are workspace-owned — the host app renders them; no stage or segment data lives in the kit (config-less, likedo_not_respond).
v4.1.2 — 2026-06-05
Keyboard accessibility for the kit's pointer-driven interactions; lockstep with @magicblocksai/[email protected]. Patch — no API change.
Changed
<DataTable tableKey>— column resize and reorder are now keyboard-operable. The resize grip is a focusablerole="separator"splitter (←/→, Shift = larger step,Home/End= min/max); headers grab withSpace, then←/→to move,Escto cancel. Mouse drag unchanged.<Checklist reorderable>— keyboard reorder (Spacegrab ·↑/↓move ·Esccancel), guarded so it never collides with the row's tick checkbox or more-button.<AgentBuilderShell>— the rail resizer gainsaria-valuenow/min/max+Home/End, completing the W3C window-splitter pattern (←/→already worked).
The audit confirmed <Kanban>, <Carousel>, <SortableList> / <SortableHandle>, and <RailBlockItem> were already keyboard-accessible. No new exports, props, or variants.
v4.1.1 — 2026-06-05
Contact-page polish; lockstep with @magicblocksai/[email protected]. Patch — no API change.
Changed
<Sparkline>/<ScoreRing>read thinner, kit-wide — lighter default stroke weights (CSS-only; no markup change). Applies everywhere they're used, including<EngagementScore>.<ContactDetailPage>— the Memories tab now wraps the memories and key-facts columns in.cd-cards (matching the Overview cards), and the AI-summary card's sparkle icon is sized correctly (was previously ballooning).
v4.1.0 — 2026-06-04
The AI-native contact page. Additive minor; lockstep with @magicblocksai/[email protected]. A new <ContactDetailPage> plus the seven contact-page primitives it composes (chapters 16.18–16.24) — industry-agnostic chrome + agent-captured intelligence.
Added
<ContactDetailPage>— the page: a warm identity hero + lifecycle journey bar + universal stat strip, then tabs over Overview / Sessions / Memories / Notes. Composes the primitives below +DropdownMenu(share / opt-out / delete). Renders the reference data with zero props (like the other*Pages).<LifecycleBar>+LIFECYCLE_STAGES— segmented lead lifecycle (Engage → Qualify → Follow-up → Converted → Re-engage).<QuietHoursIndicator>— read-only SMS-safe send window with a live "now" marker (10DLC / quiet-hours compliance).<MemoryList>— free-form, agent-summarised memories.<KeyFactGrid>— structured agent-captured facts with provenance (grid/listlayouts).<EngagementScore>— composesScoreRing+Sparkline+ a "computed from…" basis line.<SessionCard>— outcome-led session card with a sentiment rail, metric strip, channel chip (Webchat / SMS / Voice / Email) + collapsible transcript.<TriggerAgentDialog>— channel-aware outbound agent picker (composesModal+Button).
Notes
- Chapter 16.16 ("Contact detail composition") is elevated to demo the real
<ContactDetailPage>(hydrated live on the kit site); its chapter-private.cd-*CSS is retired in favour of the shipped_shared.cssoperator family. - All seven primitives ship four surfaces; declarative leaves carry active markup-equivalence fixtures, stateful/portal ones skip per precedent.
v4.0.0 — 2026-06-04
Explainability consolidation — complete. Breaking major; lockstep with @magicblocksai/[email protected]. The unified set (<Trace>, <SourcePassage>, <SourceCard>) shipped additively across 3.4.0–3.6.1; 4.0.0 cleans up the superseded names. See docs/MIGRATING-TO-4.md in the repo.
Breaking
<CompactTrace>removed (+CompactTraceProps/CompactTracePhase/CompactTraceStatus). Use<Trace>— it'slayout="compact"by default, andlayout="timeline"for the full robot-head. Types:TraceProps/TracePhase/TraceStatus.<EvidenceQuote>is now a thin alias of<SourcePassage>— it renders.source-passagemarkup (mappingsource→cite), not the old.evidence-quoteDOM. The component +sourceprop still work; restyle against.source-passage*or switch to<SourcePassage>.
Deprecated (still exported, unchanged — no action required)
<TraceTimeline>→<Trace layout="timeline">(TraceTimeline is the engine Trace renders for the timeline layout).<TraceItemCard>→<SourceCard>(TraceItemCard is the lightweight card Trace renders forkind:"card"items).
Notes
<JourneyGraph>is unchanged + first-class.- No CSS removed —
.evidence-quote*ships on as a styling back-compat alias;<Trace>renders the existing.compact-trace*/.trace-timeline*class families.@magicblocksai/[email protected]is a lockstep no-op.
v3.6.1 — 2026-06-04
Fix — restore the <Trace> implementation missing from the 3.5.0 / 3.6.0 tags. A release-staging error (an aborted git add on an already-renamed file) left CompactTrace.tsx (the <Trace> rename + layout="timeline"), index.ts (the Trace barrel exports) and TraceTimeline.tsx (the @deprecated tag) out of the 3.5.0 and 3.6.0 tags — so those published packages had no Trace export despite their CHANGELOGs announcing it. This patch lands the actual code.
If you installed 3.5.0 or 3.6.0, update to 3.6.1 to get <Trace> / layout="timeline". <CompactTrace> kept working throughout (the 3.4.0 code under the old name); <JourneyGraph> edge hover (3.6.0) was unaffected.
v3.6.0 — 2026-06-04
<JourneyGraph> — edge hover detail. Additive minor; lockstep with @magicblocksai/[email protected]. Closes the Next Gen app-team Flow-map request.
Added
JourneyGraphEdge.title?: string— the transition's rule in full (the shortlabelstays a summary). Rendered as a native SVG<title>tooltip; a titled edge also becomes keyboard-focusable +aria-labelled, with a wide invisible hit-area so the thin line is easy to hover, and highlights on hover / focus. Untitled edges render unchanged.
v3.5.0 — 2026-06-04
Explainability consolidation, part 2 — <Trace>, the unified reasoning-trace view. Additive minor; lockstep with @magicblocksai/[email protected]. Nothing is removed — every prior name still works.
Added
<Trace>(+TraceProps/TracePhase/TraceStatus) — the single reasoning-trace view, on oneTraceEvent[]spine, with two layouts: -layout="compact"(default) — the nested-disclosure trace for chat-sized surfaces (this is the former<CompactTrace>). -layout="timeline"— the full "robot head": a rich, always-expanded vertical timeline for a full operator pane, rendered via<TraceTimeline>over the same events. One component now covers both surfaces.
Deprecated
<CompactTrace>(+CompactTraceProps/CompactTracePhase/CompactTraceStatus) → renamed to<Trace>(it'slayout="compact"). Still exported as an alias; switch imports toTrace.<TraceTimeline>→ use<Trace layout="timeline">(which renders it). Unchanged and still exported.
Notes
- Additive — no exports removed, no defaults changed.
<Trace>renders the existing.compact-trace*/.trace-timeline*class families (no new CSS). - The future
4.0.0major is reserved for *removing* the deprecated names once consumers have migrated to<Trace>.
v3.4.0 — 2026-06-04
Explainability consolidation, part 1 — the knowledge unit + a trace that renders it. Additive minor; lockstep with @magicblocksai/[email protected]. The explainability components begin converging onto one TraceEvent[] spine and one vocabulary. Nothing is removed or renamed in this release — the breaking cleanup (rename CompactTrace→Trace, retire TraceTimeline/JourneyGraph, re-chapter §20) lands in 4.0.0.
Added
<SourcePassage>+SourcePassageProps— the quoted source / evidence / citation passage: a verbatim quote with a mono cite line (source · relevance · freshness · optional link),<mark>highlight, six tones. Supersedes<EvidenceQuote>. (chapter 20.8)<SourceCard>+SourceCardProps— the knowledge card: alabel · value · metahead that expands to a source body, supplied eagerly (details) or loaded on first open (loadDetails) and cached. Owns the collapsed → loading → loaded → error+retry lifecycle. Supersedes<TraceItemCard>. (chapter 20.9)createAsyncReveal/useAsyncReveal(+AsyncRevealController/AsyncRevealSnapshot/AsyncRevealState/CreateAsyncRevealOptions/UseAsyncRevealResult) — the load-on-open primitive powering<SourceCard>and any consumer disclosure that fetches on first open. Pure controller +useSyncExternalStorehook;idle/loading/loaded/error+retry, cached after the first load.<CompactTrace>— knowledge on demand + width follows open: -sourceitem kind onTraceEvent.items(+TraceSourceRef): carries a lightweight reference;<CompactTrace>renders each via a load-on-open<SourceCard>. -resolveSource?: (ref) => Promise<ReactNode>— loads eachsourceitem's body on first open, so a trace shows the knowledge it used without carrying every passage inline. -defaultAllPhasesOpen?— start with every phase open (the timeline-style preset). - Width follows open — closed, the trace is a discrete resting pill that hugs its content (so it doesn't overwhelm a chat / session window); open, it goes full-width.
Deprecated
<EvidenceQuote>→ use<SourcePassage>(migratesource→cite). Still exported, render unchanged; becomes a thin alias in4.0.0.<TraceItemCard>→ use<SourceCard>(props carry over; adds the asyncloadDetailsbody). Still exported, render unchanged; alias in4.0.0.
Notes
<TraceTimeline>gains thesourceitem kind +TraceSourceReftypes only (render unchanged; an unknown item kind falls back to a bullet row, so it degrades gracefully). It is not deprecated in this release.
v3.3.1 — 2026-06-04
Lockstep no-op with @magicblocksai/[email protected] (a .compact-trace CSS refinement: the detail / knowledge cards now flow into a responsive grid so <CompactTrace> reads well at any width — chat panel to full pane). No UI source change.
v3.3.0 — 2026-06-04
<CompactTrace> — condensed reasoning trace for chat-sized surfaces. A nested-disclosure variant of <TraceTimeline> (chapter 20) for chat-testing windows, embeds, and side panels: top (closable to a resting pill) → phases → steps → inline full-reveal detail, with timing at every level. Composes the disclosure mechanics + the timing helpers + <TraceItemCard>. Additive minor; lockstep with @magicblocksai/[email protected].
Added
<CompactTrace>+CompactTraceProps/CompactTracePhase/CompactTraceStatus. Takes the sameTraceEvent[]as<TraceTimeline>; groups events by the newphasefield. Nophaseon any event → a flat two-level list (top → steps), so short traces stay simple.TraceEvent.phase?— additive.<TraceTimeline>ignores it, so oneTraceEvent[]feeds both components.groupTracePhases/isFlatTrace/TracePhaseRun— the phase grouping helpers (contiguous-run), exported for pre-grouping.formatAbsolute/formatDeltaare now exported (the shared trace timing formatters).
Notes
<TraceTimeline>is unchanged beyond the additivephase?field. The.compact-trace*styles ship in@magicblocksai/[email protected].
v3.2.0 — 2026-06-03
WorkspaceShell adoption blockers — router-aware + sidebar-first. The four gaps NextGen was holding the shell swap on. Fully additive — every default is unchanged. Lockstep with @magicblocksai/[email protected]. App-team Round 20.
Added
linkComponenton<WorkspaceShell>(and on<WorkspaceNavIcon>/<WorkspaceSwitcher>/<WorkspaceUser>) — a router-aware link injected and threaded to every rail link, so an SPA navigates client-side instead of full-page-reloading. Active state stays the consumer'sactiveprop (the kit never computes routes). New exported typeRailLinkComponent. (A — blocker)user.menu— turn the user cluster into a menu trigger (sign-out, account) for imperative actions anhrefcan't express. Composes the kit<Menu>(opens upward);{ divider: true }separates sections. New exported typeWorkspaceUserMenuItem. (B)actionson<WorkspaceShell>+onClick/badgeon nav items — keep ⌘K / Ask-Sage / notifications in the rail (sidebar-first, no top bar).<WorkspaceNavIcon badge>renders a dot (or small count). New exported typeWorkspaceActionDef. (C)loadingon<WorkspaceShell>— a top-of-main nav-progress sliver for a rail-only shell (composes<RouteProgress>). (D)
Notes
.app-shell-maingainsposition: relative(a positioning context for the rail-only loading sliver). No visual change; only matters for absolutely- positioned descendants, which should anchor to the main column anyway.- Every change is opt-in via a new optional prop — existing
<WorkspaceShell>/ rail-primitive usage renders byte-identically (markup-equivalence unchanged).
Fixed
<Logomark>/<Logo>now render the real brand artwork. The glyph was a 2-quadrant pink+yellow approximation (6 polygons, off-brand hexes); it is now the canonical four-quadrant spark (pink · yellow · green · blue, 12 polygons from02-icon). The wordmark was plain<span>magicblocks</span>display-font text; it is now the designed "magicblocks" logotype, inlined as acurrentColorSVG so it follows the lockup colour (variant="dark"→ paper). Component API unchanged —<Logomark>/<Logo size variant>are drop-in; only the rendered SVG changed. Anything composing<Logo>(WorkspaceShell,SidebarHead,TopNavbrand slots) inherits the fix.
Docs patch (lockstep with @magicblocksai/[email protected], no CSS change).
Docs
WorkspaceShell.md— new "Composing real NextGen screens" section: worked, typecheck-verified examples for every screen shape (dashboard · list · master-detail · full-bleed agent builder).
> **Brand-kit *site* polish (not this package):** the old-chapter-URL redirects > were upgraded from meta-refresh stubs to Cloudflare _redirects 301s, and the > shell chapter's stale source comments were tidied to the new numbering.
v3.1.0 — 2026-06-03
NextGen WorkspaceShell + the inner-section contract. A strong, beautiful, decoupled operator app shell, built from a consistent primitive kit. Fully additive — nothing removed, no defaults changed. Lockstep with @magicblocksai/[email protected]. App-team Round 19.
Added — the shell
<WorkspaceShell>— batteries-included shell composing the bare<AppShell>with a fully-assembled collapsible rail (brand · workspace switcher · grouped nav · settings · theme · user) + an optional compact top bar. Owns the collapse toggle; sidebar-first (the rail carries settings/theme/profile, so the top bar is opt-in). The inner section is justchildren— any view.- The rail primitive kit (promoted from chapter-private chrome):
<SidebarHead>,<WorkspaceSwitcher>,<WorkspaceNav>+<WorkspaceNavSection>,<WorkspaceUser>,<WorkspaceThemeToggle>(in-rail light/dark). <WorkspaceBar>— the kit's first operator top bar (optional; marketing<TopNav>stays separate).
Added — the inner-section contract
<SectionView>— the thin consistent frame (pinned header · scrollable gutter'd body · optional footer) every standard section composes.<SectionHeader>— the canonical section header (back · eyebrow · title · subtitle · actions · optional tabs). The default standard-section header.
Reconciliation (guidance only — no breaking changes)
<SectionHeader>is the canonical default;<PageHeader>(editable title + metadata),<RoomHeader>(content hero),<RoomTabs>(standalone tabs) are kept for their distinct capabilities, with "when to use which" docs.<DashboardShell>doc rescoped as an *embedded marketing preview* (not the app shell — use<WorkspaceShell>).<AgentBuilderShell>documented as one inner section mounted in<WorkspaceShell>— it does not own the app chrome.
> Bare <AppShell> is unchanged — Spark CRM + the marketing website that > consume it directly are unaffected.
> **Brand-kit *site* note (not this package):** brand.magicblocks.ai chapters > were renumbered into a clean bucket-contiguous order (Operator → 15–24); old > chapter URLs redirect to the new ones. The chapter HTML isn't shipped in this > package, so it's a site edition, not a package change.
v3.0.0 — 2026-06-03
Lockstep major with @magicblocksai/[email protected] — the warm surface token ladder. No TSX/API change in this package; the breaking change is in the shared stylesheet (body / AppShell light-mode page canvas lightens --warm-3 → --warm-1; --bg insets shift --warm-3 → --warm-2). See the css changelog for the full token table + migration. Dark mode is unchanged.
> Why a major with no code change? The kit's css + ui packages are lockstep-versioned, and the css change alters a default (page background), which is breaking for anyone inheriting it. The ui version tracks css so resolvers stay aligned.
Component behaviour
<AppShell>main column now defaults to the recessed warm canvas (--bg-canvas=--warm-1) instead of paper.pageWashis unchanged as an API (now a no-op in light, still meaningful in dark). Consumers who setpageWashfor the recessed look can drop it.<AgentBuilderShell>canvas reads--warm-1; the edit pane stays--warm-2— the white-cards-on-heavy-warm surface mix (Round 17 #1/#4 root cause) is resolved.
v2.4.0 — 2026-06-03
App-team Round 17 — agent-builder shell polish (empathy pass). Lockstep with @magicblocksai/[email protected]. Operator-scoped — no token or page-background change; the marketing website is unaffected.
Added
<AgentBuilderHead density="default" | "compact">— compact tightens padding + shrinks the back button / action pills so the topbar reads ~44px instead of ~64px (frees space above the fold). Default unchanged.
Changed
.ab-jobrests transparent on-pane (hairline outline) instead of a--bg-paperwhite fill — harmonises with the warm pane + the re-skinned rooms. The--bg-paperlift returns on hover / drag as a grab cue.- Journey rail group sits flush on the rail surface — the soft accent-wash gradient behind the blocks was removed (it read as a stray panel).
.ab-rail-blockname left-align made explicit (consistent with.ab-rail-item).
> The app-wide warm surface token ladder (root cause of "pane reads heavy" / white-card jobs) is not in this release — it re-points global --bg + the body background, which the marketing website inherits. Held for a separate decision (see APP-FIXES-LOG Round 17).
v2.3.0 — 2026-06-01
App-team Round 16. Lockstep with @magicblocksai/[email protected].
Added
<ActionPicker layout="grid" | "dropdown">— a"dropdown"layout that swaps the 4-col card grid for a searchable, descriptive<Combobox>: type to filter, each option reads icon · name · one-line description (from the action registry). For long / growing action sets. Default stays"grid"(back-compat); off-channel types are omitted from the dropdown (the grid still shows them disabled). Composes the kit's<Combobox>renderOption— no new primitive.
v2.2.0 — 2026-06-01
App-team Round 15. Lockstep with @magicblocksai/[email protected].
Added
<ActionList>items take an optionalmeta(ActionListItem.meta?: ReactNode) — a dim secondary line under the name (e.g. a trigger summary like "Fires every turn" / "2 conditions"), so the agent-builder's action list shows *what fires when* without clicking each row. MirrorsSourceRow'smeta; single line, ellipsised; omitted when absent (rows withoutmetarender unchanged).
v2.1.1 — 2026-06-01
Follow-up fixes (app-team Round 14). Lockstep with @magicblocksai/[email protected].
Fixed
<TimeSeriesChart>SSR hydration #418 (definitive). The default x-axis formatter no longer callstoLocaleDateStringat all — that's ICU/locale/timezone-dependent, so a minimal-ICU Node server can format differently than the browser even pinned toen-US/UTC (the 2.0.1 mitigation). It now builds"Mon D"from a fixed month table +getUTCMonth()/getUTCDate()— identical on server and client. PassxFormatfor locale-aware labels.
Changed
<ChatComposer>disabled-state helper text lifts from faint → soft (--fg-soft) so the "why is this off" message stays legible while the input row is dimmed.
v2.1.0 — 2026-06-01
App-team backlog sweep (Round 13). Lockstep with @magicblocksai/[email protected].
Added
<DashboardTile maxHeight>— cap the body for long feeds (latest sessions / missing knowledge). The body scrolls (overflow-y: auto) while the header + footer stay put. Accepts a number (px) or any CSS length.
> The rest of the Next Gen backlog was already shipped in earlier releases (RoomTabs, RoomHeader, SubNav, SourceRow, PageHeader backLink/metadata, DashboardNavGroup mode, SageDrawer onExpand/headerActions, KpiTile density/href, Sparkline tone, LifecyclePill custom, Button/Badge asChild, Card fill="danger", Tabs variant="text", the full lucide-replacement icon set) — no further changes needed. OptionCardGrid is covered by the existing <RadioCardList columns>.
v2.0.1 — 2026-06-01
Bug fixes from the Next Gen agent-builder (app-team Round 12). Lockstep with @magicblocksai/[email protected].
Fixed
<ChatMessage>no longer collapses to ~1 character wide in a narrow container (e.g. a ~340px test rail). The grid bubble track is nowminmax(0, 1fr)so text wraps at the container width instead of the bubble's min-content.<AgentBuilderHead version={…}>accepts a block-level node (e.g.<VersionSwitcher>) without invalid SSR nesting. A string/number still renders in the.ab-versionpill; a node renders inline as-is (no<span>wrapper → no<span><div>hydration error #418).<TimeSeriesChart>default x-axis formatter is now SSR-deterministic (pinneden-US+timeZone: "UTC"), fixing the server/client hydration mismatch from the ambient-localetoLocaleDateString. PassformatXfor locale-aware labels.
Changed
- Agent-builder rail glyphs bumped 14px → 16px (
.ab-rail-item+.ab-rail-groupglyphs) to read better next to the 13px labels. - Reverted the v1.72.0 builder-pane (
.ab-pane) warm gradient back to a flat--bgfill — surface backgrounds stay flat (operator preference).
v2.0.0 — 2026-06-01
Breaking: the <AppShell> sidebar now defaults to paper/white. Lockstep with @magicblocksai/[email protected].
Changed (breaking)
<AppShell sidebarTone>default flipped"warm"→"paper". Every AppShell sidebar is now white/paper by default (a crisp rail against the warm main column). The warm-cream pin (the v1.x look) is now opt-in.<DashboardShell>sidebar (.dash-side) now defaults to paper/white too (was pinned warm-cream), so app + dashboard chrome share one uniform white-rail scheme. Pin it warm again via the--dash-side-bgCSS var.
Migration
- Want to keep the v1.x warm-cream sidebar? Pass
sidebarTone="warm": ``tsx <AppShell sidebarTone="warm" sidebar={<MyNav />}>…</AppShell>`` - No code change needed to adopt white — it's the new default.
--app-shell-side-bgstill overrides either tone with a custom colour.- Unaffected:
<DashboardShell>/.dash-sideis a separate primitive and keeps its warm sidebar.
v1.72.0 — 2026-06-01
Agent builder polish — paper sidebar option + cleaner active-block treatment. Lockstep with @magicblocksai/[email protected].
Added
<AppShell sidebarTone>—"warm"(default, back-compatible — the warm-cream pin) or"paper"(white/paper rail against the warm main column). Emitsdata-sidebar-tone="paper"only when set to"paper", so existing AppShell usages render unchanged. Overridable per-instance via the--app-shell-side-bgCSS var. App-team request 0.
Changed
- The agent builder's active rail block (
.ab-rail-block.is-active) drops itsbox-shadowhalo for a clean accent border + soft fill (no glow). - The builder pane (
.ab-pane) gains a gentle top-down warm wash (echoing the Journey group's accent gradient) instead of a flat fill.
v1.71.0 — 2026-06-01
Agent builder follow-up — <JobCard>s reorder within the Jobs-to-Do tab. No TSX change (a JobCard is already a valid <SortableList> renderRow body, exactly like <RailBlockItem>); the drag affordance was a missing-CSS gap, now shipped in @magicblocksai/[email protected]. Lockstep bump.
Changed
- Docs + chapter 18.7 demos now wrap the pane's
<JobCard>s in<SortableList>to show whole-row drag-reorder (the··handle is the affordance).
v1.70.0 — 2026-06-01
App-team Round 8 — the agent builder shell family (chapter 18.7). The rail + pane builder layout — ~100 chapter-private ab-* rules with no shipped component — is now packaged as 11 composable primitives. Additive. Lockstep with @magicblocksai/[email protected].
Added
<AgentBuilderShell>— the two-pane builder layout (head+railslots over a pane child) with a drag-to-resize boundary (--ab-rail-w, clamped 240–360px; drop-in uncontrolled, or controlled viarailWidth/onRailWidthChange; ←/→ on the focused handle). Mount inside<AppShell>; rail + pane only (the testing chat lives outside). Compound memberAgentBuilderShell.Pane(AgentBuilderPane) — the edit pane withhead/footslots.<AgentBuilderHead>— builder topbar: back · name + type chip · version + unsaved meta · trailing Share / Test / Save pills.<BuilderRail>·<RailGroup>·<RailItem>·<RailBlockItem>— the rail scroll container, collapsible sections, leaf nav rows, and draggable Journey-block rows.RailBlockItemis therenderRowbody of a<SortableList>(drag-reorder is the already-shipped primitive).<PaneHeader>·<PaneTabs>/<PaneTab>·<JobCard>— pane header (collapse · glyph + title + badge · description · tabs), underline-active tab strip (roving tabindex; drop-in or controlled), and general / collect job rows.
Notes
- DOM byte-verified by three composition fixtures (
AgentBuilderHead,BuilderRail,AgentBuilderShell.Pane) that transitively render every leaf; the resizer shell + leaves carry documented skip-fixtures. - Polish: keyboard
:focus-visiblerings on every interactive builder control (a11y; DOM-neutral).
v1.69.0 — 2026-05-31
App-team Round 7 — actions wizard config system Phase 3: recursive sub-actions, the Run Task panel, and the wizard's Goal step. The wizard is now complete end-to-end. Additive (the unknown sub-action placeholders become typed Action references). Lockstep with @magicblocksai/[email protected].
Added
<SubActionField>— the recursive nested-action editor. A button's tap action and a form's submit action are themselvesActions, edited by a restricted<ActionPicker>+ a nested<ActionConfigPanel>. The allow-list excludesadd_buttons, so the recursion always terminates. ExportsSUB_ACTION_TYPES(button set) +FORM_SUBMIT_ACTIONS(form set — no nested forms; allows Human Takeover).<ActionPicker>gains anallow?: ActionType[]prop.<RunTaskActionConfig>— Run Task graduates from declarative fields to a custom panel: a Select | Custom toggle (saved prompt fromcontext.promptsxor custom instructions) + a functions<MultiSelect>(context.functions).RunTaskActiongainspromptId?/functionIds?.<GoalStep>— the wizard's Step 3: a goal<Select>(context.goals) + a conditional$value (<Input type="number">). NewGoalConfigtype.- Nested-action data —
ButtonsAction.buttons[].action?: ActionandFormsAction.submitAction?: Action(wereunknownplaceholders).ActionConfigContextgainsprompts/functions/goals.
Changed
- Add Buttons / Add Forms panels now edit their nested sub-actions (each button row expands to a
<SubActionField>; forms gain an on-submit<SubActionField>). - Run Task is now a registry
Panel(was declarativefields).
Notes
- Conditions (Step 2) needs no new component —
<QueryBuilder>/<ConditionRow>(chapter 18.1) are the conditions builder, demoed at 18.12 Demo 2. Step 2 + Step 3 complete the 3-step wizard. - Grounded in v1's
director-action-content.tsx/experience-buttons.tsx/select-task.tsx/director-goal.tsx. The new components compose stateful primitives and ship fixture-less per kit precedent; four-surface is met. Deferred: multiple form-submit actions (the kit ships a singlesubmitAction); Switch Journey SMS-sender provisioning.
v1.68.0 — 2026-05-30
App-team Round 6 — actions wizard config system Phase 2: every remaining action type now has a working config surface. Four declarative types, four custom panels, plus the config-less Do not respond. Additive (the 8 placeholder types become fully-typed variants; PendingAction is retained as a deprecated alias). Lockstep with @magicblocksai/[email protected].
Added
- Four custom config panels for the conditional/composed action types: -
<SwitchJourneyActionConfig>— block ↔ channel cascade (<Select>block picker with a "Current Block" sentinel · a Switch-Channel<Switch>revealing a channel<Select>and, for SMS, a sender<Select>) + a follow-up section (Send message / Auto +<MessageField>) shown only while staying on the current block. -<ButtonsActionConfig>— two<MessageField>s (before / after) bracketing an inline button-label repeater (add / remove, ≤50 chars). -<FormsActionConfig>— form<Select>+ before / submit / privacy<MessageField>s. -<HumanTakeoverActionConfig>— follow-up<Select>(None / Slack) revealing a Slack connection<Select>+ Channel ID<Input>; a Staff-Availability<Select>(default "Always"); transfer + out-of-hours<MessageField>s. numberfield kind —ActionFieldSpecgainsnumber→<Input type="number">(Embed height; reusable for delays / limits). The four declarative types (End Chat, Opt-Out, Run Task, Embed) wire purely through the registry'sfields— no bespoke components.- Typed variants —
SwitchJourneyAction,EndChatAction,RunTaskAction,ButtonsAction(+ActionButtonItem),FormsAction,OptOutAction,EmbedAction,HumanTakeoverAction(+SwitchFollowUpKind,HumanTakeoverFollowUp), andSelectOptionare now exported.ActionConfigContextgainsjourneyBlocks/switchChannels/smsSenders/forms/slackConnections/availabilityProfiles.
Changed
- The agent-builder registry now wires all 13 action types (was: 4). Adding a typical type remains a one-entry change.
Deprecated
PendingAction— every action type now has its own variant. Retained as a type alias of the real variants so existing imports resolve; prefer the concrete types.
Notes
- Field-for-field grounded in v1's
experience/action.ts. The recursive per-button / form-submit sub-actions (v1actionButton.action/form.submitActions), Run Task's prompt-library reference + functions, and Conditions/Goal componentisation stay deferred to Phase 3. The four new panels compose stateful primitives (<MessageField>/ repeater / conditional reveals) and ship fixture-less per kit precedent; four-surface (chapter HTML +_shared.css+ TSX + React tab) is met.
v1.67.0 — 2026-05-30
App-team Round 5 — agent-builder actions wizard config system (Phase 1). The Actions wizard (chapter 18.12) is componentised, and selecting an action now reveals its config panel — driven by a declarative action registry. Additive; lockstep with @magicblocksai/[email protected].
Added
- Schema-driven action config. A declarative
actionRegistry(exported) is the single source of truth for the agent-builder's 13 action types.<ActionConfigPanel action onChange context>renders the selected action's config — a customPanelif the type declares one, else its declarativefields(each via<ActionField>→ the matching kit primitive), else the type's description. Adding a typical action type is a one-entry change. Exported types:Action(+ variants),ActionType,ActionFieldSpec,ActionTypeSpec,ActionRegistry,ActionConfigContext,MessageRef,CalendarProvider. <ActionWizard>— the 2-column wizard shell (actions list · pane), slot-based + presentational.<ActionList>— the actions mini-column (count · rows · New Action).<WizardSteps>— numbered Action · Conditions · Goal step indicator (active/done/todo).<ActionPicker>— registry-driven action-type tile grid + detail card; disables off-channel types ("Website only").<ActionConfigPanel>+<ActionField>— the generic config renderer (FieldSpec→<Input>/<Select>/<Switch>/<DurationField>/<MessageField>, with declarativevisibleWhen).<MessageField>— snippet-or-custom message editor (Select | Custom). The kit's most-reused config row.<CalendarActionConfig>— the custom Add Calendar panel: provider + embed type + account/event, plus the MagicBlocks→Calendly autofill<FieldMapper>with locked Name/Email rows.
Phase 1 wires Auto, Add Link, Send Message, Add Calendar; the remaining nine types carry label/icon/description and render their description until their config ships in a later batch. Chapter 18.12 reworked onto the new components.
Notes
- Markup-equivalence fixtures for the new interactive/composition components are deferred (verified via the chapter demo + browser); the four-surface rule — chapter HTML +
_shared.css+ TSX + React tab — is met for all eight.
v1.66.0 — 2026-05-30
App-team Round 4 — agent-builder shell unify. Chapter 18.7's agent-builder shell now runs on the canonical <AppShell> instead of a bespoke duplicate, and the collapsed icon-band it pioneered is promoted into the kit. Additive; lockstep with @magicblocksai/[email protected].
Added
<WorkspaceNavIcon>— the 36×36 workspace-sidebar nav item for<AppShell>'s collapsed icon-band.icon+label(drives the collapsed-mode slide-out tooltip +aria-label+ the expanded text), optionalcount,active(accent leading-stripe +aria-current="page"); renders<a>whenhrefis set, else<button>. Declarative — no'use client'. Demoed in chapter 13.5.
Changed
<AppShell sidebarMode>— the collapsed mode now renders the full icon-band visual (56–64px rail, centred 36×36 items, slide-out tooltips, accent active-stripe, expanded labelled rail), promoted from the chapter-18.7 reference. Opt-in + back-compat: every existing<AppShell>withoutsidebarModeis byte-for-byte unchanged (all new rules key off[data-sidebar-mode]). New defaultsidebarWidthCollapsed="56px"matches the reference look. The sidebar header / workspace-switcher / user-menu chrome (.app-shell-side-head,.ws-switcher*,.ws-user*) ship as documented shared CSS you compose into thesidebarslot; the collapse toggle stays consumer-owned. Chapter 13.5 gains a collapsed-shell demo.
Notes
- The agent-builder shell (chapter 18.7) is rebuilt on
<AppShell sidebarMode="collapsed">+<Logo>+<WorkspaceNavIcon>— its bespoke workspace-rail is gone, and the placeholder logo is now the real<Logomark>. Its outer-shell React tab is now real; the inner builder pane stays a vanilla demo pending the deferredBuilderShellfamily (.scratch/app-screens-component-gaps.md).
v1.65.0 — 2026-05-30
App-team Round 3 — the P2/P3 audit + design-question backlog from docs/brand-kit-requests.md, plus three agent-builder-room primitives. All additive; lockstep with @magicblocksai/[email protected].
Added
<RoomHeader>(#22) — the three-line room hero (eyebrow · display heading · lede) that opens every agent-builder room. Thin composition of<Eyebrow>+<Heading display>+<Lede>;levelsets the heading rank (default2, since rooms sit under the page<h1>);titleis aReactNodeso it can carry an<em>flourish. No actions slot — page actions live on<RoomTabs trailing>. Chapter 13.20. Declarative (no'use client').<SourceRow>(#25) — resource row: leading tinted<IconChip>+ name + metadata sub-line +statusslot +trailingslot. The canonical "list of connected sources you can see the health of and toggle" layout — knowledge collections, MCP tools, channels. Presentational: drop a<Chip>intostatus, a<Switch>intotrailing. Chapter 7.32.
Changed
<RadioCardList layout="grid">(#23) — newlayout("list"|"grid", default"list") +columns(default2). The grid variant arranges the existing native-radio cards in an N-column grid via a--rcl-colscustom property. Resolves the app team's "OptionCardGrid" ask as a variant of the existing primitive — same radio semantics, same keyboard contract — rather than a parallel component.<ChatTranscript centerEmptyState>(#10) — new boolean; vertically centres the empty state in the scroll viewport, for welcome / zero-history scenes.<Sparkline tone>(#12) — newtone("success" | "danger" | "info" | "warning"); tints the line via adata-tonehook, so a sparkline carries semantic colour without a per-instancecolor.<LifecyclePill custom>(#14) — newcustom={{ label, tone }}escape hatch for workspace-defined stages outside the 17-value taxonomy (tonemaps to the kit's existing badge/pill tints).valueis now optional — supply one ofvalue/custom.<KpiTile href>(#15) — newhref; when set, the whole tile renders as an<a>drill-down with.is-clickablehover/focus affordances.forwardRefretargeted toHTMLElementto cover both the<a>and<div>branches.<TimeSeriesChart defaultPalette="brand4">(#16) — newdefaultPalette;"brand4"auto-assigns the four brand series colours (pink · yellow · blue · green) by index to any series without an explicitcolor.<ChatComposer>disabled (#9) — only the input row dims now;helperTextstays at full opacity and readable (pointer-events: nonestill covers the whole composer). CSS-only.
Audited — no change
<Avatar>dark mode (#7) — uses an explicittone, not a name-hash palette; no hash-vs-dark-ground clash exists to fix.<DataTable striped>in<SectionCard>(#8) — the v1.29.0 SectionCard chrome-strip already removes the inner radius, so striped tints don't clip the card corners.<Heading><em>accent (#24) — confirmed: the accent italic renders correctly inside headings.
Deferred
- Tabs cross-route deep-link styling (#11) · DashboardTile sticky/expandable (#13) — later round.
v1.64.0 — 2026-05-29
Accessibility batch (website BRAND_KIT_REQUESTS.md R033/R035; lockstep with @magicblocksai/[email protected]).
Changed
<RevenueCalculator>(R033) — the four conversion-rate range sliders now associate<label>↔<input>viahtmlFor/id. No API change.<SiteFooter>(R035) — column titles render as<h2>(was<h5>), fixing a heading-order skip on marketing pages. Styling unchanged; consumers who restyled footer headings via anh5element selector should switch toh2.
R034 + R036 are CSS-only — see @magicblocksai/[email protected].
v1.63.0 — 2026-05-26
Per-section LEGO. Four cross-page primitives extracted from the Platform-Tour Operator pages — the smaller composable bricks that sit inside the page-shell pieces shipped in v1.62.0. All additive.
Data display (composed in chapter 7.10)
- New
<StatTile label value delta direction>— the kit's canonical KPI tile (chapter-07.stat-tile). Three variants:default(24px value),big(36px),moment(ink-on-accent for the one wow number per page). Optionalsparkslot for an absolutely-positioned sparkline. Sibling to<StatCard>(chapter-05.stat-cardshoulder card) — reach for<StatTile>for KPI strips,<StatCard>for the card-on-warm chrome.
Agent surfaces (composed in chapter 18.8 agents-list cards)
- New
<AgentChannelStrip label>+<AgentChannelIcon active title>— multi-channel icon chip strip. Indicates which channels (web / SMS / email / voice / WhatsApp) an agent supports, a contact has been reached on, or a campaign touches..is-onchips switch to the accent-soft / accent fill. The underlying CSS (.ag-card-channels,.ag-channel-icon) promoted from chapter-18-inline to shared.
Settings surfaces (composed in chapter 25.7 settings/credits)
- New
<PackageGrid label>+<PackageOption selected>— radio-style "pick one amount / tier / size" grid. Six columns desktop, three tablet. Originally the top-up amount picker; generalises to plan tiers, file-size limits, retention windows. CSS (.st-package-grid,.st-package) promoted from chapter-25-inline to shared.
Trace / RAG surfaces (composed inside <TraceTimeline>, now exposed standalone for citation cards)
- New
<TraceItemCard tone label value meta details defaultOpen>— the expandable card from the trace-item family, previously rendered privately inside<TraceTimeline>. Exposed as a standalone export for citation cards inline in knowledge-base pages, retrieval-debug surfaces, and "what was matched?" affordances outside of a timeline. Read-only whendetailsomitted; interactive<button aria-expanded>when provided.
Three React snippet tabs rewritten to demonstrate composition with the new pieces: 7.10 (StatTile), 18.8 (AgentChannelStrip + the v1.62.0 ListScreen* pieces together), 25.7 (PackageGrid). Each fixture for the layout-glue components skips markup-equivalence with documented rationale — same pattern as v1.62.0.
Two CSS surface-region updates require surface-manifest.txt regen (handled in-commit): the agent-channel-strip + package-grid blocks both land in the operator surface.
v1.62.0 — 2026-05-26
Page-shell LEGO. Eight layout primitives extracted from the Platform-Tour Operator pages (Contacts list, Campaigns list, Library, Agents list, Settings / Credits, Channels / Chat Appearances). All additive.
List-screen primitive (composed in chapters 14.15, 14.17, 15.12, 18.8, 19.7)
- New
<ListScreen frame>— the page-shell container with the kit-wide.list-screen-frameouter card + inner.list-screenflex column.frame={false}opts out for nesting inside another framed surface. - New
<ListScreenHead>— title + description + actions row. Takestitle/description/actionsprops or achildrenescape hatch. - New
<ListScreenKpiStrip>— 4-up KPI grid container. Body is whatever KPI shape the section uses (<KpiTile>,<StatTile>, custom cards). - New
<ListScreenTabs>+<ListScreenTabCount>— the status-filter tab row plus the count chip rendered inside each tab label. Consumers pass raw<button>children for routing / click control. - New
<ListScreenFoot centered>— page footer. Default layout is pagination (justify-between + top border);centeredswitches to the "Showing N of N" summary used by card-stack lists.
Sub-nav primitive (composed in chapters 13.19, 15.12, 25.7)
- New
<SubNav>— vertical icon+label column container. Flex-column layout; parent owns the surface chrome (bg / padding / border). - New
<SubNavItem glyph count active href>— single nav row. Optional 18px glyph slot, optional count chip, active accent-soft fill with a 3px accent left-stripe. - New
<SubNavLabel>— uppercase mono caption above a cluster of related<SubNavItem>s.
Each fixture skips markup-equivalence with a layout-primitive rationale — these are composed inside larger chapter demos rather than rendered standalone (same pattern as <BillingPage>, <WorkspaceMembersPage>, <SettingsShell>).
v1.61.0 — 2026-05-25
App team Round 2. All changes are additive.
Brand marks (chapter 1.10)
- New
<Logomark>— the canonical 4-colour spark glyph (SVG inlined from06-brand-assets/magicblocks-social-avatar-square-512.svg). - New
<Logo>— Logomark + "magicblocks" wordmark lockup;size(sm/md/lg) +variant(default/dark).
Shell + cross-route
<AppShell sidebarMode>— controlled ("expanded"/"collapsed"). When set, takes precedence oversideWidth; the kit propagates the value viauseSidebarMode()context.<AppShell sidebarWidthExpanded>/sidebarWidthCollapsed— string CSS lengths (defaults"200px"/"64px").<DashboardNavGroup mode>— optional override (auto-defaults from context). In collapsed mode hides the label and stacks items icon + small label.- New
useSidebarMode()+SidebarModefrom the main barrel. - New
<RoomTabs>(chapter 6.16) — top-of-page section tabs with per-tab icon + count chip +trailingslot + optionalsticky. Roving tabindex + arrow-key activation.
Component extensions
<PageHeader backLink>—{ label, href }link above the eyebrow.<KpiTile density="dense">— fits 5+ tiles in a row at narrower widths.<Tabs variant="text">— underline tab list only (no panels).<Card fill="ink">— dark editorial surface.<FilterChipGroup required>— blocks empty-selection in single-select.
Icons — 22 new (chapter 22.3):
- 18 P1:
Bell,Sun,Moon,MessageCircleQuestion,ExternalLink,Heart,MoreVertical,Download,Upload,Globe,MessageSquarePlus,Flask,TrendingDown,UserCog,ArrowRight,Smartphone,MessageSquare,Code. - 4 P3 chevrons:
ChevronDown,ChevronUp,ChevronLeft,ChevronRight.
P3 components
- New
<IconChip>(chapter 7.30) — tinted icon-in-square primitive with six tones. - New
<RateCard>(chapter 7.31) — metric + tonal progress card with optional 4 px tinted bar.
Lockstep with @magicblocksai/[email protected].
v1.60.0 — 2026-05-24
App team Round 1 — first round serving the v2 / Next-Gen app. All changes are additive.
- New
<RouteProgress>(chapter 13) — indeterminate route-load bar withtone/position/heightprops and aprefers-reduced- motionfallback. - New
<LinkTabs>(chapter 6) — cross-route tab variant. Framework- agnostic via alinkAsprop; pass RR7'sNavLink(or equivalent) to get automatic active-state styling (kit styles.tab.activeand.tab[aria-current="page"]). - New
<Slot>lib primitive — Radix-style composition. Merges className (composes), event handlers (chained slot→child), refs (composed). Powers the newasChildprops on<Button>and<Badge>; available to consumers authoring their own asChild-shaped components. <Button asChild>+<Badge asChild>— wrap a router<Link>and inherit the component's styling. Lets<Button asChild><Link …/>keep prefetch / middle-click / cmd-click.<SageDrawer>— newonExpandcallback (kit renders an Expand icon button when set) +headerActionsslot (consumer-owned, beside the wordmark).<PageHeader metadata>— optional horizontal metadata strip rendered below the summary.<Card fill="danger">— soft danger-zone surface (extends the existingpaper/sunk/warmenum).- New
<ExpandIcon>— drawn for<SageDrawer onExpand>. The broader 26-icon backlog from the app-team request list ships in a dedicated future round. - Onboards the MagicBlocks app team (v2 / Next-Gen) as the kit's 4th named consumer — close-out log at
APP-FIXES-LOG.md.
Lockstep with @magicblocksai/[email protected].
v1.59.2 — 2026-05-23
- R001 (reopened) — the ESM build now emits one file per component instead of a single bundled
dist/index.js."sideEffects": false(1.59.1) was inert against a single-module bundle; with real module boundaries restored, a consumer importing a subset of the barrel tree-shakes the rest away — aButton-only import drops from ~1 MB to ~71 KB. CJS output is unchanged (single-file). No public API change. Closes Spark R001.
Lockstep with @magicblocksai/[email protected].
v1.59.1 — 2026-05-22
- R001 — declare
@magicblocksai/uiside-effect-free ("sideEffects": falseinpackage.json). The package has no import-time side effects — no CSS imports, no global mutation, pure re-export barrels — so a consumer's bundler can now tree-shake unused exports instead of pulling the whole component set when importing from the barrel. No code or API change. Closes Spark request R001.
Lockstep with @magicblocksai/[email protected].
v1.59.0 — 2026-05-22
RichTextEditor v2.
- Add
<RichTextEditorPro>— a TipTap-backed rich text editor with a/block palette, a generic@mention hook,{{ }}merge tags, known-platform video embeds, and inline media (paste / drop / upload). Exported from the new@magicblocksai/ui/editorsubpath so consumers importing@magicblocksai/uinever bundle TipTap. - Add
<RichTextContent>— a light, TipTap-free, server-renderable read-only renderer for stored rich-text HTML. - The HTML sanitiser is now a shared
libprimitive, rewritten onnode-html-parserso it sanitises identically in the browser and on the server.<RichTextEditor>uses it — behaviour unchanged. - Closes Spark request R15-1.
Lockstep with @magicblocksai/[email protected].
v1.58.0 — 2026-05-22
- R001 / R002 — 11 new navigation icons for CRM sidebars:
TagIcon,PercentIcon,FilterIcon,GaugeIcon,TrendingUpIcon,MailboxIcon,ChecklistIcon,LeadsIcon,OverviewIcon,AgentsIcon,ConversationsIcon. - Version bumped in lockstep with
@magicblocksai/css1.58.0.
v1.57.0 — 2026-05-20
Lockstep release — no @magicblocksai/ui code changes.
The substance of this version is @magicblocksai/css 1.57.0 (R030): _shared.css is now partitioned into per-surface CSS bundles (/core, /marketing, /operator, /embed) alongside the existing full-kit /tokens and default exports. Consumers who want only the CSS relevant to their surface can import the matching subpath instead of the full bundle.
Lockstep with @magicblocksai/[email protected].
v1.56.0 — 2026-05-20
Round D · Plan 2 — Visualisations.
- Add
<JourneyGraph>— agent reasoning visualisation (chapter 20.6, Explainability). Node-and-edge SVG; controllable selection; consumer-supplied geometry; render-prop detail slot. Escape key deselects. - Add
<Evaluations>— scored-rubric display (chapter 15.10, AI Surfaces). Criterion list with pass/fail/warn status, score readouts, per-criterion notes, controllable expansion. Summary auto-derived from criteria when not supplied. - Add
<ReviewerInbox>— human-in-the-loop review queue (chapter 14.14, Pipeline & CRM). Filter chips + controllable selection + per-row Approve/Reject/Edit affordances (keyboard-accessible). ↑/↓ keyboard nav via useKeyboardListNav.
Closes Round D component shipping (six components total across Plans D1 + D2).
v1.55.0 — 2026-05-20
Round D · Plan 1 — Chapter 23 trio backfill.
- Add
<VoicePlayer>— audio playback chrome for voice agents (chapter 23.2). Compact + expanded variants; controllable playback state; honours prefers-reduced-motion for autoplay. - Add
<WidgetPersonaSwitcher>— visitor-facing persona switcher (chapter 23.3). Cards + header variants; collapses to vertical stack at narrow widths. - Add
<ChannelSandbox>— multi-channel widget preview shell (chapter 23.1). Tab strip over preview pane; Web (composes <WidgetShell>) / SMS (3 bubble row) / Voice (composes <VoicePlayer expanded>) channels.
Replaces the Phase-4 wireframe placeholders in chapter 23 with shipping TSX. Planned-props tables published in earlier versions honoured verbatim. Chapter 23 eyebrow + lede + side-rail entries refreshed to reflect the trio now shipping.
[1.54.0] — 2026-05-19
Minor — Round C Plan 4: Integrations cluster. Five new operator-facing components forming chapter 26 (Integrations). Closes Round C — the operator-chrome arc — alongside the kit-website v3.0 edition mark.
Added
<IntegrationCard>— single integration tile: logo + name + description + status pill (Connected / Disconnected / Error / Pending) + per-card action. Declarative.<IntegrationsGrid>— wrapper with optional category filter chips above a responsive grid of<IntegrationCard>children. UsesuseControllableStatefor filter state.<WebhookConsole>— tabular layout of recent webhook deliveries: timestamp + endpoint + event + status (2xx/4xx/5xx colour-coded) + duration.<OAuthCallbackBanner>— post-auth result banner with three intents (success / failure / pending) using--success-*/--error-*/--warning-*tokens.<IntegrationsPage>— page-shaped wrapper composing the above with optional banner at top, integrations grid in the middle, and webhook console at the bottom.
Chapter
- Chapter 26 — Integrations (new). Operator bucket. Five sections (26.1–26.5). Page-nav routes
25 Workspace → 26 Integrations → 17 Chat Widget— closes back to the Embed bucket. Chapter 17's page-navprevrerouted from chapter 21 to chapter 26.
Quality bar
Every new component visually verified at 1280/720/480/360px viewports in both light + dark modes, with keyboard navigation + focus rings + ARIA semantics confirmed before commit.
Lockstep with @magicblocksai/[email protected].
[1.53.0] — 2026-05-19
Minor — Round C Plan 3: Workspace cluster. Six new team-administration components forming chapter 25 (Workspace), plus a new exported role taxonomy.
Added
ROLE_TAXONOMY+RoleKey+RoleTint— exported constants fromlib/roles.ts. Defines the kit's canonical role set (admin / editor / billing / viewer) with semantic tint mapping. Pattern matchesWIDGET_SCHEMES(chapter 21) andHAPPA_STAGES(lib/happa.ts).<RoleBadge>— role pill primitive. Renders<span class="role-badge role-badge--{tint}">{label}</span>. Declarative; takes a singleroleprop and looks up label + tint fromROLE_TAXONOMY.<MemberRow>— team-member row inside<ul class="member-list">. Avatar + name+email stack + RoleBadge + lastActive + actions. Mobile reflow at ≤480px.<InviteRow>— pending-invite row with email + role + invited-by meta + sent-at + Resend/Revoke action buttons.<AuditLogEntry>— audit-log entry with actor + verb + target + timestamp + optional collapsible before/after diff panel. LocaluseStatefor expand toggle.<TeamHeaderBlock>— page header for team pages: eyebrow + title (with inline member-count badge) + Invite CTA + optional filter rail.<WorkspaceMembersPage>— page-shaped wrapper composing TeamHeaderBlock + member list + invite section + audit-log section. Drop-in for the common workspace-members shape.
Chapter
- Chapter 25 — Workspace (new). Operator bucket. Six sections (25.1–25.6). Page-nav routes
24 Billing → 25 Workspace → 26 Integrations(chapter 26 reserved for Plan C4).
Quality bar
Every new component visually verified at 1280/720/480/360px viewports in both light + dark modes, with keyboard navigation + focus rings + ARIA semantics confirmed before commit.
Lockstep with @magicblocksai/[email protected].
[1.52.0] — 2026-05-19
Minor — Round C Plan 2: Billing cluster. Six new operator-facing components forming chapter 24 (Billing), the kit's first new chapter since the v2.0 unification tag.
Added
<PlanCard>— plan-tier card with name + price + features + CTA. Three intensities (muted/accent/ink). Optional corner pill (Current plan / Recommended).<UsageMeter>— labeled progress bar with current + limit + overage. Three states: healthy / approaching / over. Optional projection line for month-end forecast.<InvoiceRow>— invoice list row: date + number + amount + status pill + download icon. Status states: paid / open / void / uncollectible. Used inside<BillingHistoryTable>or standalone.<PaymentMethodCard>— card-on-file display: brand mark + last4 + expiry + actions. Brands: Visa / Mastercard / Amex / Discover / Generic.<BillingHistoryTable>— composed table wrapping invoice rows with filter chip group + date-sort toggle.<BillingPage>— page-shaped wrapper composing PlanCard grid + UsageMeter stack + PaymentMethodCard + BillingHistoryTable. Drop-in for the common billing-page shape.
Chapter
- Chapter 24 — Billing (new). Operator bucket. Six sections (24.1–24.6). Page-nav routes
21 Designer Toolkit → 24 Billing → 25 Workspace(chapter 25 reserved for Plan C3).
Quality bar
Every new component visually verified at 1280/720/480/360px viewports in both light + dark modes, with keyboard navigation + focus rings + ARIA semantics confirmed before commit.
Lockstep with @magicblocksai/[email protected].
[1.51.0] — 2026-05-19
Minor — Round C Plan 1: Settings cluster. Eight new operator-facing components extending chapter 13 (App Shell). The kit's first settings-page primitives + a page-shaped wrapper that composes them all together.
Added
<SettingsNavRail>— left rail navigation for settings pages. Groups items into labelled sections with optional icons + count badges. Active item carriesaria-current="page"+ accent highlight. Controlled viaactiveKey/onActiveChange, or drop-in withdefaultActiveKey. Mobile (≤720px) reflows to horizontal-scroll so the rail stays usable on phones.<SettingsHeaderBlock>— per-page header for settings pages. Eyebrow + title + description + actions row. Stateless; slot-driven.<PreferenceToggleRow>— labeled row with a<Switch>on the right. Used 5–20× per settings page for boolean preferences. Composes the kit's existing<Switch>primitive.<ApiKeyCard>— masked-by-default token with reveal + copy + revoke actions. Last-used metadata above. Used inside the API keys settings page.<SessionList>+<SessionRow>— list of active sessions with device, location, last-active timestamp, and per-session revoke. Current session is marked and non-revocable.<DangerZoneBlock>+<DangerZoneAction>— red-bordered footer for destructive actions. Takes children — consumers compose any number of action rows. Used on account, workspace, and integration settings pages.<UnsavedChangesBar>— sticky bottom bar with Save + Discard actions. Tracks dirty state via controlleddirtyprop — consumers wire form state (React Hook Form, Formik, plainuseState). Portal-mounted so it floats above page scroll.<SettingsAccountPage>— page-shaped wrapper composing<SettingsShell>+<SettingsNavRail>+<SettingsHeaderBlock>+ body. Drop-in for the common API-keys / Profile / Sessions shape.
Quality bar
Every new component visually verified at 1280/720/480/360px viewports in both light + dark modes, with keyboard navigation + focus rings + ARIA semantics confirmed before commit.
Lockstep with @magicblocksai/[email protected] (CSS for all eight components added to _shared.css).
[1.50.0] — 2026-05-18
Minor — Round CS-2: customer-success taxonomy unification. Follow-up to the v1.49.0 audit. Where v1.49.0 shipped the safe additive fixes and documented the deferred-design items, this round actually unifies the surfaces. No breaking changes — deprecated aliases preserve existing call sites.
Changed — <PipelineBar> default cleaned up
The zero-prop <PipelineBar /> default previously started at lead and used lead → qualified → negotiation → won → renewal. lead is a *lifecycle* stage on the contact, not a stage on the deal — deals don't exist until the lead is qualified into an opportunity. The aligned default now mirrors the <Kanban> zero-prop demo (qualified → discovery → negotiation → won) plus the trailing renewal so the bar still represents the full sales-to-renewal arc.
<PipelineBar>default:qualified (3d) → discovery (5d) → negotiation (Day 4, current) → won → renewal- Chapter §14.2 demo (data-mb-props + hand-rolled HTML + snippet tabs) updated to match
- Production consumers passing their own
stagesare unaffected
Changed — <InboxRow state> completed → done (with back-compat alias)
<Checklist>'s ChecklistItemState has always used done for the "finished" state; <InboxRow state> used completed for the same concept. The two are now aligned on done — same word, same concept, same semantic.
- New canonical value:
state="done"(renders strikethrough + faded avatar, same as before) state="completed"is now a deprecated alias — still accepted at the prop boundary, normalised todonebefore render. The emitteddata-stateattribute is always"done". To be removed in v2.0.0.<Inbox />zero-prop demo set updated to usedone- Chapter §14.4 demo (data-mb-props + hand-rolled HTML + inline CSS + snippet tabs) migrated to
done components/_shared.cssselectors target[data-state="done"]as canonical; the[data-state="completed"]selector is kept transitionally so consumers writing raw HTML with the old value still get the right treatment until v2.0.0
Added
- Spelling convention note in
CLAUDE.md— documents that the kit deliberately uses UK English in prose (personalise,customise,recognise,optimise) while US-spelling code identifiers (color,behavior). Stops the next agent from "correcting" the UK forms. The v1.49.0 audit had flagged "Personalise" as drift; the audit was wrong —grepshows 14×personaliseand 0personalizeacross the kit. The convention is real, just unwritten until now. docs/_customer-success.mdupdated — the deferred-items section now reflects the resolved unifications; the "Same concept, different component" disambiguation tables show the aligned vocabularies.
Lockstep with @magicblocksai/[email protected] (only CSS change: the .inbox-row[data-state="done"] selectors added alongside the existing [data-state="completed"] ones for back-compat).
[1.49.0] — 2026-05-18
Minor — Round CS: customer-success taxonomy audit + fixes. A read-only audit of the kit's customer-success surfaces (LifecyclePill, DealStagePill, RiskPill, RiskBadge, TicketStatusPill, PipelineBar, Kanban, Inbox, Checklist, ScoreRing/Card, SLARing, ActivityTimeline, StageChat/HappaArc) surfaced a handful of P0 bugs, a tier-naming inconsistency in <LifecyclePill>, two parallel risk vocabularies without a "when to use which" doc, and a duplicated HAPPA stage type. This round fixes everything that can ship without breaking changes; the deferred items (stage-vocab unification, Checklist/Inbox state alignment, <LifecycleStrip> primitive) are tracked in the new IA doc.
Fixed
<InboxRow>chapter 14 demo: 3 of 6 demo rows incomponents/14-app-pipeline.html(and the kit-site mirror) serialised"priority":"med"— not a valid value forInboxRowPriority("low" | "medium" | "high"). When kit-islands hydrated the demo, the priority chip fell back to undefined. Fixed to"medium"everywhere.<TicketStatusPill>doc: was missing thesnoozedvalue documented in TSX since v1.9.2 (R8-2). Added the row to the tint table, expanded the type signature, added a usage note explaining thesnoozedsemantic vspending/closed.
Changed
<LifecyclePill>: relabelled the v1.13.3 second tier from "post-sale operational stages" to "operational sales-to-renewal stages" — the previous name was inaccurate because four of the nine values (qualified,discovery,proposal,contracted) are pre-contract sales stages. The TSX comment + the doc now spell out the duplicate-pair semantics (sqlvsqualified,customervscontracted,churnedvschurn).<LifecyclePill>: rendered spans now carry an additionaldata-lifecycle-tier="funnel" | "operational"attribute so consumers can disambiguate the duplicate-pair stages in CSS without writing their own lookup table. New exported helperlifecycleTierOf(value)does the same lookup in TS. NewLifecycleTiertype exported from the package barrel.<HappaArc>and<StageChat>: both now consume the new sharedHAPPA_STAGES/HAPPA_STAGE_LABELS/HAPPA_STAGE_GLYPHS/HappaStageIdprimitives fromsrc/lib/happa.ts. Previously each component duplicated its own version of the same five-stage list.StageChat's legacyStageChatStagetype is now an alias ofHappaStageId(back-compat preserved for existing consumers).<ScoreRing>: TSX comment now includes a "score-vs-risk colour direction" callout —<ScoreRing band="low">renders red while<RiskBadge level="low">renders green. Same word, opposite tint. Added the same callout to<ScoreCard>and both risk-component docs.
Added
docs/RiskBadge.md— was missing entirely. Documents the 5-level scale, the pairing with<Sparkline>, and explains when to reach for<RiskBadge>vs<RiskPill>.docs/RiskPill.md— added "when to useRiskPillvsRiskBadge" guidance plus the score-vs-risk colour callout.docs/_customer-success.md— new IA doc covering the four orthogonal axes (lifecycle stage / pipeline position / workflow status / quantitative health), per-surface recipes (record detail, kanban, inbox, health dashboard, ticket detail), and "same concept, different component" disambiguation tables. Also lists the known gaps the audit deferred (state-vocab unification, default pipeline alignment,<LifecycleStrip>).src/lib/happa.ts— new shared HAPPA-conversation primitives. Re-exported from the package barrel.SLA_THRESHOLDS+slaStateFromElapsed(f)exported from<SLARing>. Consumers can now either roll their own status pill with the same banding as the kit's<SLARing>, or overridestatebased on a custom rule while comparing against the kit's defaults.
Deferred (audit findings tracked for follow-up rounds)
- Unify the default stage vocabularies of
<PipelineBar>,<Kanban>, and<LifecyclePill>(currentlynegotiation/wonvsproposal/contractedfor the same concepts). - Normalise the
done(Checklist) vscompleted(Inbox) state vocab — same concept, two words. - Bulk-rename "Personalise" → "Personalize" across the 7+ chapter/component/doc files that ship it (>5-file rename, deferred per repo CLAUDE.md ask-first rule).
- Absorb a
<LifecycleStrip>primitive once a second consumer hits the wall described in<LifecyclePill>'s "When to outgrow this" doc section.
Lockstep with @magicblocksai/[email protected] (no CSS changes this round; bump matches the convention).
[1.48.0] — 2026-05-18
Minor — Round W: the killer chat widget. Single-shot delivery of the full end-user-facing chat widget system in one focused version. Fourteen components covering the visitor runtime (10), the operator-facing designer (2), and supporting kit primitives (2). Every visual lever from the platform's Chat Appearance editor is wired to a CSS custom property emitted by <WidgetThemeProvider>. Lockstep with @magicblocksai/[email protected].
Added — visitor runtime (what shows on acme.com)
<WidgetThemeProvider>+useWidgetTheme()+WIDGET_DEFAULTS— the token override layer. Accepts a deeply structuredWidgetThemeconfig covering every lever (launcher / shell / messages user-side & ai-side / send / buttons primary-secondary-suggestion / feedback / composer / proactive / welcome disclaimer / legal disclaimer / debug surfaces / labels / mobile overrides / customCss). Emits scoped CSS custom properties under.widget-theme-scope. Mobile-specific overrides re-emit inside a@media (max-width: 540px)scope.<WidgetLauncher>— the floating bubble. Theme-driven background colour / icon colour / icon glyph / image background / position (left/right) / size / offsets. Stateless. Unread badge slot.<WidgetProactiveMessage>— pre-engagement bubble that pops out beside the launcher. Click body opens chat; click × dismisses.<WidgetShell>— the expanded chat panel. Auto-built or override-able slots for header / welcome disclaimer / transcript / quick-replies row / composer / legal disclaimer / branding marker. Below 540px the floating panel becomes full-screen.<WidgetMessage>— visitor-facing bubble. Two sides (user/ai), themable colours per side, smart 12h / 24h timestamp formatting from theme, optional AI name, streaming + failed states.<WidgetComposer>— input + customizable Send button. Send label, icon, icon-position, colours theme-driven. Autogrowing textarea.<WidgetButton>— three variants (primary/secondary/suggestion), each fully themable.<WidgetFeedback>— thumbs up/down chip pair. Stateless.<WidgetDisclaimer>—welcomeandlegalvariants with separate token sets.<WidgetBrandingMarker>— "Powered by MagicBlocks" footer. Plan-gated by consumer.
Added — operator designer (Chat Appearance editor)
<WidgetStyleEditor>+<WidgetStyleSection>— split-pane shell with form pane + live preview pane + optional left sidebar. Below 960px collapses to single-column.<WidgetEmbedSnippet>— embed-code generator supporting five framework targets (HTML / React / Next.js App Router / Vue / WordPress). Tab switcher + Copy button.
Added — support primitives (kit-wide additions)
<ColorField>— labelled colour picker with hex input + native swatch + optional presets row.<ColorSwatchPicker>+WIDGET_SCHEMES— named-preset row of colour swatches. Default palette: Green / Yellow / Pink / Blue / Navy / Orange / Black / White. Supports two-tone swatches viaaccent.
What the theme covers
Every lever from the platform's Chat Appearance editor (see docs/Widget.md for the full map):
- Top-level — dir / fontFamily / fontWeight / fontSize / lineHeight
- Launcher — icon / background / iconColor / imageUrl / position / bottomOffset / sideOffset / size
- Shell — backgroundImage / headerBackground / headerText / chatBackground / width / height / showBranding
- Proactive — enabled / text / textColor / background / border / borderRadius
- Messages (user) — textColor / bubble / border / borderRadius / showTimestamp / timestampColor / timestampFormat
- Messages (AI) — textColor / bubble / border / borderRadius / showName / nameColor / timestamp
- Debug — debugTimestampColor / debugActionColor (for in-widget trace surfaces)
- Send — label / icon / iconPosition / textColor / buttonColor / border / borderRadius
- Buttons — primary / secondary / suggestion (each: textColor / buttonColor / border / borderRadius)
- Feedback — buttonColor / iconDefault / iconActive
- Composer — placeholderColor / textColor / background / border / fontSize
- Welcome disclaimer — enabled / message / textColor / background
- Legal disclaimer — enabled / message / textColor / alignment
- Notification sound, Labels (10 strings), Mobile overrides, Custom CSS escape hatch
Notes
- All fourteen ship in the kit-chrome carve-out (no per-chapter HTML demo, docs-only at
packages/ui/docs/). Overview atdocs/Widget.md; per-component docs alongside. - Distinct from the B1 operator-facing conversation primitives — different audience, different chrome.
- Embed-first. All styles namespaced under
.widget-*selectors and the.widget-theme-scopewrapper, so embedding into a customer's site doesn't leak kit tokens or affect their CSS. - No external dependencies. No chart libs, no DnD libs, pure SVG + CSS + standard React.
- Tokens, not CSS injection. Operators design via a form, not a code editor. The
customCssescape hatch exists for edge cases. - Token-driven, dark-mode safe (where the embedding site uses dark mode), honours
prefers-reduced-motion. - Pair with
@magicblocksai/[email protected].
Round retrospective
- Rounds A1–A3 (1.41.0–1.43.0): 25 form / list-chrome / settings primitives
- Round B1 (1.44.0): 5 conversation primitives (operator transcript)
- Round B2 + B2-refined (1.45.0–1.46.0): TraceTimeline (the robot head)
- Round B3 (1.47.0): TimeSeriesChart + StructuredDocEditor + LiveChatTester
- Round W (this version): the full chat widget system
Remaining: Round C (settings/billing/workspace chrome); Round D (future-state visualisations — JourneyGraph, Evaluations, ReviewerInbox).
[1.47.0] — 2026-05-18
Minor — Round B3 closes Round B. Three independent shapes that round out the conversation / analytics surface before Round W (the killer end-user-facing chat widget, queued next): a multi-series line chart for Dashboard analytics, a sectioned doc editor for Sales Playbooks, and a composed LiveChatTester shell for Try-my-Agent surfaces. Lockstep with @magicblocksai/[email protected].
Added
<TimeSeriesChart>— multi-series line chart for the Dashboard analytics surface and any "value over time" pane. Pure inline SVG with no external chart dependencies (norecharts, nochart.js, nod3). SSR-safe via viewBox-based rendering — no client-side container measurement. Features: auto Y-axis scaling with "nice" tick intervals (1 / 2 / 5 × 10ⁿ rounding), multi-series with toggleable legend chips, hover tooltip showing all visible series values at the snapped X, optional area fills, optional Catmull-Rom smooth curves, optional dashed lines. Default 8-colour palette mirrors<TraceTimeline>tone semantics for cross-component consistency. Documented atdocs/TimeSeriesChart.md.<StructuredDocEditor>— sectioned document editor for Sales Playbooks, structured Q&A docs, change logs, RFC drafts, and any "named sections with rich body per section" surface. Each section has a numbered heading, editable title (real<input>, not contentEditable), optional icon / kind chip / caption, and a body (default autogrowing textarea; passrenderBodyfor<RichTextEditor>/<SnippetTextarea>/ custom). Optional sticky table-of-contents rail (left or right, with anchor-scroll).lockedTitle/lockedBody/requiredflags for template mode. Compose with<SortableList>for drag-to-reorder. Documented atdocs/StructuredDocEditor.md.<LiveChatTester>— composed shell for Try-my-Agent surfaces, channel sandboxes, any interactive agent-preview pane. Wraps<ChatTranscript>+<ChatComposer>(B1) with a sticky agent-status header (avatar + name + version pill + status dot + caption) and an optional reset / restart affordance. Four status states (online/offline/thinking/error) with appropriate dot treatments —thinkingblinks amber, collapses to static underprefers-reduced-motion.header={null}opts out of the header entirely for embedded uses. Documented atdocs/LiveChatTester.md.
Notes
- All three ship in the kit-chrome carve-out (no per-chapter HTML demo, docs-only at
packages/ui/docs/). - Round B is complete. Conversation surface (B1) + TraceTimeline (B2 / B2-refined) + Round B3 = the full operator-facing conversation, explainability, and analytics surface.
- Round W (the end-user-facing embedded chat widget —
<WidgetShell>+<WidgetLauncher>+<WidgetThemeProvider>+<WidgetStyleEditor>+<WidgetEmbedSnippet>+<WidgetBrandingMarker>) is next. This is the killer-feature round for the Channels page and Go-Live flow; getting it focused and right is the next big push. - Token-driven, dark-mode safe, honours
prefers-reduced-motion. All animations (chart-line hover opacity, status-dot blink, transcript-scroll smooth, autogrow textarea) collapse to static states under the motion preference. - Pair with
@magicblocksai/[email protected].
Cumulative coverage
- Round A1 (1.41.0) + A2 (1.42.0) + A3 (1.43.0) → 25 form / list-chrome / settings primitives
- Round B1 (1.44.0) → 5 conversation primitives (operator-facing transcript)
- Round B2 (1.45.0) + B2-refined (1.46.0) →
<TraceTimeline>+<TraceEventCard>(the robot head) - Round B3 (this version) →
<TimeSeriesChart>+<StructuredDocEditor>+<LiveChatTester>. Round B complete.
Remaining: Round W (widget — next), Round C (settings/billing/workspace chrome), Round D (future-state: JourneyGraph, Evaluations, ReviewerInbox).
[1.46.0] — 2026-05-18
Minor — TraceTimeline refinement pass. The v1.45.0 first cut anchored too closely on the platform-tour mockup (tinted cards everywhere, numbered badges as the primary signal, repeating timestamps, faint hairline track). This pass pushes the design past the mockup toward a more premium, scannable, production-grade surface. Lockstep with @magicblocksai/[email protected]. Fully backward-compatible — all new props are additive; v1.45.0 consumers get the refined visuals automatically.
Refined
<TraceTimeline> redesigned around four principles:
1. Calm by default. Per-event card tinting dropped. Every card uses the neutral Card background. Tone colour now lives in: the leading icon ring, the eyebrow chip text, bullet dots, and (only for error + security) a 2px coloured left rail. Loud colours earn their loudness; the rest of the timeline stays calm. Across 12 events this is dramatically less noisy. 2. Icons over numbers. Number badges downgrade to a small step 1· eyebrow prefix. The prominent visual slot now accepts a consumer-provided leadingIcon — drop <BookIcon> for Knowledge, <TargetIcon> for Goal, <BoltIcon> for Tool, <ShieldIcon> for Security. Instantly scannable in a glance. 3. Time as progression, not noise. Smart timestamp dedup via the new timeMs event prop + timeStrategy timeline prop. First event renders an absolute time; subsequent events render deltas (+12ms, +1.2s, +2min). Eleven repeating timestamps become one anchor + eleven readable steps. 4. Section auto-grouping. Adjacent events with different tones automatically get extra spacing — the success cluster, the error cluster, the security cluster visually separate without needing section headings.
Added (additive API)
event.leadingIcon— optionalReactNodeshown in the gutter as the primary visual. Falls back to the auto-numbered step glyph when absent.event.eyebrow— small uppercase mono category chip above the title ("KNOWLEDGE","TOOL","SECURITY"). Recommended for scannability.event.timeMs— epoch / duration ms. When set, the timeline applies itstimeStrategyto format timestamps. Backward-compatible with the existingevent.timestampstring prop (which wins when both are set).event.streaming— whentrue, the leading icon pulses softly with a tone-coloured ring, a smallLIVEchip renders next to the timestamp, and the card gets a faint tonal halo. Future-proofs the component for live runtime traces.timeStrategyon<TraceTimeline>:"absolute"/"deltas"/"first-then-deltas"(default).hideStepNumberson<TraceTimeline>— hides thestep neyebrow prefix while keeping leading icons.hideLeading— renamed fromhideNumbers(still backward-compatible-safe; both render the leading slot identically).
Refined CSS
- Vertical track upgraded from a 1.5px hair-grey hairline to a confident 2px line in
color-mix(in oklab, var(--ink) 10%, transparent). Brightens to 18% ink on row hover. - Leading slot grows from 22px to 28px and gets a
box-shadow: 0 0 0 4px var(--bg-paper)"cutout" so the track terminates cleanly under it. - Card hover state: subtle bg shift to a warm off-paper, border accepts an 18% tonal tint. Real product polish.
- Bullet items use a
label:colon-after-pseudo and a flex-wrap body so long values flow gracefully. - Code items get a
</>leading glyph in a tone-tinted pill and a ghost Copy button (no border, transparent until hover). - Streaming pulse is a CSS-only scaling ring animation; collapses to a static
scale(1.1)frame underprefers-reduced-motion. - Section-break spacing is a single CSS rule (
.trace-timeline-item.is-section-break { padding-top: var(--s-3); }).
Notes
- Backward-compatible: every v1.45.0 consumer gets the refined visuals with no code changes. Adopt
leadingIcon/eyebrow/timeMs/streamingopt-in to unlock the full polish. - Token-driven, dark-mode safe, honours
prefers-reduced-motion. All animations (track-brighten, hover bg, chevron rotate, streaming pulse, live-dot blink) degrade gracefully. - Pair with
@magicblocksai/[email protected].
Round W flagged
The end-user-facing chat widget (Round W — <WidgetShell> + <WidgetLauncher> + <WidgetThemeProvider> + <WidgetStyleEditor> + <WidgetEmbedSnippet> + <WidgetBrandingMarker>) is queued as a dedicated future round, distinct from the operator-facing transcript surfaces shipped in v1.44.0.
[1.45.0] — 2026-05-18
Minor — Round B2 of the MagicBlocks Next Gen platform-tour gap analysis: the AI Reasoning Flow timeline (the "robot head"). Single-focus version. <TraceTimeline> is the single most powerful explainability surface in the entire product — operators use it to answer "why did the agent do that?" without reading JSON logs. Designed carefully against the platform-tour mockup; the docs include a full 12-event example reproducing it exactly. Lockstep with @magicblocksai/[email protected].
Added
<TraceTimeline>— vertical, numbered list of agent-runtime events for a single message turn. Six tones group events by category (success / warn / error / security / info / neutral) drawn directly from the platform's observed event taxonomy; three item kinds (bullet / card / code) cover every body shape in the mockup. The faint vertical track connecting number badges auto-suppresses on the last item. Tone palette cascades via CSS custom properties on.trace-event, so a custom category requires one.trace-event.is-tone-<name>rule. Dark-mode adjustments lift saturation per tone. Documented atdocs/TraceTimeline.mdwith a full 12-event example reproducing the platform-tour mockup and recipes for inline-expansion-beneath-message vs modal-deep-dive surfaces.<TraceEventCard>— same shape as a single timeline row, exported for rendering one-off events outside a timeline (toast previews, single-event drawers, debug surfaces).
Tone vocabulary
| tone | Use for | |---|---| | "success" | Knowledge Used, Facts Captured, Goal Triggered, Director Match, Block transition success | | "warn" | Tool calls, Form completed, Form submitted, operational steps | | "error" | Model failed, Form post error, Tool timeout, Guardrail rejection | | "security" | Jailbreak detected, Moderation hit, Redaction applied, PII intercept | | "info" | Message ID, Session ID, telemetry, raw metadata | | "neutral" | Default — events without an established category |
Item kinds
| kind | Shape | |---|---| | "bullet" (default) | • {label}: {value} line with a small dot in the tone colour | | "card" | Boxed row, optional chevron + expand-to-reveal details body | | "code" | Mono code block with a Copy-to-clipboard button (Message ID shape) |
Notes
- Both ship in the kit-chrome carve-out (no per-chapter HTML demo, docs-only at
packages/ui/docs/). - Pure presentation — the kit doesn't enforce a runtime event schema. Consumers map their backend events into the
TraceEventshape. - Pairs with
<MessageTraceButton>(Round B1, the per-message trigger) and<SummaryBanner>(Round B1, the TL;DR card typically rendered above the timeline in modal mode). - Token-driven, dark-mode safe, honours
prefers-reduced-motion. Expand-chevron rotation and card-trigger hover are the only animations; both collapse to static states underprefers-reduced-motion. - Pair with
@magicblocksai/[email protected].
Cumulative coverage so far
- Round A1 (1.41.0) + A2 (1.42.0) + A3 (1.43.0) — 25 form / list-chrome / settings primitives → "every operator form is skinnable from the kit"
- Round B1 (1.44.0) — 5 conversation-surface primitives (the operator-facing transcript)
- Round B2 (this version) —
<TraceTimeline>+<TraceEventCard>→ the trace surface is complete
Remaining in Round B: B3 — <TimeSeriesChart>, <StructuredDocEditor>, <LiveChatTester> (composed shell). Then Round C (chrome/settings/billing), Round D (future-state visualisations: JourneyGraph, Evaluations), and Round W (the dedicated end-user-facing chat widget — separate from the B1 operator-facing transcript).
[1.44.0] — 2026-05-18
Minor — Round B1 of the MagicBlocks Next Gen platform-tour gap analysis: the conversation surface. Five tightly-coupled primitives that compose into the Sessions viewer, Try-my-Agent surface, Contact Sessions tab, and any inline chat preview. Designed in a single API pass since the parts wire into each other (<ChatTranscript> wraps <ChatMessage> nodes, <ChatMessage actions={...}> typically holds a <MessageTraceButton>, <ChatTranscript footer={...}> typically holds a <ChatComposer>, and <SummaryBanner> sits above the whole stack). Lockstep with @magicblocksai/[email protected].
Added
<ChatMessage>— one conversation bubble (agent / user / system). The atom. Three sides drive alignment + bubble shape + avatar treatment. Optionalactionsslot (hover-revealed on fine pointers, always-visible on coarse),reactionsrow (thumbs / emoji chips),status(streamingadds a blinking cursor;failedpaints a red rail + retry hint),confidence(coloured dot beside the timestamp),density(comfortable/compact). System messages render as a centred pill ("Block transition: Hook → Align"). Pre-wrap whitespace preserved in the body. Documented atdocs/ChatMessage.md.<ChatTranscript>— scrollable conversation viewer shell with stickyheader+ pinnedfooterslots. Sticky auto-scroll behaviour: when new children arrive, scrolls to bottom *only* if the user is already near the bottom (~120px, configurable). If they've scrolled up to read older messages, new arrivals don't yank the view. OptionalagentTypingindicator (three pulsing dots in an agent bubble),loadingskeleton, andemptyStateslot. Wraps content inrole="log" aria-live="polite". Documented atdocs/ChatTranscript.md.<ChatComposer>— bottom-of-transcript composer. Autogrowing textarea (capped atmaxRows, default 6) + Send button. Submit on Enter (Shift+Enter inserts a newline; toggle viasubmitOnEnter). Send is disabled when the value is empty (after trim); passingsendingswaps the label for a spinner. Optional left-sideattachmentsslot + right-sideactionsslot. The composer does NOT clear itself — the consumer owns the value and decides when to clear. Documented atdocs/ChatComposer.md.<MessageTraceButton>— per-message "robot head" trace icon. Pairs with<TraceTimeline>(shipping next in v1.45.0 / Round B2). Thenoteworthydot-badge is the discoverability lever: most messages are boring, but the ones that triggered a block transition / action firing / guardrail / RAG miss announce themselves visually so operators learn to click them. Three sizes (xs/sm/md), optional event-count badge. Documented atdocs/MessageTraceButton.md.<SummaryBanner>— collapsible AI-generated TL;DR card. The kit's standard "here's a summary of this conversation / session / agent diff / change log" banner. Defaults to expanded;accentfor the pink left rail used on AI-content surfaces. Optionalicon(defaults to a sparkle glyph),caption,meta(typically a "Regenerate" button).hideTogglefor static (always-visible) mode. Documented atdocs/SummaryBanner.md.
Notes
- All five ship in the kit-chrome carve-out (no per-chapter HTML demo, docs-only at
packages/ui/docs/). <MessageTraceButton>ships now (Round B1) but its companion<TraceTimeline>follows in v1.45.0 / Round B2. Consumers wiring up the trace surface can stub the modal with aconsole.loginonClickuntil then.- Token-driven, dark-mode safe, honours
prefers-reduced-motion. The chat-bubble streaming caret, typing-dots animation, skeleton pulse, send-spinner, and summary-banner chevron all collapse to static / non-animated states underprefers-reduced-motion. - Pair with
@magicblocksai/[email protected].
[1.43.0] — 2026-05-18
Minor — Round A3 of the MagicBlocks Next Gen platform-tour gap analysis. Cleanup batch — ten remaining Round A primitives that close the "every agent-builder form, every list-page chrome, every settings surface is skinnable from the kit" claim. With v1.41.0 (Round A1) + v1.42.0 (Round A2) + this version, every Next Gen product-app surface identified in the gap analysis now has a kit primitive (Rounds B/C/D, the conversation surfaces and future-state visualisations, follow). Lockstep with @magicblocksai/[email protected].
Added
<MultiBindingList>— repeater ofselect + removerows, every row binds a single value from a shared options pool. Sibling to<KeyValueRepeater>. Backs the Brain → MCP picker (+ Add MCP), Knowledge Base Collections multi-select (≤3), multi-recipient email destinations. Documented atdocs/MultiBindingList.md.<FilterPopover>— generic filter trigger + popover with title / body / footer slots. Body is consumer-owned (typically<QueryBuilder>); the popover handles trigger / outside-click / Escape. Active-count pill on the trigger when filters are set. Documented atdocs/FilterPopover.md.<TimePicker>— HH:MM time input wrapping the native<input type="time">for keyboard / a11y parity, with optional 12-hour caption. Used in Availability work-days, future Schedule-send, "from/to" surfaces. Documented atdocs/TimePicker.md.<SavedViewsRail>— named query-views rail with labels + counts + groups + optional star-toggling. Backs the Sessions left-rail saved views (All / Favorites / Goal Conversions / Principles Fixed / Missing Knowledge / Negative Sentiment) and Dashboard Latest-Sessions tabs. Documented atdocs/SavedViewsRail.md.<VersionSwitcher>— versioned-entity dropdown with timestamps + status pills + optional+ Create new version/Compare versionsfooter actions. Used for Agents, Personas, Tasks (the platform's three versioned entities). Documented atdocs/VersionSwitcher.md.<HelpBubble>— floating help / chat launcher (the global bottom-right "Magic on Magic" bubble). 4 anchor presets, optional badge count, dismisses on Escape + outside-click. Documented atdocs/HelpBubble.md.<DomainList>— chip-input for hostnames. Enter / comma / space commits, backspace removes, custom validator hook. Optional "Allow all domains" toggle. Backs Design & Go Live whitelist and CORS-origin lists. Documented atdocs/DomainList.md.<CategoryGroupList>— grouped expandable list (label + count + meta header, items revealed on expand). Distinct from<AccordionGroup>— this one is item-focused, multi-open by default. Backs Contact Memories (5 categories), Knowledge categories rail. Documented atdocs/CategoryGroupList.md.<ExpandableEditRow>— collapsed row → expanded inline editor reveal pattern. Row-shaped (icon + title + meta in a line, fits inside a list), distinct from the card-shaped<Accordion>. Backs Knowledge Q&A items, Missing Knowledge convert flow, Key Fact ledger. Documented atdocs/ExpandableEditRow.md.<SourceFreshnessBanner>— status banner for a synced source (fresh/stale/changed/errortones). Backs Knowledge collection "Sitemap Changes Found · Setup Detector", Webhook last-ping status, Connection freshness. Documented atdocs/SourceFreshnessBanner.md.
Notes
- All ten ship in the kit-chrome carve-out (no per-chapter HTML demo, docs-only at
packages/ui/docs/). - Cumulative coverage across Round A: 25 primitives, ~65% of the platform-tour gap analysis by surface count. Every form, list chrome, and settings surface identified in the analysis now has a kit primitive.
- Token-driven, dark-mode safe, honours
prefers-reduced-motion.<HelpBubble>usesposition: fixedand is full-page chrome (sits above all other content via z-index60). - Pair with
@magicblocksai/[email protected].
[1.42.0] — 2026-05-18
Minor — Round A2 of the MagicBlocks Next Gen platform-tour gap analysis. Nine product-app primitives — the high-leverage molecules behind the agent-builder condition surfaces, snippet-token text fields, mapping repeaters, and async probe buttons. <QueryBuilder> is the centrepiece: a single primitive that unifies four near-identical condition surfaces in the platform (Contacts filter, Action Conditions, Key Fact Ask-when, Webhook event filters). Lockstep with @magicblocksai/[email protected].
Added
<QueryBuilder>+<ConditionRow>— single-level AND/OR condition builder. Each row composes a field picker → operator picker → type-driven value editor (text / number / select / multiselect / boolean / duration / known). Seven field types, fifteen operator labels, optional AND/OR conjunction toggle. Nested groups deliberately left out of v1; the row data model is stable enough to extend withgroups?later without breaking consumers. Documented atdocs/QueryBuilder.md.<SnippetTextarea>— textarea +Insert snippetbutton + grouped picker menu. Inserts tokens at the cursor position, restores focus after insertion, dismisses on Escape + outside-click. Distinct from<MergeTagInput>(which is keyboard-driven via{{) — this one matches the platform's always-visible-toolbar chrome. Documented atdocs/SnippetTextarea.md.<FieldMapper>— two-column source → target field mapper with+ Add field. Used for HubSpot deal mapping, Calendar field auto-population, CSV column → CRM field, and anysource → targetrepeater shape. Documented atdocs/FieldMapper.md.<VariantsRepeater>— list of textarea variants with add + remove. AI Message Versions, Snippet Variants, "give me three ways to say this" surfaces.maxcap, numbered rows. Documented atdocs/VariantsRepeater.md.<RadioCardList>— vertical radio cards w/ title, description, optional icon / meta / reveal sub-form. The kit's standard "pick one option with explanation" shape — First Action (8 types), Form Interaction, Region select, Off/On config panels, Credits top-up packages. Documented atdocs/RadioCardList.md.<TwoPathChooserDialog>— two-card chooser body (drop inside<Modal>). Recurring "pick one of two creation paths" shape: Key Fact Template-vs-Custom, Tools & MCP HTTP-vs-MCP-Server, Forms Custom-vs-Snippet, future block-template chooser. Documented atdocs/TwoPathChooserDialog.md.<PayloadPreview>— formatted JSON preview card w/ copy button + optional top-level-key highlight pills. HubSpot deal payload, Webhook destination, MCP argument shape, future event-stream samples. Display-only. Documented atdocs/PayloadPreview.md.<TestConnectionButton>— async probe button w/ built-in pending / ok / error chip. Auto-disables during the request, auto-clears the chip after a configurable timeout. PassonTest: () => Promise<…>for the simple case; passstatefor fully-controlled use. MCP Discover Tools, Webhook Test, HubSpot Test, Form Test. Documented atdocs/TestConnectionButton.md.<KpiDeltaTile>— KPI tile with explicit period-comparison delta ("+12% vs last 7 days"). Sibling to the existing<KpiTile>.invertDirectionfor metrics where up is bad. Documented atdocs/KpiDeltaTile.md.
Notes
- All nine ship in the kit-chrome carve-out (no per-chapter HTML demo, docs-only at
packages/ui/docs/). - Round A2 covers another ~25% of the new surface area identified in the platform-tour gap analysis (cumulative with A1: ~40%).
- Token-driven, dark-mode safe, honours
prefers-reduced-motion. AND/OR conjunction toggle is responsive (collapses gracefully below 720px). - Pair with
@magicblocksai/[email protected].
[1.41.0] — 2026-05-18
Minor — Round A1 of the MagicBlocks Next Gen platform-tour gap analysis. First batch of product-app primitives surfaced by mapping every section of the AI sales-agent platform against the kit's existing component inventory. Six small kit-chrome carve-outs that unblock a long tail of agent-builder, settings, and list-page surfaces. Lockstep with @magicblocksai/[email protected].
Added
<Slider>— numeric slider with paired value readout. The kit's standard "creativity", "temperature", "weight" control. Drop-in viadefaultValue+onValueChange(controlled withvalue). CustomformatValuefor non-default readouts. Honoursprefers-reduced-motion. Documented atdocs/Slider.md.<Accordion>+<AccordionGroup>— single-section disclosure with optional grouping. Backs Guardrails Off/On panels, per-block Advanced overrides (First action / Persona / Brain / Guardrails / Settings), and Chat-appearance style-category panels.AccordionGroupdefaults to single-open;allowMultipleopens any subset. Stable item ids preserved across edits. Documented atdocs/Accordion.md.<SyncStatus>— "Synced 3 minutes ago · Refresh" status line. Auto-ticks the relative time every minute (tickMsoverride). Spinner state viabusy. Recurs on every list page in the Next Gen app. Documented atdocs/SyncStatus.md.<SplitButton>— primary action + caret-menu of alternate modes ("Send now" + "Schedule send", "Save" + "Save and publish"). Four tones (primary / neutral / danger / ghost), three sizes. Caret-menu dismisses on Escape + outside-click. Documented atdocs/SplitButton.md.<DurationField>— composite count + unit input. Backs theNo message received from user for…agent condition and future delay surfaces. Default units: minutes / hours / days / months; override viaunits. Documented atdocs/DurationField.md.<KeyValueRepeater>— list of key/value pairs with add + remove affordances. Canonical shape for MCP auth headers, Webhook headers, HTTP function parameters.readOnlymode for discovered-state displays. Documented atdocs/KeyValueRepeater.md.
Notes
- All six ship in the kit-chrome carve-out (no per-chapter HTML demo, docs-only at
packages/ui/docs/). Round A2/A3 + Rounds B/C/D from the platform-tour gap analysis (QueryBuilder,SnippetTextarea,FieldMapper,ChatTranscript/TraceTimeline,WidgetStyleEditor,JourneyGraph, …) follow in subsequent versions. - Token-driven, dark-mode safe, honours
prefers-reduced-motion. - Pair with
@magicblocksai/[email protected].
[1.40.0] — 2026-05-16
Minor — narrative / long-form utilities harvested from "The Reliability Gap" research-report landing page. Four reusable patterns from a long-form MagicBlocks-vs-OnePrompt comparison piece lifted into the canonical kit. Lockstep with @magicblocksai/[email protected]. Not consumer-driven (no Spark / Website / Wingman round) — designer ship, no fixes-log entry.
Added
useReveal()hook (in thelib/primitives barrel) — attach to an element withclassName="reveal"(and optional.d1–.d5stagger classes); the hook flips.inwhen the element enters the viewport. Synchronously applies.inif the element is already onscreen at mount (handles back-button navigation landing mid-page). Honoursprefers-reduced-motion. Options:rootMargin/threshold/once. Documented atdocs/ScrollCue.md.<ScrollCue>— animated "scroll for more" indicator. Mono uppercase label above a 38px vertical hairline that pulses on a 2.4s loop. Honoursprefers-reduced-motion. Position via the consumer's wrapper (typically absolute-positioned inside aposition: relativehero). Documented atdocs/ScrollCue.md.<DialogueContrast>family (DialogueContrast+DialogueColumn+DialogueMessage+DialogueEllip) — two-column conversation comparison container. Each column has a header (who · tag · status), a stack offrom="us"/from="them"messages, and an optional outcome banner.tone="win" | "loss" | "neutral"colours the top accent stripe and theis-usbubble fill. Collapses to a single column below 720px.<DialogueEllip>renders soft italic stage-direction text ([explains privacy policy at length]) inside a message. Documented atdocs/DialogueContrast.md.<DotMatrix>— proportion visualisation. Renders a grid of dots where a subset is highlighted to communicate "out of N, M did X." Default 1000-dot 40×25 grid (one dot per lead in a 1k-lead sample).distribution="scatter" | "head" | "tail" | "random"controls placement;scatter(default) spaces highlighted dots evenly so the proportion reads visually. Pure markup (no canvas, no JS), scales fluidly, prints cleanly. CSS custom properties (--dm-cols,--dm-gap,--dm-base,--dm-highlight) for per-instance tuning. Documented atdocs/DotMatrix.md.
Notes
- Four new primitives ship in the kit-chrome carve-out (no per-chapter HTML demo, docs-only at
packages/ui/docs/, same pattern as<EmailThreadRow>/<ScoreCard>/<NarrationLog>etc.). - All four honour
prefers-reduced-motion. Token-driven, dark-mode safe. - Pair with
@magicblocksai/[email protected].
[1.39.0] — 2026-05-15
Minor — Wingman Round 1 (the first round of asks from the 10DLC Wingman team — a third named consumer alongside Spark CRM and the MagicBlocks Sales Website) plus a small Spark Round X+1 ask rolled in lockstep. Lockstep with @magicblocksai/[email protected] and @magicblocksai/[email protected]. Full close-out at WINGMAN-FIXES-LOG.md Round 1 + SPARK-FIXES-LOG.md Round 38.
Added
<ScoreCard>(W-001) — hero "score + interpretation + breakdown" card. Composes<ScoreRing size="xl">+ a Card-style surface + label / interpretation / meta slots.fill="paper" | "warm" | "ink". Consumer-owned interpretation copy (kit only knows the score, not what it means in your domain). Kit-chrome carve-out; documented atdocs/ScoreCard.md.<NarrationLog>(W-002) — event-log / scan-narration list for long-running async processes (scan / build / deploy / sync / audit). Status-coloured rows (info/pass/warn/fail), optionalmaxVisiblecollapsing of older entries behind a "show N earlier" toggle, optionalenterAnimation="slide-fade". Lighter than<ActivityTimeline>— no filters, no per-day grouping, no rich-row layout. Documented atdocs/NarrationLog.md.<EvidenceQuote>(W-003) — verbatim quote + source caption for audit / review / compliance surfaces. 3px accent left rail, italic body, mono uppercase caption with optionalurl→ "Open ↗" link (opens in a new tab withnoopener noreferrer). Documented atdocs/EvidenceQuote.md.<PasteAndRecheckArea>(W-004) — paste-revised-text + re-run-audit composition. Composes<Textarea>+<Button>+ a status row. Tracks textarea state internally; consumer owns the async work andresultreporting.result.deltaanimates on improvement. Documented atdocs/PasteAndRecheckArea.md.<ScoreRing size="xl">(W-008) — 140px hero ring with a 38px value font. Used internally by<ScoreCard>and exposed for direct use too.<Drawer placement="bottom">(W-007) — bottom-anchored sheet (slides up from the viewport bottom). Mobile-first capture flow shape — email gate, cookie consent, post-action confirmation. Same focus-trap / scroll-lock / Esc primitives. Newheight?: number | stringprop sets the sheet height for the bottom placement (defaults toautoup to90dvh);widthbecomes a no-op whenplacement="bottom". The pre-1.39.0sideprop is deprecated as an alias forplacement(kept until v2.0.0); when both are passed,placementwins.<MenuItem keepOpen>/<DropdownMenu.Item keepOpen>(Spark Round X+1) — opt-in boolean, defaultfalse. Whentrue, selecting the item runsonSelectbut suppresses the auto-close. Multi-toggle flows (workspace switcher with checkboxes, column-visibility chooser, tag picker) no longer have to re-open the menu between each click. Applies to both the compositional API (<DropdownMenu.Item keepOpen>) and the data-driven API ({ label, onSelect, keepOpen: true }insidegroups).
Notes
- Four new primitives (
ScoreCard/NarrationLog/EvidenceQuote/PasteAndRecheckArea) ship in the kit-chrome carve-out (no per-chapter HTML demo, same pattern as<EmailThreadRow>/<EmailThread>/<MentionInput>/<SectionCard>). - W-005 (
validateAgainstVoice()) shipped in@magicblocksai/[email protected]— see that package's CHANGELOG. - W-009 (Tailwind v4
@importordering note) shipped inINSTALL-FROM-NPM.md. - W-006 (sub-path exports for tree-shaking) deferred to a follow-up round — needs a tsup multi-entry refactor + per-component
exportsentries generated from the catalogue. Tracked in the Wingman log. - Pair with
@magicblocksai/[email protected]+@magicblocksai/[email protected].
[1.38.0] — 2026-05-14
Minor — Spark Round 39. New hideFilters prop on <ActivityTimeline> so consumer surfaces that already render their own filter chip row can opt out of the kit's auto-derived row. Lockstep with @magicblocksai/[email protected] (no CSS changes; lockstep only). Full close-out at SPARK-FIXES-LOG.md Round 38.
Added
<ActivityTimeline hideFilters>(R39) — boolean prop, defaultfalse. Whentruethe auto-rendered.act-filterschip row is omitted from the markup.defaultFilterandonFilterChangekeep working — they're just decoupled from the chip UI when this is set. Lets consumers like Spark'sEntityActivity(which renders a richer filter row with per-category counts from/api/v1/activities/countsabove the composer) drop the kit chips without resorting to a.act-filters { display: none }consumer CSS override.
Notes
- No breaking changes. Existing call sites continue to get the chips by default.
- Pair with
@magicblocksai/[email protected].
[1.37.0] — 2026-05-14
Minor — Spark Round 39 (Spark IDs R37 + R-sortable-space). Two fixes: 1. <DataTable> non-sortable header cells now render in the same <button> structural shell as sortable headers — uniform resize-grip hit target and uniform trailing caret slot for right-aligned label alignment. 2. useSortable (and therefore every consumer of <SortableList>) no longer hijacks Space / ArrowUp / ArrowDown when the event originates inside an <input> / <textarea> / <select> / contenteditable child of a sortable item.
Lockstep with @magicblocksai/[email protected]. Full close-out at SPARK-FIXES-LOG.md Round 37.
Changed
<DataTable>header shell uniformity (R37) — every header now renders inside<button class="data-table-sort">, regardless ofsortable. Whensortable: falsethe button hastype="button"with noonClick,tabIndex={-1},aria-disabled={true}, and the modifier class.is-static(which neutralises the hover recolour and pointer cursor). The caret-slot<span>is still emitted but with empty content for non-sortable headers — amin-width: 11pxrule reserves the same trailing space so right-aligned labels line up across the row even when some columns are sortable and some aren't. Resize-grip hit target is consistent across all headers. No prop changes; this is a markup change inside the component that consumers won't see except in their browser inspector.
Fixed
useSortablespace-key hijack (R-sortable-space, P1) —itemKeyDownnow bails out early when the event target is an<input>,<textarea>,<select>, or[contenteditable]element. Pre-1.37.0 the kit unconditionallye.preventDefault()'d Space on the sortable wrapper, swallowing the keystroke before the browser could insert it. Fixes typed-into-input handling on every page that mounts a sortable card stack (per Spark: IssueDetailPage / ContactDetailPage / CompanyDetailPage / DealDetailPage). The same guard coversArrowUp/ArrowDownso caret nav inside grabbed-card inputs keeps working.
Notes
- No new exports, no new props.
- Pair with
@magicblocksai/[email protected].
[1.36.0] — 2026-05-14
Minor — Spark Round 38. New <EmailThreadRow> primitive: a Gmail-shaped inbox row sibling to <InboxRow>. Both compose into <Inbox>. Lockstep with @magicblocksai/[email protected]. Full close-out at SPARK-FIXES-LOG.md Round 36.
Added
<EmailThreadRow>(R38) — 5-column row layout (star · avatar · sender+subject+preview · time · archive) for mailbox-shaped surfaces. Star is always-visible and pressable; archive is hover-discoverable on pointer-fine devices and always-visible on(pointer: coarse).unreadpaints a 3px accent left-rail and bolds the sender + subject text. Keyboard contract:Enter/Space→onOpen(id). Typed-into-input bubbling is honoured. The slot model is opt-in — omitonStarto hide the star column, omitonArchiveto hide the archive column. Sender / subject / preview / time acceptReactNodeso consumers can slot in chips, icons, or formatted timestamps. Kit-chrome carve-out (no per-chapter HTML demo); documented atpackages/ui/docs/EmailThreadRow.md.
Notes
- Lives alongside
<InboxRow>, not in place of it. Reach for<EmailThreadRow>when the row represents an email / message thread; reach for<InboxRow>when the row represents a task / ticket to dispatch. - Pair with
@magicblocksai/[email protected].
[1.35.0] — 2026-05-13
Patch — Spark Round R-cmdk. CSS-only scoping fix to the kit's keyboard focus ring; no TSX surface change. Lockstep with @magicblocksai/[email protected]. Full close-out at SPARK-FIXES-LOG.md Round 35.
Changed
- Focus ring no longer paints on bare
<input>/<button>/<textarea>/<select>. Previously_shared.csscarried a:focus-visible { box-shadow: var(--sh-focus) }rule plus a bare-element union that caught every focusable form control — meaning a chromeless input (e.g. Spark'sCmd+Kcommand-palette search field, which intentionally omits the.inputclass) showed a 3px pink halo on focus with no way to opt out without a local override. v1.35.0 replaces those with a single scoped rule covering only semantic-default focusables (a,[role="button"],[tabindex]). Consumers using the kit's classes (.input,.btn,.icon-btn, etc.) see no change — each class still declares its own focus styling further down in_shared.css. Consumers who relied on auto-styled bare elements need to add the class or supply their own focus ring.
Notes
- All TSX components in
@magicblocksai/uialready use the opt-in classes — no fixture diffs. - The
useControllableState/ focus-trap / etc. primitives are untouched. - Pair with
@magicblocksai/[email protected].
[1.34.0] — 2026-05-12
Patch — Spark Round R36. Single CSS-only fix to the <DataTable tableKey> chrome layout. No TSX surface change; no new prop. Full close-out at SPARK-FIXES-LOG.md Round 34.
Changed
<DataTable tableKey>columns kebab now overlays the table's top-right corner (R36 — option 3, auto-collapse). Pre-1.34.0 the.data-table-chrometoolbar took ~32px of vertical space above the header row to host the columns kebab; with no other chrome children populating the band it read as a layout bug — the gap between the SectionCard title bar and the column headers was visible dead space. Fix: chrome nowposition: absolutes onto the table's top-right corner (top: var(--s-1); right: var(--s-1)), withpointer-events: noneon the chrome wrapper andpointer-events: autoon its children so the underlying header row stays click-through for sort. The kebab functions identically and the popover anchors against the chrome's positioning context (preserved). Bumped chromez-indexto3so the overlay sits above the sticky header (which isz-index: 2). Spark filed three options (chromeInlineprop,renderChromeslot, auto-collapse) — option 3 has the best zero-config story and matches their workaround inapps/web/src/index.css. Spark deletes that override after upgrading.
Notes
- No prop surface change. Existing
<DataTable tableKey="...">calls render the kebab in the new corner-overlay position automatically. Tables that don't passtableKey(no chrome) are completely unaffected. - Markup-equivalence unaffected — the chrome only renders when
tableKeyis set, and no chapter HTML demostableKey. Existing fixtures pass unchanged. - All gates green: typecheck ✓, markup-equivalence ✓,
check:docs✓, CSS / UI / kit-site builds. - Pair with
@magicblocksai/[email protected].
Round R36 mapping
| spark-id | kit-id | what shipped | |---|---|---| | R36 (open) | R34-1 | .data-table corner-overlay chrome — option 3 (auto-collapse). CSS-only; no TSX change. |
[1.33.0] — 2026-05-12
Minor — Spark Rounds R33 + R34 + R35-B. Six small additive props / glyphs from the Spark /admin/docs/* build-out, all opt-in and zero-breaking. Three docs-page primitives (R33-A · R33-B · R33-C), two source-control icons (R34), and the per-table compact density (R35-B) Spark filed alongside the resize round (R35-A is already shipped — see "Notes" below). Full close-out at SPARK-FIXES-LOG.md Round 33.
Added
<SectionCard subtitle>(R33-A). Newsubtitle?: ReactNodeprop renders a one-line scoping note inside the header band, on a second line under the title. Smaller (12.5px) and softer (var(--fg-soft)) so the title stays the primary anchor — mirrors<PageHeader summary>. When set, the kit wraps title + subtitle in a column-flex container (.section-card-head-text) so the icon / count / action slots stay vertically centred against the text stack via the band'salign-items: center. When unset, the head markup is identical to pre-1.33.0 (no extra wrapper) — adopt subtitle on the cards that need it without ripple changes elsewhere. Subtitle is capped at 60ch so long copy wraps cleanly inside narrow cards. Default-variant only.
<Switch onCheckedChange>(R33-B). NewonCheckedChange?: (checked: boolean) => voidhigher-level handler — receives the boolean directly, mirroring the Radix / shadcn convention used elsewhere in the kit (<Checkbox onCheckedChange>,<Combobox onValueChange>,<MultiSelect onValueChange>). Sibling to — not a replacement for — the nativeonChange; both fire when set, withonChangefirst (consumers readinge.target.checkedsee the same valueonCheckedChangeis about to be called with). Useful when you only care about the boolean and don't want to plumb(e) => onChange?.(e.target.checked)everywhere.
<CodeBlock copyable>(R33-C). Newcopyable?: booleanprop renders the copy button as a corner-positioned overlay in the top-right of the block, instead of (or in addition to) the figcaption header strip. When set, the figcaption-strip copy button is suppressed so the block only ever shows one copy affordance — when neitherlanguagenorfilenameis set, the figcaption collapses entirely and the corner button is the only chrome. Use for chrome-light docs pages where every snippet has a copy affordance but the figcaption header strip would feel like banner bloat. Pre-1.33.0 the only way to land this composition was to wrap<CodeBlock>in aposition: relativecontainer and absolute-position a separate<CopyButton>over it — Spark was repeating that wrapper a dozen times across/admin/docs/*. The new prop bakes the composition into the kit so consumers stop reinventing it. Pairs with new.codeblk-copy-cornerCSS rule that anchors the overlay top-right with a small ink-tint + blur backdrop so the button reads cleanly against any code colour underneath.
<GithubIcon>+<BranchIcon>(R34). Two source-control glyphs for the Spark/admin/docs/githubdocs surface (and any other consumer wanting canonical GitHub mark / git-branch iconography).<GithubIcon>is a single-stroke Octocat outline;<BranchIcon>is the canonical two-rail tri-node git-branch mark (top-left + bottom-left + top-right nodes joined by a curve from the right rail back to the left). Both follow the kit's monoline 18×18 / 1.5px /currentColorstyle; both inherit the existingIconPropssurface (size, forwardedclassName,aria-label, etc.). Spark replaces a hand-rolled inline Octocat SVG (~200 bytes) inapps/web/src/components/GithubUnfurl.tsxand switchesDocsHomePage.tsxfrom<LinkIcon>to<GithubIcon>for the GitHub docs card.
<DataTable density>(R35-B). Newdensity?: "comfortable" | "compact"prop."comfortable"is the historic default (var(--s-3) var(--s-4)= 12px vertical, 16px horizontal cell padding)."compact"halves the horizontal padding tovar(--s-2)(8px) on every cell + sortable-header button — on a 9-column table that's ~144px of column budget bought back for content. Vertical padding is left at 12px so row height stays scan-friendly. Per-table; doesn't interact with the globalbody[data-density]mode (which targets<Inbox>,<DashboardShell>rows, etc. —<DataTable>is intentionally outside that ladder so a CRM page can mix dense tables with comfortable inboxes without the global mode picking sides). Best for numeric-heavy tables (counters, percentages, money) on/customers/health,/payments, persona surfaces.
Notes
- R35-A already shipped in v1.31.0. Spark's R35-A asked for
resizableColumns+storageKeyprops on<DataTable>for drag-to-resize columns with localStorage persistence. That's exactly what v1.31.0's R32 close-out shipped — but with the more comprehensivetableKey="..."API that unlocks resize and reorder and show/hide all in one prop. R35-A is satisfied bytableKey; the per-columnresizable: falseopt-out Spark wanted is also already in. No kit change needed for R35-A — Spark just needs to usetableKeyinstead of the proposed prop names. - R34 (naming nit) acknowledged, no kit change. Spark also flagged that
MarketingFeatureGridhas an associated type but noMarketingFeatureIconicon export. Naming is fine; no rename needed. Logged the team's docs-page choice (RocketIcon) for context. - All five additions are opt-in. Existing consumers see no behaviour change. SectionCard markup is identical when
subtitleis unset; Switch firesonChangefirst when both handlers are set; CodeBlock figcaption-with-copy is unchanged whencopyableis omitted; DataTable padding is unchanged whendensityis omitted. - All gates green: typecheck ✓, markup-equivalence ✓,
check:docs✓, CSS / UI / kit-site builds. - Pair with
@magicblocksai/[email protected].
Round R33–R35 mapping
| spark-id | kit-id | what shipped | |---|---|---| | R33-A (open) | R33-1 | <SectionCard subtitle> slot + .section-card-head-text + .section-card-subtitle CSS | | R33-B (open) | R33-2 | <Switch onCheckedChange> higher-level boolean handler | | R33-C (open) | R33-3 | <CodeBlock copyable> corner-overlay copy button + .codeblk-copy-corner CSS | | R34 (open) | R33-4 | <GithubIcon> + <BranchIcon> source-control glyphs | | R35-A (open) | — | Already shipped in v1.31.0 as tableKey prop (more comprehensive than the proposed resizableColumns + storageKey) | | R35-B (open) | R33-5 | <DataTable density="compact"> per-table compact-density mode |
[1.32.0] — 2026-05-09
Patch — Website Round W7 (R025 + R026 + R027 + R028 + R029). Five mobile-layout bugs across kit narrative components — every fix is scoped to mobile breakpoints (≤640px / ≤720px / ≤768px); desktop renders identically to v1.31.0. Full close-out at WEBSITE-FIXES-LOG.md Round W7.
Fixed
<RaceTimeline>blends both scenarios on mobile (R025). Pre-1.32.0 the column-collapse on mobile rendered rows in source order (left-1, right-1, left-2, right-2, …) — the two-timeline mental model was destroyed and visitors read it as one nonsensical interleaved sequence. Fix: mobile media query now uses CSSorderto stack all left ticks (without-MagicBlocks scenario) under the bad heading, then all right ticks (with-MagicBlocks scenario) under the good heading. Visitor reads scenario A top-to-bottom, then scenario B top-to-bottom — the cleaner mobile rhythm Spark requested. Thedisplay: contentspair-wrappers in the JSX letorderapply to children as direct grid items. Pure-CSS fix; no TSX change.
<ConversationPreview>.cv-msgescapes the device frame (R026). The website team observed message bubbles rendering 616px above their.cv-threadparent on/use-cases/prequalify-leadsat 375×812. Their diagnosis suspected.cv-msgabsolute positioning — but the kit's.cv-msgrule has no positioning; the bug was natural-flow placement going wrong inside a tall fixed-aspect parent (.device.phone .device-viewportwhoseaspect-ratio: 9/19.5produces a 325×705 viewport on mobile while.cvcontent is shorter). Fix: anchor the layout deterministically —.cvbecomes a flex column (head + thread + foot stack vertically);.cv-threadflex-grows to fill remaining space (flex: 1 1 auto; min-height: 0). Messages flow inside the thread regardless of parent height. Desktop unchanged (parents on desktop have natural height;flex-growwith no height constraint resolves toauto).
- Hero
StatStackoverlapsHeroLiveDemoon mobile (R027). Consumer-side composition bug — the website's hero composesHeroLiveDemo+StatsStripand the mobile reflow at ~375px caused stat numbers to paint on top of the phone illustration. Fix: new.kit-heroCSS recipe utility class. Consumers wrap their hero in<section class="kit-hero">with three slot classes (.kit-hero-text,.kit-hero-visual,.kit-hero-proof); the recipe handles desktop side-by-side layout and mobile reflow (text → visual capped at 360px centred → proof) so the proof block always stacks below the visual. Documented inHeroLiveDemo.md§"Hero composition recipe".
<DecayCurve>axis labels illegible on mobile (R028). SVG text scales with the viewBox; at narrow viewports the chart shrinks to ~280px wide and the 11px axis labels render at ~3px (illegible). Fix: at ≤640px wrap the chart in a horizontally-scrollable scope with the SVG'smin-width: 480px+ a soft right-edge mask cue (same idiom as<DataTable>mobile +<PressStrip layout="scroll">). The chart paints at min 480px wide; the visitor swipes horizontally to see the full curve while every label stays at its 11px readable size. The website team's R028 entry preferred a "2-state comparison instead of full curve" alternative — that's a real future enhancement requiring component restructuring; this round ships the CSS-only relief and leaves room for the bigger redesign later.
<EngineBlock>channel chips disappear on mobile (R029). The kit's mobile rule explicitly hides the SVG orbit (display: noneon.engine-orbit) at ≤720px — that broke the "we work across multiple channels" beat on every mobile visitor to/how-it-works,/, and/built-for-production. Fix: TSX renders an additional.engine-channel-stripHTML strip below the engine-core listing the same 4 channel labels as inline pills with an "Across" caption; CSS hides the strip on desktop (where the orbit owns the channels) and shows it on mobile (where the orbit is hidden). Channel labels reuse thechannelsprop (defaults to["SMS", "MAIL", "CHAT", "VOICE"]).
Notes
- All five fixes are mobile-only. Desktop renders identically to v1.31.0 — every change is wrapped in
@media (max-width: ...)or applies to a mobile-shown HTML element. Existing kit-site chapter demos and dashboard-shaped consumers see no behaviour change. - No breaking changes. R025–R028 are CSS-only. R029 adds one new HTML element to
<EngineBlock>'s render tree (the.engine-channel-strip) but it's hidden on desktop via CSS; markup-equivalence fixtures match. - All gates green: typecheck ✓, markup-equivalence (25/0/58) ✓,
check:docs(346 snippets) ✓, CSS / UI / kit-site builds. - CSS bundle: 590450 / 386504 bytes. Tokens-only subpath unchanged.
- Pair with
@magicblocksai/[email protected].
Round W7 mapping
| website-id | kit-id | what shipped | |---|---|---| | R025 (open) | W7-R025 | RaceTimeline mobile reflow via CSS order — sequential stacked lists | | R026 (open) | W7-R026 | ConversationPreview .cv flex-column + .cv-thread flex-grow anchor | | R027 (open) | W7-R027 | New .kit-hero CSS recipe utility class for hero compositions; HeroLiveDemo.md doc | | R028 (open) | W7-R028 | DecayCurve mobile horizontal-scroll with min-width: 480px + mask-image cue | | R029 (open) | W7-R029 | EngineBlock mobile-only .engine-channel-strip HTML strip; reuses channels prop |
Website queue: empty
This round closes the open queue from the website team's BRAND_KIT_REQUESTS.md. Whatever they file next is the next round.
[1.31.0] — 2026-05-09
Minor — Spark Round 30 (R31 + R32 by Spark numbering). The <DataTable> user-customisable column layout round. R31 ships the small developer-side opt-out for chip-shaped cells (truncate: false per column); R32 ships the major user-side knob (tableKey prop unlocking drag-to-resize + drag-to-reorder + show/hide columns + reset, all with localStorage persistence). Both shipped together because they're complementary — R31 fixes the truncated-chip display bug; R32 fixes the underlying "consumer-sized columns don't fit every user's workflow" problem of which the truncated chip is one symptom. Full close-out at SPARK-FIXES-LOG.md Round 30.
Added (R31 — truncate: false per column)
DataTableColumn.truncate?: boolean(defaulttrue). Whenfalse, applies.is-no-truncateto the cell, disablingtext-overflow: ellipsisso chip / pill / badge / button cells render complete instead of being painted with a stray…for any few-pixel overflow. The historic kit default (true) is correct for cells with string-shaped content (names, domains, subject lines) — it's wrong only for cells whose render returns a single inline visual unit. Spark hit this on Support's priority chip; the workaround was over-sizing the column to fit the chip + cell padding, which is a leaky abstraction (consumers had to know chip intrinsic width + cell padding + a font-metrics fudge factor).truncate: falselets consumers author chip-bearing columns at the right intrinsic width without the fake-ellipsis artefact.
Added (R32 — user-customisable column layout)
DataTableProps.tableKey?: string— when set, unlocks four user-facing affordances: 1. Drag-to-resize columns — hover the right edge of any header cell, the cursor switches tocol-resize, drag commits a new width. Persists tolocalStorageundermagicblocks.data-table.${tableKey}.widths. Min-width clamped to 60px. 2. Drag-to-reorder columns — drag a header, drop on another to insert before/after based on cursor position relative to the target's midpoint. Persists tomagicblocks.data-table.${tableKey}.order. HTML5 drag-and-drop (no new npm dependency). 3. Show / hide columns — kebab menu in a new chrome bar above the grid lists every column with checkboxes. Hidden set persists tomagicblocks.data-table.${tableKey}.hidden. 4. Reset columns — menu item at the bottom of the kebab menu clears all three localStorage keys at once and re-reads the consumer's column definitions.
- Per-column gating fields on
DataTableColumn(all defaulttrue): -resizable: false— pin the column's width. -reorderable: false— pin the column's position. -hideable: false— appears in the show/hide menu but the checkbox is disabled with a "Pinned" badge. Use for primary-key columns the table is meaningless without (e.g. a "Name" column).
useDataTableLayout(tableKey)hook — exported from@magicblocksai/ui. Returns the layout state + setters (setWidth,setOrder,toggleHidden,setHidden,reset). PasstableKey={undefined}to receive a no-op layout (always empty; setters are silent). Lets consumers read or mutate the layout state externally — e.g. to render a sibling "Reset columns" button outside the table chrome, or mirror the layout to a different surface.
applyDataTableLayout(columns, layout)helper — pure function that returns columns in user-defined order, with width overrides applied, and hidden columns filtered out. Generic over the column type — only requires a stringifiablekeyfield. Lets consumers apply the same layout state to non-DataTable surfaces (printable views, export configs, etc.).
CSS additions
.data-table-cell.is-no-truncate(R31) — disablestext-overflow: ellipsis; preserveswhite-space: nowrap..data-table-chrome(R32) — the chrome bar above the grid hosting the kebab trigger..data-table-menu-trigger+.data-table-menufamily — kebab + popover; uses the kit's existing--sh-2,--sh-focus, animation tokens..data-table-resize-grip— 8px-wide invisible-by-default hit target on the right edge of each header cell with a 2px tinted bar shown on hover via::after..data-table-th.is-reorderable/.is-dragging/.is-drop-before/.is-drop-after— drag-state visual feedback. Drop indicator is a 2px accent-coloured bar at the target cell's leading or trailing edge.
Notes
- Both behaviours are gated on
tableKey. Without it,<DataTable>is byte-equivalent to v1.30.0 — no chrome bar, no kebab, no resize-grips, no draggable headers. The kit ships zero customisation UI for tables that don't opt in. - No new npm dependencies — uses HTML5 drag-and-drop for reorder, native pointer events for resize. The kit's existing
useLocalStorage(v1.7.0+) backs the persistence layer. - SSR-safe —
useLocalStoragereturns the fallback during server render and on first client render, then hydrates on mount. This produces a single column-layout flicker on first paint for tables with non-default saved layouts; documented as a tolerated trade-off matching<ThemeToggle>behaviour. - Layered overrides, not a data binding. The consumer's
columnsprop remains authoritative on first load and after Reset. Saved layouts are an override on top — when the consumer adds a new column, it appears at the end of the user's saved order automatically; when the consumer removes a column, the user's order silently drops the dead key. - All gates green: typecheck ✓, markup-equivalence (25/0/58) ✓,
check:docs(346 snippets) ✓, CSS / UI / kit-site builds. - CSS bundle: 583810 / 385031 bytes. Tokens-only subpath unchanged.
- Pair with
@magicblocksai/[email protected].
Companion features deferred
Spark's R32 entry mentioned two "worth considering" items that didn't ship in this round:
- Column-pin (sticky-left / sticky-right) — useful for wide tables on desktop (the leading "Name" column stays visible while horizontal scrolling). Real feature, real demand, but requires CSS
position: stickyon the cell + careful interaction with the existingmask-imagemobile scroll cue. Deferred to a follow-up round. - Saved named layouts (presets per
tableKey) — power users who slice the same table multiple ways. Lower priority and a non-trivial UI surface. Deferred indefinitely until a concrete consumer ask lands.
Spark migration
After upgrading to @magicblocksai/[email protected] + @magicblocksai/[email protected]:
1. Restore the LifecyclePill columns to their natural width on Contacts, Companies, CustomersWork, Expansion. The 175px workaround Spark flagged as "surgical fix; will be retired when R32 lands" can come out — pass tableKey="spark.contacts" (etc.) on each table; users will resize as they need. 2. Add truncate: false to every column whose render returns a chip / pill / badge / button. Spark's specific call-out is the Support priority + status chips, but the round notes "likely lurks on every other chip / pill / badge column" — sweep them. 3. Pin primary-key columns with hideable: false so users can't hide the column the table is meaningless without (e.g. the Name column on Contacts). 4. Pick stable, namespaced tableKey values — "spark.tickets", "spark.contacts", "spark.deals", etc. The key participates in the localStorage path; collisions across pages would corrupt each other's saved layouts.
Round 30 mapping
| spark-id | kit-id | what shipped | |---|---|---| | R31 (open) | R31 | DataTableColumn.truncate?: boolean + .data-table-cell.is-no-truncate CSS | | R32 (open) | R32 | DataTable.tableKey?: string + useDataTableLayout hook + applyDataTableLayout helper + per-column resizable / reorderable / hideable flags + chrome bar + kebab menu + resize-grip + drag-and-drop reorder + Reset menu item |
Spark queue: empty
This round closes the open Spark queue. Whatever Spark files next is the next round.
[1.30.0] — 2026-05-09
Patch — Spark Round 29 (R30 by Spark numbering). Singleton CSS-only fix following the same shape as R29 (v1.29.0), one component family over. Lifts the v1.9.3 R9-1 chrome-strip rule from direct-child (>) to descendant for the four on-root-chrome list primitives so it survives consumer wrapper-div compositions. Full close-out at SPARK-FIXES-LOG.md Round 29.
Fixed
<Inbox>(and siblings) doubled-up chrome inside bucket-wrapped<SectionCard>layouts (R30). The kit's v1.9.3 R9-1 chrome-strip family used direct-child selectors (.section-card-body > .inbox, > .checklist, > .data-table, > .calendar, > .panel) — correct for the simple<SectionCard padded={false}><Inbox/></SectionCard>case, but broken for the bucket-wrapped pattern consumers reach for to interleave date-group headers between list groups:
``html <SectionCard padded={false}> <div> <!-- bucket wrapper --> <div class="spark-bucket-head">…</div> <!-- "LAST WEEK" header --> <Inbox> <!-- grandchild — R9-1 missed it --> <InboxRow … /> </Inbox> </div> <div>… next bucket …</div> </SectionCard> ``
Pre-1.30.0 the inner <Inbox> was a grandchild of .section-card-body and inherited its full card chrome (border + radius + paper bg), doubling up against the outer SectionCard. Visible on Spark's email-replies / multi-bucket inbox pages.
Fix: lift the chrome-strip from direct-child to descendant for the four on-root-chrome primitives (.inbox, .checklist, .calendar, .panel). .data-table stays at direct-child because DataTable's chrome lives on a known inner element (.data-table-grid, see R29's sibling rule in v1.29.0) — there's no wrapper-div ambiguity to handle there.
Notes
- No breaking changes. The descendant selector is strictly more permissive — every consumer relying on the old
>behaviour still gets the chrome-strip; bucket-wrapped consumers now also do. - Edge case — a kit consumer who genuinely wants a deeply-nested
.inbox/.checklist/.calendar/.panelto keep its chrome inside a SectionCard body (e.g. a literal "card within a card" composition with intentional doubled chrome) would now lose it. This is exotic; consumers in that case can re-add the chrome on the inner element from their own stylesheet. - CSS-only patch. No TSX changes, no new prop. Same shape as R29 (v1.29.0).
- All gates green: typecheck ✓, markup-equivalence (25/0/58) ✓,
check:docs(346 snippets) ✓, CSS / UI / kit-site builds. - CSS bundle: 577921 / 381474 bytes. Tokens-only subpath unchanged.
- Pair with
@magicblocksai/[email protected].
Spark migration
After upgrading to @magicblocksai/[email protected] + @magicblocksai/[email protected]:
apps/web/src/index.css— delete the scoped.section-card-body .inbox { border: 0; border-radius: 0; background: transparent }override that fixed the bucket-wrapped email-replies layout. The kit ships the equivalent rule by default now.
The lesson, generalised
Two consecutive rounds (R29 v1.29.0 + R30 v1.30.0) caught the same shape of bug — direct-child selectors are brittle to consumer markup variations. R29's fix kept > because DataTable's chrome lives on a known inner element (.data-table-grid) which the consumer doesn't wrap. R30's fix lifts to descendant because the four on-root-chrome primitives' chrome lives on the root, and any wrapper between root and section-card-body breaks the rule.
The audit-on-markup-change rule from R29's close-out generalises: any time the kit team adds an interleaved-header pattern (or any other "wrap each list in a div" recipe) to a chapter or doc, the R9-1 chrome-strip family should be re-checked — both directions. The kit-side check is mechanical: grep _shared.css for .section-card-body > selectors and confirm the wrapped-grandchild case still resolves correctly.
[1.29.0] — 2026-05-09
Patch — Spark Round 28 (R29 by Spark numbering). Singleton CSS-only fix for a long-standing no-op in the kit's chrome-strip rule for list-roots inside SectionCards. Full close-out at SPARK-FIXES-LOG.md Round 28.
Fixed
<DataTable>doubled-up chrome inside<SectionCard padded={false}>(R29). The kit's v1.9.3 R9-1 chrome-strip rule (.section-card-body > .inbox, .section-card-body > .checklist, .section-card-body > .data-table, .section-card-body > .calendar, .section-card-body > .panel { border: 0; border-radius: 0; background: transparent }) correctly stripped chrome from.inbox,.checklist,.calendar, and.panel(all chrome-on-root primitives) but was a no-op for.data-table— DataTable's chrome (border + radius + paper background) lives on the inner.data-table-gridelement, not the outer.data-tableflex wrapper. Result: a 14px-radius outer SectionCard around a 10px-radius inner DataTable grid, both with the same hair-coloured border. Visible on every Spark list page (/sequences,/contacts,/deals,/companies,/leads,/broadcasts,/triage).
Fix: sibling rule that targets the actual chrome-bearing inner element: .section-card-body > .data-table > .data-table-grid { border: 0; border-radius: 0; background: transparent }. Zero-config — no prop, no modifier; the canonical <SectionCard padded={false}><DataTable /></SectionCard> shape just renders correctly now. Same idiom as the rest of the chrome-strip family.
Notes
- No breaking changes. The fix only takes effect when DataTable sits as a direct child of
.section-card-body— standalone DataTable use (page-top-level inside a regular wrapper) keeps its full card chrome. - CSS-only patch. No TSX changes, no new prop. The kit's React
<DataTable>is unchanged. - All gates green: typecheck ✓, markup-equivalence (25/0/58) ✓,
check:docs(346 snippets) ✓, CSS / UI / kit-site builds. - CSS bundle: 576906 / 381474 bytes. Tokens-only subpath unchanged.
- Pair with
@magicblocksai/[email protected].
Spark migration
After upgrading to @magicblocksai/[email protected] + @magicblocksai/[email protected]:
apps/web/src/index.css— delete the scoped.section-card-body .data-table-grid { border: 0; border-radius: 0; background: transparent }override. The kit ships the equivalent rule by default now.
Why ship as :where()-style auto-strip rather than <DataTable bare> prop
Spark's R29 entry offered both options. Going with the zero-config CSS rule because:
1. Matches the kit's existing pattern — the v1.9.3 R9-1 family handles .inbox, .checklist, .calendar, .panel automatically; making DataTable the only opt-in primitive in the family would be inconsistent. 2. Zero new API surface — no prop to teach, document, or maintain. The canonical <SectionCard><DataTable /></SectionCard> shape just works. 3. Edge cases are exotic — consumers using a SectionCard body for something other than the "card-of-list" pattern with a chrome-wanting DataTable inside are rare. If anyone hits that, they can override by re-adding the chrome on the inner .data-table-grid from their own stylesheet.
The descendant selector is scoped tightly (> .data-table > .data-table-grid direct-children, not deep descendant) so nested compositions don't accidentally strip chrome from a DataTable that lives several layers deep inside the SectionCard body.
[1.28.0] — 2026-05-09
Minor — Website Round W6 (R021 + R022 + R023). Three-item CSS-only batch closing the open website-team queue. All three are polish items on existing primitives (no new components, no new props). Full close-out at WEBSITE-FIXES-LOG.md Round W6.
Fixed
.section.is-warm/.section--warmtoken rescope (R021) — extended to include--bg-paper(pinned tovar(--paper, #FFFFFF)) and--hair-soft. Pre-1.28.0 the rule only rescoped--bg,--fg,--fg-soft,--fg-dim,--fg-faint,--hair— kit components inside warm sections that consumevar(--bg-paper)(<HandoffCard>,<EcosystemRings>inner chips, anything with paper-elevated card surfaces) inherited the body-level dark-mode value (#2A3050elevated ink) and rendered as dark blobs on cream surfaces in dark mode. The.section.is-inkrule already rescoped--bg-papercorrectly; this brings.is-warmto symmetry..triptych margin-inline: auto(R022) — missed from the v1.25.0 W5-R018 narrative-component centring batch. The Triptych hasmax-width: 1080pxbut pre-1.28.0 had nomargin-inline, so on a 1200px page-content measure it sat at the start of the line with all 120px of leftover space stuck on the right beneath a centred head. Same shape as the.scoreboard/.engine-block/.decay-curve/.happa-arc/.handoff-cardrules that did get the W5-R018 treatment..ecosystem-rings .er-ringand.integration-hub .spokes linestrokes (R023) — bumpedstroke-widthfrom1→1.6on both, and shifted the stroke colour to a more readable tint: -.ecosystem-rings .er-ring(inner):var(--hair)→color-mix(in oklab, var(--ink) 22%, transparent)-.ecosystem-rings .er-ring.outer: 32% accent → 55% accent (the brand-pink anchor of the visual centre) -.integration-hub .spokes line: 35%-accent /--hairmix → 45%-accent solid
Pre-1.28.0 both components shipped dashed connectors at 1px / hair-tinted strokes — they read as faded ghosts on cream / paper marketing surfaces at 1× zoom, undermining what should be the visual centre of both components ("the engine connects everything"). The new defaults make the connectors clearly visible without losing the "ambient backdrop" feel.
Notes
- No breaking changes. All three are CSS-only adjustments to existing rules. Stroke widths bumped 0.6px and tints adjusted within the same colour family — visually a touch-up, not a redesign.
- Visual change scoped to consumers using
.section--warm(R021),.triptych(R022), and the two narrative components (R023). Any other surface is unchanged. - All gates green: typecheck ✓, markup-equivalence (25/0/58) ✓,
check:docs(346 snippets) ✓, CSS / UI / kit-site builds. - CSS bundle: 576104 / 381386 bytes. Tokens-only subpath unchanged.
- Pair with
@magicblocksai/[email protected].
Website migration
After upgrading to @magicblocksai/[email protected] + @magicblocksai/[email protected]:
1. BaseLayout.astro .section--warm token-rescope override — delete; the kit pins --bg-paper to paper-white inside warm sections now. Kit cards (<HandoffCard>, <EcosystemRings> chips, etc.) render correctly in both themes. 2. BaseLayout.astro .triptych { margin-inline: auto } override — delete; the kit ships this rule by default now. 3. BaseLayout.astro er-ring + .spokes line stroke overrides — delete; the kit's defaults are now the values the website team was overriding to.
Round W6 mapping
| website-id | kit-id | what shipped | |---|---|---| | R021 (open) | W6-R021 | .section.is-warm + .section--warm rescope adds --bg-paper: var(--paper) and --hair-soft | | R022 (open) | W6-R022 | .triptych margin-inline: auto (W5-R018 family member that was missed) | | R023 (open) | W6-R023 | .ecosystem-rings .er-ring + .integration-hub .spokes line stroke bump + tint |
Website queue: empty
This round closes the last open kit-team item from the website team's BRAND_KIT_REQUESTS.md. Whatever the team files next is the next round.
[1.27.0] — 2026-05-09
Minor — Spark Round 27 (kit-side; R18 + R19 + R25 + R26 + R28 by Spark numbering). Five-item batch covering the open Spark queue accumulated since v1.26.0: a new icon, a Label-composability fix, an InboxRow grid-track fix, an InlineHeadline-inside-PageHeader CSS scope, and a SageDrawer composer-cap prop. Full close-out at SPARK-FIXES-LOG.md Round 27.
Added
<TrashIcon>(R18). Bin glyph for destructive-remove affordances on list rows / attachments / custom-field deletions. Distinct from<CloseIcon>(the X-glyph "dismiss / close" affordance) — Spark was using<CloseIcon>as a delete fallback, which read as "dismiss" not "delete." Matches the kit's standard 18×18 monoline spec; documented atpackages/ui/docs/IconFamily.md.<SageDrawer maxRows>prop (R28). Maximum rows the composer textarea grows to before scrolling internally. Default3. Pre-1.27.0 the kit shipped a hardcodedmax-height: 180px(~9 rows), which let the composer eat too much vertical real-estate in floating drawers with busy chat threads — the message stream above ended up cramped and the user lost sight of the last reply while typing a multi-line follow-up. Translates to a--sage-input-max-rowsCSS custom property the.sage-inputrule consumes viamax-height: calc(var(--sage-input-max-rows, 9) * 1.45em + 20px). Same ergonomic shape as<Textarea rows>. The fallback9preserves pre-1.27.0 behaviour for any consumer rendering.sage-inputoutside a<SageDrawer>and not setting the variable.
Fixed
<InboxRow>6-child overflow on desktop (R25). Pre-1.27.0 the.inbox-rowgrid template had 5 explicit tracks (4 base + the v1.22.0 R21-1 selectable variant) but<InboxRow>could render 6 children whenix-select+av+ix-body+ix-due+ix-priority+ix-actionswere all present. Grid auto-flow pushed the 6th child onto a new row at column 1, so the ✓ complete button rendered in the bottom-left corner of any row that setdue+priority+onComplete. Visible on/tasks, the home overview's Tasks/Upcoming card, the contact / company / deal detail LinkedTasks card, and anyInboxPagesection.
Fix: add a trailing auto track to both base templates. 36px 1fr auto auto → 36px 1fr auto auto auto (and the selectable variant gets the same treatment). Trailing unused auto tracks collapse to 0 width — no regression for rows with fewer children. The mobile rule (@media (max-width: 480px)) already accounted for all six slots via subgrid stacking, so this is desktop-only.
<PageHeader onTitleSave>title wraps every word inside flex parent (R26). When<InlineHeadline>was composed inside<PageHeader>'sdisplay: inline-flextitle slot, the kit's defaultword-break: break-wordon.inline-headline-textcollapsed the text node's CSSmin-contentto 1 character — the inline-flex parent shrank to~icon + 1ch, then<InlineHeadline width: 100%>filled that tiny parent and the title wrapped at every word boundary even on wide viewports."MagicBlocks Platform"rendered on two lines asMagicBlocks/Platformon the project detail page (and same on issue / ticket detail).
Fix: scoped CSS rule that swaps the page-header-title-slot's <InlineHeadline> to width: auto; max-width: 100% and the inner .inline-headline-text to white-space: nowrap; overflow: hidden; text-overflow: ellipsis; word-break: normal. Standalone <InlineHeadline> (e.g. inside a card body) keeps word-break: break-word for long-word safety; the page-header title gets ellipsis-at-edge — the right rhythm for a page-title slot anyway.
<Label>collapses gap when wrapping composite kit controls (R19). The kit's recommended pattern is the sibling layout (<Label htmlFor=…>Text</Label> <Input id=… />), but the nested-child pattern (<Label>Text<Input/></Label>) is what most consumers reach for first because it works for native<input>(the input's block-level rendering provides incidental gap). For composite kit controls (<DatePicker>,<Combobox>,<MultiSelect>,<Select>,<DateRangePicker>) the gap collapsed to zero — the label text and trigger button rendered flush.
Fix: :has() rule on .input-label that turns it into a flex column when it wraps any kit form control as a direct child. Both patterns now produce the same vertical rhythm (label text + 6px gap + control). Consumers wrapping their own custom controls can opt into the same layout via the new .input-label.is-stack modifier.
Notes
- No breaking changes. All five fixes are either additive (new icon, new prop, new CSS rule) or strictly more permissive (the InboxRow grid template grows from 5 to 6 explicit tracks; the trailing
autocollapses for fewer-child rows). - All gates green: typecheck ✓, markup-equivalence (25/0/58) ✓,
check:docs(346 snippets) ✓, CSS / UI / kit-site builds. - CSS bundle: 574044 / 381275 bytes. Tokens-only subpath unchanged (16865 / 4992).
- Pair with
@magicblocksai/[email protected].
Spark migration
After upgrading to @magicblocksai/[email protected] + @magicblocksai/[email protected]:
1. apps/web/src/index.css — delete the scoped @media (min-width: 481px) .inbox-row { grid-template-columns: ... 6-track } workaround for R25. Kit ships the 6th track by default now. 2. Project / issue / ticket detail page CSS — delete the scoped .page-header-title-text .inline-headline { width: auto } + .inline-headline-text { white-space: nowrap } overrides for R26. Kit ships those rules by default now (scoped to PageHeader's title slot). 3. apps/web/src/routes/admin/EmailDeliverabilityPage.tsx — replace the <CloseIcon> "remove" buttons with <TrashIcon>. Same for any other surface where a close-glyph was serving double-duty as a delete affordance (attachment lists, custom-field deletions, etc.). 4. apps/web/src/routes/ProjectDetailPage.tsx (PlanCycleDialog) — drop the hand-rolled <label style={{display:'flex',flexDirection:'column',gap:6}}> workaround. Wrap the <DatePicker> in <Label> directly; the :has() rule does the right thing now. 5. Sage drawer override — delete .sage-drawer .sage-input { max-height: 80px } per-page CSS. Pass <SageDrawer maxRows={3}> instead (3 is the kit default, so just dropping maxRows works too).
Round 27 mapping
| spark-id | kit-id | what shipped | |---|---|---| | R18 (open) | R18 | <TrashIcon> added to icons.tsx + barrel + carve-out + IconFamily.md catalogue | | R19 (open) | R19 | :has() rule on .input-label for composite-control children + .input-label.is-stack opt-in modifier | | R25 (open) | R25 | .inbox-row grid template extended from 4/5 → 5/6 explicit tracks | | R26 (open) | R26 | Scoped white-space: nowrap + ellipsis on .page-header-title-text .inline-headline-text | | R28 (open) | R28 | <SageDrawer maxRows> prop + --sage-input-max-rows CSS custom property |
Spark queue: empty
This round closes the last open kit-team item from the Spark file. Whatever Spark files next is the next round.
[1.26.0] — 2026-05-08
Minor — Spark Round 26 (R27 by Spark numbering). Singleton round adding the <Inbox striped> prop Spark requested, plus a small cosmetic CSS fix for the last-row hairline bleeding through <SectionCard> rounded corners. Full close-out at SPARK-FIXES-LOG.md Round 26.
Added
<Inbox striped>prop (R27). Boolean, defaultfalse. Same behaviour as the existingbandedprop (which is now deprecated alongside) but with the friendlier name matching<DataTable striped>. Whenstriped={true}, every other row gets a hairline-soft tint to give long mailbox-style lists rhythm to scan. The kit's CSS class is unchanged (.inbox.inbox-banded) so vanilla CSS-class consumers see no behaviour change.
The kit's default has always been NOT striped. Spark's R27 ask read as "default is striped, please add an opt-out" — that was a misread of the kit's current state (the v1.12.0 R11-3 zebra rule is scoped to .inbox-banded, opt-in only). The right migration for Spark's /inbox page is to drop the implicit banded (or pass striped={false} explicitly to override any upstream config).
Deprecated
<Inbox banded>prop. Alias forstriped— same behaviour. Kept for back-compat with v1.12.0 call sites; will be removed in v2.0.0. When bothstripedandbandedare set,stripedwins.
Fixed
- Last-row hairline bleeds through
<SectionCard>rounded corners. When an<Inbox>(or<Checklist>, or<DataTable>) is the direct child of a<SectionCard>, the row'sborder-bottom: 1px solid var(--hair-soft)paints across the bottom edge of the last row — and that 1px line gets clipped by the card'soverflow: hiddenrounded corner curve, producing a tiny hairline arc that cuts inside the bottom-left and bottom-right corners. v1.26.0 drops the last-row border on every list-shaped child of.section-card-bodyso the card's curve owns the bottom edge cleanly.
Notes
- No breaking changes.
<Inbox>consumers withoutstripedorbandedsee no change (the kit default has always been clean rows).<Inbox banded>consumers continue to render banded — same CSS class under the hood, the prop is just deprecated. The cosmetic last-row fix is invisible on cards where the layout was already fine; visible only where the artefact existed. - CSS bundle grows ~10 lines (the new last-row rule + comment block).
- All gates green: typecheck ✓, markup-equivalence (25/0/58) ✓,
check:docs(346 snippets) ✓, CSS / UI / kit-site builds. - Pair with
@magicblocksai/[email protected].
Spark migration
After upgrading to @magicblocksai/[email protected] + @magicblocksai/[email protected]:
1. /inbox page CSS overrides — delete the .spark-page-feed .inbox-row, .spark-page-feed .inbox-row:nth-child(2n) { background: var(--bg-paper) } rules. The kit's default is already clean rows; just don't pass banded (or striped) on the inbox-inside-SectionCard instances. 2. .spark-page-feed .section-card-body > .inbox:last-child .inbox-row:last-child { border-bottom: 0 } — delete; the kit ships this rule by default now for every list-shaped child of <SectionCard>. 3. Existing <Inbox banded> call sites — keep working as deprecated alias. Optional cleanup: rename banded → striped for consistency with <DataTable striped>. The TypeScript prop carries @deprecated so editor hovers will surface the rename.
[1.25.0] — 2026-05-08
Minor — Website Round W5 (R012 + R014 + R015 + R018). Narrative-component layout batch — four asks shipped together about how kit narrative components and dark-mode tokens behave on real marketing surfaces. Closes the website-team queue (R006–R020 all shipped or documented). Full close-out at WEBSITE-FIXES-LOG.md Round W5.
Added
<HeroBloomCanvas bleed>prop (W5-R015). Drops the canvas's defaultborder-radius: var(--r-lg)for full-bleed marketing-page heroes. The default rounded variant stays correct for kit-chapter use (canvas inset on a paper page);bleedis the marketing-hero opt-in for contexts where the canvas extends to the section edges and rounded corners produce a visible "card on a page" gap. Maps to.hero-bloom-canvas.is-bleedCSS.
Fixed
- Dark-mode tokens recognise
data-theme="dark"on either<html>OR<body>(W5-R014). Pre-1.25.0 every dark-mode selector was scoped tobody[data-theme="dark"]— the conventional no-flash pattern (run-before-paint script in<head>) sets the attribute on<html>first because<body>hasn't parsed yet, so kit tokens stayed in light values until body parsed. Fix: mass-replace everybody[data-theme="dark"]selector with:is(html, body)[data-theme="dark"](46 rules).:is()keeps the original specificity (bothhtmlandbodyare 0,0,1 type selectors) so cascade is identical; the:is()ancestor accepts whichever root the consumer prefers. The website team can now drop their dual-set workaround and putdata-themeon<html>only. - Narrative components centre by default (W5-R018). Pre-1.25.0
<Scoreboard>,<EngineBlock>,<DecayCurve>,<HappaArc>, and<HandoffCard>had their ownmax-widthconstraints internally but nomargin-inline: auto— when dropped into a wider section under a centered head, they sat at the start of the line and read as left-aligned beneath a centered headline. The siblings (<EcosystemRings>,<IntegrationHub>,<GuardianShield>) already shipped centred. v1.25.0 aligns the family — every chapter-11 narrative component nowmargin-inline: autos by default. The<HandoffCard>was the worst offender (380px constraint inside a 1200px section = 410px off-axis); now centres cleanly.
Documentation
<HeroBloomCanvas>fixed-aspect-frame constraint documented (W5-R012). New "Layout — fixed-aspect canvas + sibling overflow" section inHeroBloomCanvas.md. The canvas is intentionally fixed-aspect (aspect-ratio: 16/7; min-height: 320px) with absolutely-positioned.hbc-content— children sit centred inside the cinematic frame and don't grow the canvas. The fix is always the sibling pattern (consumer proof bands / stats strips render beneath the canvas, not inside it), neverposition: relative; height: autoon.hbc-content— that would break the fixed-aspect contract every kit chapter demo relies on. Inline ⚠️ comment added in_shared.cssnear the rule pointing at the doc.
Notes
:is(html, body)[data-theme="dark"]keeps specificity identical (0,1,1 — same asbody[data-theme="dark"]). No cascade reshuffle; existing rules that override or are overridden by these selectors behave the same way.- CSS bundle grows by ~30 lines (the
.hero-bloom-canvas.is-bleedrule,margin-inline: autodeclarations on five narrative components, comment blocks). The dark-mode selector replacement adds bytes via the longer selector but doesn't add rules. Tokens-only subpath: 16064 → 16865 bytes (~5KB minified, marginal increase from the wider dark-token selector). - No breaking changes. Every existing consumer keeps working —
<HeroBloomCanvas>defaults to rounded; dark-mode tokens still cascade when consumers setdata-themeon body (the original pattern); narrative components that were previously left-aligned will now centre, which most consumers explicitly wanted (the siblings already centred — this is bringing the family in line). - All gates green: typecheck ✓, markup-equivalence (25/0/58) ✓,
check:docs(346 snippets) ✓, CSS / UI / kit-site builds. - Pair with
@magicblocksai/[email protected].
Website migration
After upgrading to @magicblocksai/[email protected] + @magicblocksai/[email protected]:
1. BaseLayout.astro no-flash script — drop the dual-set workaround (the deferred <body> set after DOMContentLoaded). Setting data-theme on <html> only is now sufficient for the kit's tokens to cascade correctly. 2. .home-hero .hero-bloom-canvas { border-radius: 0 } consumer override — delete; pass <HeroBloomCanvas bleed> instead. 3. <StatsStrip> proof band placement — already correct as a sibling of <HeroBloomCanvas> (the website team's local fix). Keep it. The kit now documents this as the canonical pattern in HeroBloomCanvas.md. 4. Narrative-component centring overrides — the :global .scoreboard, .engine-block, .decay-curve, .happa-arc, .handoff-card { margin-left: auto; margin-right: auto } rule in index.astro — delete; the kit centres these by default now.
Round W5 mapping
| website-id | kit-id | what shipped | |---|---|---| | R012 (open) | W5-R012 | Doc + ⚠️ inline comment for <HeroBloomCanvas> fixed-aspect-frame constraint; sibling-overflow recipe in HeroBloomCanvas.md | | R014 (open) | W5-R014 | :is(html, body)[data-theme="dark"] selector (46 dark-mode rules updated) | | R015 (open) | W5-R015 | <HeroBloomCanvas bleed> prop + .hero-bloom-canvas.is-bleed CSS | | R018 (open) | W5-R018 | margin-inline: auto on .scoreboard, .engine-block, .decay-curve, .happa-arc, .handoff-card |
Website queue: empty
This round closes the last open kit-team item from the website team's BRAND_KIT_REQUESTS.md (R006–R020 all resolved; the team's log just needs to mark R006–R011 ✅ from v1.19.0). Recap of website items shipped:
- W1 (v1.17.0) — R003 partial (tokens subpath), R004 (Drawer lazy-import doc), R005 (Scoreboard composition recipe)
- W2 (v1.18.0) — R001 (
<IndustryBar>), R002 (<CostCompare>) - W3 (v1.19.0) — R006 (
--on-accenttoken), R007 (.heroreserved), R008 (--f-italictoken), R009 (<EcosystemRings>dev-warn), R010 (HeroLiveDemo / ChatCompare freeze-frame doc), R011 (<PressStrip layout="scroll">) - W4 (v1.24.0) — R013/R016 (
.section-head.is-stack), R017 (.section.is-ink/.is-warm), R019 (<Button variant="ink">), R020 (<PressStrip chrome="bare">) - W5 (v1.25.0, this) — R012, R014, R015, R018
Whatever the website team files next is the next round.
[1.24.0] — 2026-05-08
Minor — Website Round W4 (R016/R013 + R017 + R019 + R020). Marketing-page chrome batch — four asks shipped together because they're all about making the kit's primitives read correctly on real marketing surfaces (vs the kit chapter pages where they were authored). Closes the highest-impact website-team items per the user's recommended sequencing. Full close-out at WEBSITE-FIXES-LOG.md Round W4.
Added
<Button variant="ink">(W4-R019, headline item). New filled CTA variant — ink bg + paper text — for sales-led marketing primary CTAs. Per the brand kit's own02-colour.html§2.2: "Pink-700 is the signature accent. … the colour that glows without shouting." Pink-as-primary on every marketing CTA inverts that — it makes the brand colour the resting paint instead of an accent. Theinkvariant restores the right separation: pink stays for accents (italic words, callouts, comparisons, in-app affirmative actions like Save/Apply), ink takes the marketing-CTA role (homepage hero, pricing page, "Watch a demo" / "Book a demo" / "Get started"). Hover state mixes 8% accent into the ink so the brand colour stays in the interaction model. Active mixes 14%. Dark-mode automatically inverts to a paper-on-ink-page button so the CTA still reads as primary in dark contexts.<PressStrip chrome="bare">(W4-R020). New chrome variant for marketing-page trust strips. The defaultchrome="card"(back-compat) renders the strip with paper bg + hairline border + 14px radius — correct for kit chapter pages where the strip is a card-on-paper demo, wrong on real marketing pages where the rounded card chrome reads as a "weird floating block.""bare"strips the chrome (transparent bg, no border / radius / padding) so the strip becomes a thin trust signal that flows with the page rhythm. The token rescope inside.press-stripstays so eyebrow + label tones still resolve correctly. Pair withlayout="scroll"(v1.19.0 W3-R011) for the canonical homepage trust-strip recipe..section.is-ink/.section.is-warmmodifiers (W4-R017). Surface-tone modifiers for marketing sections. Same idiom as the kit's existing.scoreboard.dark,.cc-col, and.hero-bloom-canvas[data-variant="war-room"]token-rescope pattern — the modifier rescopes--bg,--fg,--fg-soft,--fg-dim,--fg-faint,--hair,--hair-softto ink-mode (or warm-mode) values inside the section. Every kit text primitive (.eyebrow,.lede,.caption,.micro,.section-title, etc.) adapts its colour automatically without per-class.on-inkmodifier proliferation. Pre-1.24.0 the website team had to ship a.section-head--on-inkmodifier with hardcoded paper-tinted overrides; now the surface modifier on the wrapping.sectiondoes the work. BEM-style.section--ink/.section--warmaliases also accepted for consumers who prefer that convention..section-head.is-stackmodifier (W4-R016/R013). Centred editorial variant of the.section-headrow. The default.section-headis a dashboard-style flex row (justify-content: space-between) for app-surface "Title | Actions" headers — the website team's marketing pages need the inverse: a vertically-stacked centered triplet (eyebrow + h2 + lede). The new.is-stackmodifier flipsdisplaytoblock, centres children viamargin: 0 auto, and caps<p>lede children at 64ch. Pair with.section.is-ink/.section.is-warmfor surface-tone-aware rendering.
Documentation
Button.md— full rewrite of the "Picking a variant" section. New table mapping surface (marketing / in-app / destructive / secondary / tertiary) → variant. New "Migration from avariant="primary"-only world" section with the audit recipe.PressStrip.md— new "Marketing-page recipe —chrome="bare"+layout="scroll"" section with the canonical homepage trust-strip pattern.ReservedClassNames.md—.section-headadded to the reserved-class table with a recommendation to reach for.section-head.is-stack(v1.24.0+) for marketing-page editorial headers._shared.csscarries inline ⚠️ comments at the new rules pointing at the docs.
Decisions
- Ship
inkas a new variant, not as a surface-flag onprimary. Keepingbtn-primary(pink) intact preserves every existing call site's behaviour. The decision tree at the consumer site is "marketing surface → useink; app surface → useprimary" — clean cognitive model, no implicit-context resolution. Forcing every consumer to migrate to a different default (or have the variant adapt based on parent surface) would be a hard breaking change for thin payoff. .section.is-inkrescopes tokens, doesn't ship per-class.on-inkmodifiers..eyebrow,.lede,.caption,.micro,.section-title, every other kit text primitive — they already use the kit's token system. The modifier rescopes the tokens; every primitive inherits automatically. Per-class.eyebrow.on-inkmodifiers would lock the kit into knowing which primitives need ink-aware rendering, instead of letting the token system handle it..section-head.is-stackmodifier, not a new.marketing-section-headclass. Same shape as<Kanban mobileLayout>(R18-2) and<PressStrip layout>(R11) — opt-in modifier, default unchanged for back-compat. The website team's existing.section-headoverrides become a one-class swap.
Notes
- No breaking changes. Every existing consumer keeps working —
<Button variant="primary">paints pink as before;<PressStrip>defaults tochrome="card";.section-headdefaults to its dashboard flex layout. Every new variant / modifier is additive opt-in. - CSS bundle grows ~85 lines (
.btn-inkfamily +.press-strip.is-bare+.section.is-ink/.is-warm+.section-head.is-stack). Tokens-only subpath unchanged in shape — the modifier rules live below/* @TOKENS-END */. - All gates green: typecheck ✓, markup-equivalence (25/0/58) ✓,
check:docs(346 snippets) ✓, CSS / UI / kit-site builds. - Pair with
@magicblocksai/[email protected].
Website migration
After upgrading to @magicblocksai/[email protected] + @magicblocksai/[email protected]:
1. Top-nav Watch a Demo → + hero Watch a Demo — replace local .btn-ink port with <Button variant="ink">. Drop the :global([data-theme='dark']) inversion override (kit handles it). 2. .press-strip consumer override in BaseLayout.astro — delete; replace the <PressStrip> call site with <PressStrip chrome="bare" layout="scroll" items={…} />. 3. .section-head consumer overrides in index.astro scoped CSS — delete; replace <header class="section-head"> with <header class="section-head is-stack">. 4. .section--on-ink / .section-head--on-ink modifiers in index.astro — delete; replace with .section.is-ink (or BEM-style .section--ink) on the wrapping section. Eyebrow + lede + section-title all adapt their colour automatically. 5. R006–R011 stale-log entries — mark them ✅ RESOLVED in BRAND_KIT_REQUESTS.md (they all shipped in v1.19.0 — the website team's log just got out of sync).
Round W4 mapping
| website-id | kit-id | what shipped | |---|---|---| | R013 (open) | W4-R013 | .section-head reserved-class doc + .section-head.is-stack modifier (consolidated with R016) | | R016 (open) | W4-R016 | Same as R013 — duplicate ask consolidated | | R017 (open) | W4-R017 | .section.is-ink / .section.is-warm surface-tone modifiers | | R019 (open) | W4-R019 | <Button variant="ink"> + .btn-ink CSS family + Button.md rewrite | | R020 (open) | W4-R020 | <PressStrip chrome="bare"> + .press-strip.is-bare modifier |
Deferred to W5 (next round)
- R012 —
<HeroBloomCanvas>.hbc-contentisposition: absolute; height: 623.4px;(children overflow) - R014 — Dark-mode tokens scoped to
body[data-theme="dark"](no-flash scripts set on<html>first) - R015 —
<HeroBloomCanvas>hasborder-radius: 14pxbaked in (no bleed prop) - R018 — Narrative components don't
margin-inline: autoby default
W5 ships next as a "narrative-component layout" round.
[1.23.0] — 2026-05-08
Minor — Spark Round 25 (kit-side; R20 + R22 by Spark numbering). The "DataTable + PageHeader composability" round per the user's recommendation. Two non-overlapping additive features: anchor-target exposure on <DataTable rowHref> rows (R20) and click-to-edit titles on <PageHeader> (R22) via a new <InlineHeadline> primitive. Closes Spark's queue from the latest hand-off message. Full close-out at SPARK-FIXES-LOG.md Round 25.
Added
<DataTable rowTarget>+rowRelprops (R20). Exposes the underlying<a>'stargetandrelattributes for rows rendered as anchors. PassrowTarget="_blank"for "open every row in a new tab" (file-download tables, external-resource lists), or a(row) => "_blank" | "_self" | undefinedcallback for per-row targeting. WhenrowTargetresolves to"_blank"androwRelisn't explicitly set, the kit auto-injectsrel="noopener noreferrer"on those rows for the standard tabnabbing-protection pattern. Pre-1.23.0 consumers had to fall back toonRowClick+window.open(href, "_blank", "noopener,noreferrer"), which loses the cmd-click / middle-click / right-click "Open in new tab" affordances native anchors provide.<InlineHeadline>primitive (R22). Click-to-edit page-title primitive matching headline typography (28px / 600 / display face). Resting state is a<button>with the headline; click swaps to a styled<input>with the same metrics so there's no visual jolt. Enter commits, Escape cancels, blur commits. AsynconSavesupported (kit doesn't manage in-flight state). Documented atpackages/ui/docs/InlineHeadline.md. Listed in the kit-chrome carve-out (no chapter demo).<PageHeader onTitleSave>+titlePlaceholderprops (R22). WhenonTitleSaveis set, the title slot becomes click-to-edit via<InlineHeadline>automatically — eyebrow / icon / summary / actions all stay in their normal positions; only the title gains hover/click state. Requirestitleto be astring(the kit dev-warns and falls back to a non-editable title for that render if it isn't). The standalone<InlineHeadline>is the right reach when your page chrome doesn't fit<PageHeader>(custom hero layouts, modal titles, drawer headers).
CSS
- New
.inline-headlinefamily (~70 lines) —.inline-headline,.inline-headline-display,.inline-headline-text,.inline-headline-placeholder,.inline-headline-input, plus hover/focus/disabled/editing states and a 640px-and-below mobile rule that drops the headline to 22px (matching<PageHeader title>'s mobile reflow from v1.14.0 R18-8).
Notes
<DataTable>API surface changes are purely additive. Existing consumers withoutrowTargetsee no change — the resolved target staysundefined, anchor renders as a same-tab navigation. Therelauto-inject only fires when the consumer opts intorowTarget="_blank", and they can override with an explicitrowRel.<PageHeader>API surface change is purely additive. Existing consumers withoutonTitleSavesee no change — the title still renders as a static<h1>with the same icon / eyebrow / summary / actions chrome. The new editable path only fires whenonTitleSaveis set.<InlineHeadline>is'use client'(it ownsuseStatefor the editing mode + draft value, refs for the input, keyboard handlers).<PageHeader>itself stays declarative — whenonTitleSaveis set it composes<InlineHeadline>into the title slot, and React handles the client-boundary inheritance.- All gates green: typecheck, markup-equivalence (25/0/58), check:docs (346 snippets), CSS / UI / kit-site builds.
- CSS bundle: dist/magicblocks.css 559723 bytes (was 557087); tokens-only subpath unchanged.
- Pair with
@magicblocksai/[email protected].
Spark migration
After upgrading to @magicblocksai/[email protected] + @magicblocksai/[email protected]:
- Files tab on
apps/web/src/routes/ProjectDetailPage.tsx— theonRowClick+window.open(href, "_blank", "noopener,noreferrer")fallback can come out. Replace withrowHref={(f) => f.downloadUrl}+rowTarget="_blank". Cmd-click / middle-click / right-click affordances all return. apps/web/src/routes/TicketDetailPage.tsx— the local<SubjectEditor>workaround can come out. Replace with<PageHeader title={ticket.subject} onTitleSave={async (next) => { … }} eyebrow="Tickets" />. Or, if the ticket page renders its own custom hero chrome that doesn't fit<PageHeader>, use the standalone<InlineHeadline>primitive directly.apps/web/src/components/InlineHeadline.tsx— delete after the import-path swap from"../components/InlineHeadline"→"@magicblocksai/ui".apps/web/src/routes/IssueDetailPage.tsx/ContactDetailPage.tsx/CompanyDetailPage.tsx/DealDetailPage.tsx/ProjectDetailPage.tsx— passonTitleSaveto the<PageHeader>to enable click-to-edit. Same recipe as<TicketDetailPage>. Spark gets editable titles on every detail-page headline by adding one prop per page.
Round mapping
| spark-id | kit-id | what shipped | |---|---|---| | R20 (open) | R20 | <DataTable rowTarget> + rowRel props with _blank auto-rel injection | | R22 (open) | R22 | <InlineHeadline> primitive + <PageHeader onTitleSave> + titlePlaceholder |
Spark queue: empty
This round closes the last open kit-team item from Spark's most recent feedback. Open Spark items prior to this batch (R19-1, R21-1, R21-2, R21-3, R20, R22, R24) all shipped: R19-1 in v1.21.0, R21-1/2/3 in v1.22.0, R20 + R22 in this round (v1.23.0), R24 in v1.20.0. Whatever Spark files next is the next round.
[1.22.0] — 2026-05-08
Minor — Spark Round 24 (kit-side; R21-1 / R21-2 / R21-3 by Spark numbering). Three small detail-page UX fixes batched per the user's recommendation. All three were producing real visible bugs in Spark's running app — alignment broken on /tasks, missing empty states on every detail-page card with a portaled dialog or && short-circuit children, and warning-yellow / exclamation-mark icons making half the activity timeline read as "alert."
Fixed
<InboxRow selectable>desktop grid template (R21-1). Pre-1.22.0 the.inbox-rowbase rule setgrid-template-columns: 36px 1fr auto auto(4 children). Whenselectablewas on, a 5th leading checkbox child got prepended but no rule extended the grid template above the 480px breakpoint — children flowed into 4 columns, avatar grabbed all the slack, body / due / actions piled up on the right with a wide empty band between avatar and title. Fix: new.inbox-row.is-selectable { grid-template-columns: 22px 36px 1fr auto auto }rule alongside the base. The mobile equivalent (@media (max-width: 480px) .inbox-row.is-selectable { grid-template-columns: 22px 36px 1fr }) already existed; this is the missing desktop counterpart. Spark's only consumer was/tasks.<SectionCard emptyState>no longer suppressed by JSX&&short-circuit (R21-2). The old gate wasemptyState !== undefined && children === undefined, which only treated literalundefinedchildren as empty. JSX commonly producesfalse(from&&short-circuit),null,[…false…](arrays of all-falsy), or React elements (e.g. always-rendered portaled dialogs that don't actually paint into the card subtree) — none of which=== undefined. Result:<SectionCard emptyState={…}>{rows.length > 0 && <List/>}</SectionCard>never showed the empty state whenrowswas empty. Spark hit this on every detail-page Tasks / Subscriptions / Referred-customers card. Fix: newisChildrenEmpty(children)helper treatsnull | undefined | falseand arrays where every entry is itself empty as "empty." React elements, non-empty strings, numbers (incl.0), and arrays containing real content stay non-empty.<ActivityTimeline>icon styling (R21-3). Three sub-fixes: 1. See-through icon backgrounds — every per-type tint was alpha-channel rgba (var(--info-soft)/--accent-soft/--warning-soft/--success-soft/ acolor-mix(…, transparent)formeeting), and the kit's vertical rail line bled through every chip. Fix: opaquevar(--bg-paper)baseline on.act-iconplus the soft tint as alinear-gradientoverlay (background-image: linear-gradient(<tint>, <tint>)), so paper sits underneath everywhere. Themeetingrule'scolor-mix(in oklab, var(--ink) 8%, transparent)second-arg-of-transparentnow sits on opaque paper too. 2.noteretinted from warning-yellow (--warning-soft/--warning-text, which read as "⚠️ alert") to--accent-soft/--accent-text(brand pink — "this is content"). Notes aren't alerts. 3.customglyph swapped from a circle-with-exclamation (which read identical to<Banner tone="warning">) to a four-point sparkle.customis the catch-all type for every consumer-defined sub-type that doesn't map to email/conversation/meeting/note/stage-change — in Spark's app that'stask_*,ticket_*,stripe_*,mb_*,form_submitted,file_uploaded, etc. (most rows). The exclamation flagged every catch-all row as "needs attention."
Added
<ActivityItem icon>slot (R21-3, optional). Every variant of theActivityItemdiscriminated union now accepts an optionalicon?: ReactNodeprop that overrides the default per-typeglyph the kit ships inICON. Pass any 13×13ish SVG usingcurrentColorso it tints with the kit's per-type palette. Use it for richer per-row glyphs the kit's 6-type set doesn't cover natively (e.g.task → ✓,ticket → headset,stripe → coin). The CSS (.act-row[data-type="…"] .act-icon) still drives the chip background/border colour from thetypediscriminant — theiconprop only swaps the inner SVG. Spark's locallib/activity.tsxper-sub-type glyph workaround (a:has([data-spark-type^="task_"]) .act-icon::afterCSS injection) can come out in favour of passingiconto each item.
Notes
- No new props on
<SectionCard>or<InboxRow>. The fixes are purely internal — existing call sites work correctly without code change. - Spark workarounds can be ripped out. The scoped CSS block at the bottom of
apps/web/src/index.css(R21-1 + R21-3 overrides) and the three R21-2 codebase sweeps (move dialogs out of subtree /?:ternary / inline<SectionCard.Empty>) are no longer needed. The dialog-as-child trap auto-resolves;&&short-circuit works as users naturally write it; the:has()per-sub-type glyph injection becomes a first-classiconprop. - No breaking changes.
<ActivityItem>shape is byte-equivalent for callers who don't passicon. The<SectionCard emptyState>gate is a strict superset of the old gate — anything that fired pre-1.22.0 still fires. CSS rules updated in place; the.act-iconper-type rules now usebackground-imageinstead ofbackgroundto layer paper underneath, but the visible result for non-overlapping cases is identical. - Pair with
@magicblocksai/[email protected].
Spark migration
After upgrading to @magicblocksai/[email protected] + @magicblocksai/[email protected]:
apps/web/src/index.css— delete the scoped block that overrides.inbox-row.is-selectabledesktop grid (R21-1) and the per-.act-iconpaper-layer +note-tint + custom-glyph injection (R21-3). The kit owns those rules now and they'll fight the new defaults if left in place.apps/web/src/components/LinkedIssues.tsx— the move of<IssueDialog>out of the<SectionCard>subtree is no longer required (R21-2). Same for the?:ternary rewrites inSubscriptionsCard.tsx/ReferredCustomersCard.tsx/CustomerWorkDetailPage.tsx. Optional cleanup; the workarounds keep working.apps/web/src/lib/activity.tsx— replace the per-sub-type CSS injection withicon: <Glyph/>on eachActivityItemyou produce. Use the kit's existing icons (CheckIcon,HeadphonesIcon,CoinIcon,WandIcon, etc.) or any consumer-defined SVG.
Round mapping
| spark-id | kit-id | what shipped | |---|---|---| | R21-1 (open) | R21-1 | .inbox-row.is-selectable desktop grid template | | R21-2 (open) | R21-2 | isChildrenEmpty helper for <SectionCard emptyState> gate | | R21-3 (open) | R21-3 | Paper layer under .act-icon; note retint to accent-soft; sparkle custom glyph; new icon slot on ActivityItem |
Deferred from this round (queued)
- R20 —
<DataTable rowHref>notarget="_blank"exposure - R22 —
<InlineHeadline>for click-to-edit titles
Both ride in a "DataTable + PageHeader composability" round.
[1.21.0] — 2026-05-08
Minor — Spark Round 23 (kit-side; R19-1 by Spark numbering, P0). Fixes a real "live tables broken on mobile" regression from v1.14.0 R18-3. Singleton round — Spark's open queue (R20, R21-1/2/3, R22) is queued for separate dedicated rounds. Full close-out at SPARK-FIXES-LOG.md Round 23.
Fixed
<DataTable>priority-hidden columns no longer leave grid tracks behind (R19-1 / P0). Pre-1.21.0 the v1.14.0 R18-3 mobile rules hid cells withdisplay: none, but the underlyinggrid-template-columnstrack for the hidden column stayed allocated — a 7-column table with three tertiaries squashed its visible columns to ~5px wide on a phone. Spark hit this on/leads,/sequences, and/broadcastsand reverted the priority annotations as a workaround.
The fix: the React component now computes three grid-template-columns strings (one per priority tier — desktop / tablet / mobile) and exposes them as CSS custom properties (--data-table-cols-desktop, --data-table-cols-tablet, --data-table-cols-mobile) on every .data-table-row. The kit's media queries read the right variable at each breakpoint via var(), so the surviving columns expand into the freed space at every viewport. No inline-style precedence fight (variables get set inline, declarations live in CSS), no JS resize observer, no flicker on viewport changes.
Spark's workaround can come out: re-add priority: "tertiary" / "secondary" annotations on /leads, /sequences, and /broadcasts columns and the mobile layouts will collapse cleanly.
Notes
- Behavioural change scoped to mobile breakpoints only. Desktop (≥720px) renders identically to v1.20.0 — the desktop template still includes every column. Only at ≤720px (drop tertiary) and ≤520px (drop secondary as well) does the new computed template take effect.
- CSS-class / vanilla consumers consuming
.data-table-grid+.data-table-rowdirectly need to set the three custom properties inline themselves to get the priority-aware behaviour. The fallback ladder (var(--data-table-cols-mobile, var(--data-table-cols-tablet, var(--data-table-cols-desktop, 1fr)))) keeps the old behaviour intact for consumers who don't set them. - No new props or API changes on
<DataTable>. Existing call sites withpriorityannotations work correctly without any code change beyond upgrading. - The
display: noneon.data-table-cell[data-priority="tertiary"]/[data-priority="secondary"]rules stay — they're still useful for any custom row layout a consumer authors with the samedata-priorityconvention. The grid tracks are now correctly sized regardless. - Pair with
@magicblocksai/[email protected].
Deferred from this round (queued)
- R20 —
<DataTable rowHref>notarget="_blank"exposure - R21-1 —
<InboxRow selectable>desktop grid template missing the leading checkbox column - R21-2 —
<SectionCard emptyState>JSX&&short-circuit suppression - R21-3 —
<ActivityTimeline>icon tints (see-through, warning-yellow onnote, alarmingcustomglyph) - R22 —
<InlineHeadline>for click-to-edit titles
These ride in their own dedicated rounds. R21-1/2/3 will likely batch (all small detail-page UX fixes); R20 + R22 will likely batch as a "DataTable + PageHeader composability" round.
[1.20.0] — 2026-05-08
Minor — Spark Round R24 (file-type icon set + dispatcher). Closes the kit gap Spark filed in .scratch/components-to-fix-or-add.md while polishing the project Files tab — Spark hand-rolled an inline 7-glyph file-type SVG set in apps/web/src/components/FileTypeIcon.tsx, this round absorbs that surface into the kit so the local copy can be deleted. Full close-out at SPARK-FIXES-LOG.md Round 22.
Added
- Seven file-type icons in
icons.tsx—FileIcon,FileTextIcon,FileImageIcon,FilePdfIcon,FileVideoIcon,FileAudioIcon,FileArchiveIcon. All match the kit's standard icon spec (18×18 viewBox, 1.5 stroke,currentColor, no fill, round caps + joins) and share a "page with dog-ear corner fold" base so the family reads as cohesive — the inner mark is what distinguishes the type. All export from the package barrel and forwardIconPropslike every other kit icon. <FileTypeIcon contentType="…" />dispatcher — picks the right kit icon from a MIME / content-type string. Forwards every standard icon prop (size,className,aria-label, native SVG attrs) to the resolved icon. Spark's existing<FileTypeIcon contentType={file.content_type} size={18} />call sites are byte-equivalent after swapping the import path.getFileTypeIcon(contentType)helper — returns aComponentType<IconProps>for use inside generated lists,<DataTable>cell renderers, or anywhere else you need to capture the icon as a value first. The dispatcher above isgetFileTypeIcon(...)rendered.getFileTypeKind(contentType)helper — returns the bareFileTypeKindunion ("doc" | "text" | "image" | "pdf" | "video" | "audio" | "archive") for branching downstream of the icon (e.g. "preview button only for images and videos").FileTypeKind+FileTypeIconPropstypes — both exported from the package barrel for consumer typing.packages/ui/docs/FileTypeIcon.md— full doc with API reference, examples, the exact mapping rules, and the migration recipe for Spark's local file.IconFamily.mdgains a new "File-type set" subsection in the Catalogue.
Style exception (documented inline)
<FilePdfIcon>carries a typographic "PDF" label — 4.25px monospace text,stroke="none",fill="currentColor". The kit's icons are otherwise pure monoline outlines (no fills, no two-tone), but at 18×18 grid, PDF can't be visually distinguished from a generic document without either letters or fills. Spark's hand-rolled set already used the letters approach — the kit follows. The label tints withcurrentColorand scales cleanly withsizeso the icon stays consistent with the rest of the family visually. The exception is scoped to this one icon and documented inicons.tsxinline +FileTypeIcon.md§"Style note — the PDF letter exception".
Mapping rules
getFileTypeKind(contentType) runs in this order — first match wins:
1. Lowercase + trim. Falsy (null / undefined / "") → "doc". 2. image/* → "image" 3. application/pdf → "pdf" 4. video/* → "video" 5. audio/* → "audio" 6. (zip|tar|gzip|gtar|compressed|7z|rar|bzip|xz|lzma) regex match → "archive" 7. text/* OR JSON / XML / YAML / CSV / JSONL flavours → "text" 8. Default → "doc"
The set is deliberately small (7 kinds) — consumers needing richer treatment (a spreadsheet glyph, a slide-deck glyph, a "code file" variant) should add their own sibling icon using the same iconComponent factory pattern, or fall back to <FileIcon> plus a sibling label.
Notes
- Kit-chrome carve-out.
<FileTypeIcon>and the seven new individual icons all live inscripts/check-coverage.mjs'sKIT_CHROMEset under "1.20.0 — Spark Round R24". They're consumer-facing primitives without per-chapter demos; the per-component MD doc + theIconFamily.mdcatalogue subsection are the canonical reference. - No
'use client'. Both the dispatcher and every individual icon are purely declarative — render in Astro / Next RSC without a hydration boundary. - No CSS surface change. The icons render via
currentColorlike every other kit icon; no new classes in_shared.css.@magicblocksai/[email protected]is a lockstep no-source-change bump. - Pair with
@magicblocksai/[email protected].
Spark migration
After installing @magicblocksai/[email protected]:
```tsx // before import { FileTypeIcon } from "../components/FileTypeIcon"; <FileTypeIcon contentType={file.content_type} size={18} />
// after import { FileTypeIcon } from "@magicblocksai/ui"; <FileTypeIcon contentType={file.content_type} size={18} /> ```
Then rm apps/web/src/components/FileTypeIcon.tsx. The component prop shape (contentType, size, aria-label) is byte-equivalent to Spark's local interface — no other changes needed at call sites.
[1.19.0] — 2026-05-07
Minor — Website Round W3 (R006–R011). Six asks filed by the magicblocks-website team after first-pass adoption. Five ship with code/CSS/doc changes; one (R010 — animation controllers) ships docs-only with a clear roadmap. Two real bugs fixed (R007 + R008), one new token (R006 → --on-accent), one new prop (R011 → <PressStrip layout>), one runtime dev-warn (R009 → <EcosystemRings>), and three new docs (ReservedClassNames.md, Tokens.md, plus updates to HeroLiveDemo.md / ChatCompare.md / PressStrip.md). Full close-out at WEBSITE-FIXES-LOG.md Round W3.
Added
--on-accenttoken (W3-R006) — colour for text PLACED ON--accentbackground (primary CTAs, accent pills, etc.). Resolves tovar(--paper)(cream) in both light and dark modes — paper-on-pink reads at WCAG AA-large for the kit's accent in both themes. Distinct from--accent-textwhich means "accent-coloured text on a NEUTRAL background" (aliased to--accent). The two roles were collapsed into a single--accent-texttoken historically, which led consumers reading the name as text-on-accent (the convention in some other design systems) to ship invisible pink-on-pink CTAs. Documented atpackages/ui/docs/Tokens.md§"Accent vs on-accent". The kit's own.btn-primaryrule now usesvar(--on-accent, var(--paper))— fallback preserves back-compat with consumers on older@magicblocksai/cssreleases.--f-italictoken (W3-R008) — aliases tovar(--f-serif)(Fraunces). The kit's chapter rules (.pg-title em,.ctab-title em,.au-quote em,.cv-bubble em,.nf-title em,.hero-title em, and ~10 more) referencevar(--f-italic)paired withfont-variation-settings: "SOFT" 80for the Fraunces SOFT-80 italic-accent treatment. Pre-1.19.0 the token was undefined —<em>text fell back to whatever browser default italic was inherited (usually the body face, in italic). Trivial bugfix; rules already shipped, the token was just orphaned.<PressStrip layout>prop (W3-R011) —"wrap" | "scroll", default"wrap"(back-compat)."scroll"switches toflex-wrap: nowrapwith horizontal overflow-scroll and a soft right-edge fade (same idiom as<IndustryBar>mobile). Below 640px both variants wrap (horizontal scroll on phones is a footgun: users miss off-screen logos). For lineups of 8+ outlets at marketing widths,"scroll"reads as a "row of credibility"; the default wrapping reads as a logo wall. New.press-row.is-scrollCSS family in_shared.css.- Runtime dev-warn for
<EcosystemRings>empty labels (W3-R009) — whenprocess.env.NODE_ENV !== "production", the component checks eachmiddleLabels/outerLabelsentry and emits aconsole.warniflabelis missing, null, or empty. The warning names the offending ring + index + id and explicitly calls out the typo trap (the kit prop islabel, nottext). No production cost; no behavioural change. Shipped because Astro's loose JSX type-checking can let typo'd prop names through silently — the website team hit this on the homepage with<EcosystemRings middleLabels={[{ top, left, text: "Chat" }]}>, which produced invisible labels with no console signal. Type-level guard rails in theEcosystemRingLabelinterface + the per-prop JSDoc now also call out the trap. packages/ui/docs/ReservedClassNames.md— new doc listing every kit-reserved global class name (.hero,.section,.container,.page, plus the canonical component primitives). Consumers naming their own sections with these classes silently inherit the kit's chrome (overflow rules, surfaces, token rescopes) — this doc names the trap and recommends namespaced-class patterns (.home-hero,.product-hero, etc.). The.herorule in_shared.cssnow carries an inline⚠️comment pointing at this doc.packages/ui/docs/Tokens.md— new doc covering token-pair naming notes that are easy to misread. Currently covers "Accent vs on-accent" (W3-R006) and "Italic-accent face:--f-italic" (W3-R008). Will grow as new ambiguities are documented.
Documentation
HeroLiveDemo.md+ChatCompare.md— "Consumer integration — animation behaviour" sections (W3-R010) — clear answer to "the kit doesn't animate on consumer sites." Documents the freeze-frame end-state pattern (staticDisplay={true}default + noclient:*directive) as the kit's blessed integration pattern for consumer sites, with the rationale: the end-state still tells the full story, ships zero JS overhead, and works in Astro / Next RSC out of the box. Theautoplayprop / vanilla controller path is on the roadmap (W4 or later) — documented but not shipped this round, since it requires a'use client'boundary and a non-trivial controller implementation. Consumers needing autoplay sooner can open a GitHub issue to prioritise.PressStrip.md—layoutprop docs with a "When to uselayout='scroll'" recipe.
Bug fixes
.heroglobal class collision (W3-R007, doc-only fix). The kit's global.hero { overflow: hidden; padding: …; background: var(--warm-3); border-radius: var(--r-xl); isolation: isolate; }rule applies to chapter 09 narrative + the kit-site index. Consumers naming their own page sectionsclass="hero"silently inherit the chrome — includingoverflow: hidden, which clips legitimate consumer content (the website team hit this with their homepage's V2 proof bar). Fix: documented in the newReservedClassNames.md;_shared.csscarries an inline ⚠️ comment near the.herorule pointing at the doc. Renaming.hero→.kit-hero(the obvious "fix") would be a hard breaking change for every kit consumer + every chapter HTML demo, so the chosen path is documentation + a recommended namespaced-class pattern (.home-hero,.product-hero, etc.).--f-italicundefined (W3-R008) — see "Added" above.
Notes
- CSS bundle grows by ~30 lines (sentinel comments + the new
.press-row.is-scrollfamily). Tokens-only subpath unchanged in shape —--on-accentand--f-italicboth live above/* @TOKENS-END */and ship intokens.css. - No breaking changes. Every existing API is unchanged. The
<PressStrip>default stayslayout="wrap"; the<EcosystemRings>dev-warn doesn't fire in production;--accent-textkeeps its current value and meaning. - Pair with
@magicblocksai/[email protected].
W3 mapping
| website-id | kit-id | what shipped | |---|---|---| | R006 (bug) | W3-R006 | New --on-accent token + Tokens.md "Accent vs on-accent" doc | | R007 (bug) | W3-R007 | ReservedClassNames.md doc + ⚠️ comment in _shared.css near .hero | | R008 (bug) | W3-R008 | --f-italic: var(--f-serif) token added | | R009 (enhancement) | W3-R009 | Runtime dev-warn in <EcosystemRings> for empty labels + tightened JSDoc | | R010 (enhancement) | W3-R010 | Docs-only — freeze-frame canonical pattern in HeroLiveDemo.md + ChatCompare.md; autoplay prop on roadmap | | R011 (enhancement) | W3-R011 | <PressStrip layout="wrap" \| "scroll"> prop (default "wrap" for back-compat) |
Website migration
After installing @magicblocksai/[email protected] + @magicblocksai/[email protected]:
- Primary CTAs — drop the local
color: var(--paper)override on.site-nav__cta/.btn-primaryand rely on the kit default. The kit's.btn-primarynow resolves tocolor: var(--on-accent, var(--paper))— the on-accent token does the right thing automatically. <em>italic accents — drop the local--f-italicdefinition inBaseLayout.astro; the kit defines it now.<PressStrip>— passlayout="scroll"if you want the marquee-style row. The hand-rolled.press-row { flex-wrap: nowrap; … }override inBaseLayout.astroglobal styles can come out.- Homepage hero — the
.hero→.home-herorename you already shipped is the right pattern; keep it. The newReservedClassNames.mddoc backs that up. <EcosystemRings>middle/outer labels — already migrated to{ id, label, top, left }shape; no further change needed. The new dev-warn would have caught thetext:typo at runtime if it had landed sooner.
[1.18.0] — 2026-05-07
Minor — Website Round W2. Closes the two new-component asks the website team filed in W1 (R001 + R002) once the V2 spec was attached. Both ship as kit-chrome consumer-facing primitives (no chapter demo; per-component MD doc). Full close-out at WEBSITE-FIXES-LOG.md Round W2.
Added
<IndustryBar>(W2-R001) — slim full-width sub-nav strip beneath the topnav. Answers "is this for me?" in under half a second. N-agnostic — pass however many industries the consumer's sitemap calls for (the V2 spec drifted between 6 and 7 across different sections; the kit takes a list rather than picking a side). Two surface variants (warmdefault +ink);compactmodifier; mono / uppercase / letter-spaced 0.06em label styling per spec. Active item getsaria-current="page"+ accent dot + underline. Hover lifts text to--accentwith a 1px underline. Below 768px the strip becomes horizontally scrollable with a soft right-edge gradient cue. Custom Link component prop for client-side navigation (Next.js / React Router / TanStack — same shim pattern as<SectionCard LinkComponent>). Documented atpackages/ui/docs/IndustryBar.md.<CostCompare>(W2-R002) — pricing-page-hero side-by-side cost-comparison bars. Full-width muted bar (high) +ratio%-width accent bar (low) with optional mono caption between. Theratioprop (default 5, clamped to 1–100) lets the consumer scale the visual proportion precisely as the underlying numbers shift, so the bar widths stay honest to the math. The bars are decorative (aria-hidden); the cost values in the labels are the screen-reader surface. Documented atpackages/ui/docs/CostCompare.md.
CSS
- New
.industry-barfamily in_shared.css(~95 lines):.industry-bar,.industry-bar.is-ink,.industry-bar.is-compact,.industry-bar-track,.industry-bar-eyebrow,.industry-bar-list,.industry-bar-item,.industry-bar-sep,.industry-bar-link,.industry-bar-link.is-current,.industry-bar-link:focus-visible,.industry-bar-dot,.industry-bar-label. Mobile rules ≤768px shrink the row to 36px (30px compact), drop label size to 11px, and apply amask-imageright-edge fade for the horizontal scroll cue. Reduced-motion drops the colour transition. - New
.cost-comparefamily in_shared.css(~70 lines):.cost-compare,.cost-compare-row.is-high/.is-low,.cost-compare-meta,.cost-compare-label,.cost-compare-value,.cost-compare-unit,.cost-compare-bar.is-high/.is-low,.cost-compare-gap. The low-bar width comes from--cost-compare-ratio(custom property) — set inline by the React component or by hand on CSS-class consumers.
Notes
- Both components are kit-chrome carve-out. Consumer-facing primitives without per-chapter demos; per-component MD doc is the canonical reference. Listed in
scripts/check-coverage.mjsKIT_CHROMEset under "1.18.0 — Website Round W2". - Spec drift (industry count) handled by parameterisation, not adjudication. The website team's note flagged that V2 sitemap says 7 industries while the Component-1 anatomy diagram and responsive breakpoint note both say 6. Building for
N(consumer passes the list) makes both readings correct; the kit doesn't need to pick. - No 'use client'. Both components are purely declarative — no hooks, no events, no browser APIs. Render in Astro / Next RSC without a hydration boundary.
- CSS bundle grows by ~165 lines for the two new component families.
- Pair with
@magicblocksai/[email protected](the CSS package mirrors_shared.cssverbatim — every new rule lands there too).
W2 mapping
| website-id | kit-id | what shipped | |---|---|---| | R001 (P0) | W2-R001 | <IndustryBar> — N-agnostic, two surface variants, compact modifier, mobile scroll cue | | R002 (P1) | W2-R002 | <CostCompare> — high + low sides + ratio + gapLabel |
Website migration
After installing @magicblocksai/[email protected] + @magicblocksai/[email protected]:
src/components/nav/IndustryBar.astro— replace the Phase 0 static fallback with<IndustryBar industries={INDUSTRIES} currentSlug={activeSlug} />. The Astro recipe inIndustryBar.mdshows the URL-basedactiveSlugderivation./pricinghero — drop in<CostCompare>per the doc example. The 5% default ratio matches the V2 spec's "$2.50–$4.00 vs $30–$80" framing.- No need to ship a local port for either component now.
[1.17.0] — 2026-05-07
Minor — Website Round W1. First feedback round from the magicblocks-website build team (in addition to the existing Spark CRM stream). Five entries filed (R001–R005); two ship as docs in this round, one is partial-shipped via a tokens-only CSS subpath (see @magicblocksai/[email protected]), and two are deferred pending the V2 spec doc. Full close-out at WEBSITE-FIXES-LOG.md Round W1.
Documentation
Drawer.md: "Lazy-import on first interaction" recipe (W1-R004) — the canonical answer for budget-sensitive pages where<Drawer>is the rare-path mobile-menu. Two complete copy-paste recipes (Astro vanilla-script and Next.js / Remixlazy()form) plus a "trade-offs" sub-section covering first-tap latency, bundler chunking requirements, and when to skip the pattern. The CSS-only-drawer alternative (R004's option (a)) is deferred to a future round — would add a third drawer surface beyond React + CSS-class.Scoreboard.md: "Composition recipes" section (W1-R005) — answers the website team's.stress-scoreboardquestion with a "compose, don't extend" recipe.<Scoreboard tone="dark">already provides the war-room chrome; the consumer wraps the section with their own.stress-scoreboardclass for layout chrome and passes a stress-framedrowspayload. The kit will not ship a dedicated<StressScoreboard>for this — the new doc section also enumerates the three signals that *would* warrant a dedicated component (structurally different markup / different prop contract / different default behaviour) and confirms none apply here.
Notes
- Two consumer streams. This is the first round serving both Spark CRM (via
SPARK-FIXES-LOG.md) and the magicblocks-website (viaWEBSITE-FIXES-LOG.md). The kit'sCLAUDE.mdnow documents both consumer paths under a new "Downstream consumers" section. Conventions for cross-stream conflicts are written into the W1 close-out. - Deferred items requiring spec attachment. R001 (
<IndustryBar>) and R002 (<CostCompare>) are real new components blocked on_components-to-commission.mdComponents 1 + 8 from the website team's V2 doc. The website team's local mitigation is shipping a static fallback (R001) / deferring to Phase 1 (R002), so neither blocks their critical path. Will ship in a W2 round once the spec is attached. - No
'use client'/ barrel changes. All shipped items in this round are doc-only on the React side. The CSS package is the one that picks up actual code (the newtokens.cssbuild output). - Pair with
@magicblocksai/[email protected]for thetokenssubpath.
W1 mapping
| website-id | kit-id | what shipped | |---|---|---| | R001 (P0) | — | Deferred — needs _components-to-commission.md Component 1 spec | | R002 (P1) | — | Deferred — needs _components-to-commission.md Component 8 spec | | R003 (perf) | W1-R003 | Tokens-only subpath in @magicblocksai/css/tokens (4.9 KB min). Per-chapter splits deferred — PostCSS purge gives strictly better tree-shaking | | R004 (perf) | W1-R004 | Lazy-import recipe in Drawer.md (Astro + Next/Remix forms) | | R005 (docs) | W1-R005 | Composition recipe in Scoreboard.md; <Scoreboard tone="dark"> already covers the use case |
[1.16.0] — 2026-05-07
Minor — Spark Round 20 R20-1: structured <SectionCard> empty states. Singleton round shipping R20-1; further R20 items (if any) will land in a follow-up bump.
Added
<SectionCard emptyState>accepts a structured object (R20-1) — in addition to the existingReactNode(string / JSX) shape, the prop now accepts{ icon?, title?, description?, action? }and renders the canonical icon-chip · title · description · action stack inside the existing.section-card-emptywrapper. Sized for in-card density (40px icon chip, 14px title, 13px description, 44ch description cap) — distinct from the larger<EmptyStatePage>/ chapter-7.emptyfamily that's sized for full-page surfaces.<SectionCard.Empty>subcomponent (R20-1) — exposed as a static property on<SectionCard>(and as a standaloneSectionCardEmptyexport) for consumers who need to render the structured empty-state visual from inside a non-empty body slot. Same content slots as theemptyStateprop; identical DOM minus the outer.section-card-emptywrapper (the consumer owns positioning).SectionCardEmptyContentinterface exported from the package barrel for consumers who want to type their empty-state config separately from the JSX.
CSS
- New
.section-card-empty-contentwrapper plus four slot classes —.section-card-empty-icon,.section-card-empty-title,.section-card-empty-description,.section-card-empty-action— sized for in-card density. Dark-mode token swap on the icon-chip (--bg-sunkbackground + dimmed-fg colour). The bare-text path (children rendered directly inside.section-card-empty) is unchanged.
Notes
- No breaking change. The
emptyStateprop's existingReactNodeshape is preserved verbatim; the union widens via TypeScript's narrowing (plain objects aren't validReactNodes, so the structured shape resolves cleanly without ambiguity). Runtime detection uses React'sisValidElement— a non-array, non-element object is the structured shape. <SectionCard.Empty>is attached viaObject.assignso<SectionCard>retains itsforwardRefexotic type while picking up the static property. Both<SectionCard.Empty …>and<SectionCardEmpty …>work; pick whichever you find more readable.- Both packages bump lockstep —
@magicblocksai/[email protected]ships the new slot CSS.
Spark migration
After installing @magicblocksai/[email protected] + @magicblocksai/[email protected]:
- For sections that should render a structured empty state when there's nothing to show, swap any local hand-rolled empty-state markup for
emptyState={{ icon, title, description, action }}. - For sections that need the empty-state visual *inside* a populated body (LinkedTasks etc.), render
<SectionCard.Empty …>directly inside the body slot. Drop the per-consumer hand-roll.
[1.15.0] — 2026-05-06
Minor — Spark Round 19 R19-2: <SectionCard padded> prop with default-flip. Singleton round (R19-1 still pending from Spark; shipping R19-2 standalone since the change is mechanical and Spark's adoption is blocked on it).
Added
<SectionCard padded>prop (R19-2) —boolean, defaulttrue. Pads the body region withvar(--s-4) var(--s-5). Defaults toward the more common case (free-form prose / KV pairs / form fields). Passpadded={false}for list-shaped children (<Inbox>,<Checklist>,<DataTable>,<Calendar>,<Panel>) that paint their own edge-to-edge rows. Replaces the deprecatedpadBodyprop, which had the inverse default and forced every consumer to wrap free-form bodies in a padded<div>.
Deprecated
padBodyprop on<SectionCard>— kept as an alias for back-compat. When set explicitly, takes precedence overpaddedso existing call-sites are byte-equivalent until you touch them. The TypeScript prop carries@deprecatedso editor hovers surface the rename. Will be removed in v2.0.0.
Behavioural change (consumer-visible)
<SectionCard>body padding default flipped from off → on. Existing call-sites that relied on the default flush behaviour (i.e. didn't passpadBodyand expected flush) now render padded. Migration is one prop addition per call-site:
| Old call (v1.14.x) | New call (v1.15.0) | |---|---| | <SectionCard>…flush list…</SectionCard> | <SectionCard padded={false}>…flush list…</SectionCard> | | <SectionCard padBody>…prose…</SectionCard> | <SectionCard>…prose…</SectionCard> (drop the prop) | | <SectionCard padBody={false}>…</SectionCard> | <SectionCard padded={false}>…</SectionCard> |
Call-sites still passing padBody keep their behaviour unchanged (the deprecated alias wins when explicitly set), so the migration can be incremental.
Notes
- No CSS change. The
.section-card-body.is-paddedmodifier already exists in_shared.css; this round only changes the TSX default to emit it. CSS-class consumers (@magicblocksai/css) see no behaviour change — they still opt in to padding by adding.is-paddedon the body. - The
SectionCard.mddoc updates the existing examples to showpadded={false}on the list / table / chrome-only patterns and adds a "Migration frompadBody" section. - Pair with
@magicblocksai/[email protected](lockstep version bump only — no CSS source change).
Spark migration
- Sweep
<SectionCard>…<Inbox|Checklist|DataTable|Calendar|Panel>…</SectionCard>call-sites and addpadded={false}. Or grep forpadBodyand rename topadded(with the inverse value if you were usingpadBody={false}→ drop entirely). - Where you were wrapping free-form bodies in
<div style={{ padding: 16 }}>to compensate for the old default — drop the wrapper, the kit handles it now.
[1.14.0] — 2026-05-06
Minor — Spark Round 18 mobile audit. 10-item sweep filed by Spark in .scratch/mobile-audit-kit.md; the kit ships all 10 in this release. Two were already shipped pre-round (verified — see Notes). Three P0s + five P1s + two P2s — see "Round 18 mapping" below for spark-id ↔ kit-id correspondence.
Added
<SortableHandle>primitive (R18-1, P0) — drag-grip + secondary-action affordance for sortable cards / rows. Composes withuseSortablefrom the kit's primitive layer. Visible at rest on touch (@media (pointer: coarse)); fades in on:hover/:focus-withinof the parent row on mouse devices. Single action → direct icon button; 2+ actions →…trigger + popover menu (Esc + outside-click dismissal). Ships because every consumer who builds a hover-reveal handle and forgets the touch case ends up with an invisible drag affordance on mobile — Spark hit this exact bug on/overviewcards in the round prior to filing this audit. Documented atpackages/ui/docs/SortableHandle.md. Listed in the kit-chrome carve-out (no chapter demo).<Kanban mobileLayout>prop (R18-2, P0) —"scroll"(default) |"stack". New default at ≤720px is horizontal flex withscroll-snap-type: x mandatoryso columns retain their pipeline shape and snap one at a time on phones. Behavioural change: prior to v1.14.0 the kit collapsed.kanbanto a single column below 520px, which (per Spark) "destroys the pipeline metaphor" on the most-used screens (Deals, Onboarding). The"stack"opt-in preserves the old single-column behaviour for triage kanbans where stage order is informational rather than the visual metaphor.DataTableColumn.priorityfield (R18-3, P0) —"primary" | "secondary" | "tertiary"(default"primary"). Tertiaries hide ≤720px; secondaries hide ≤520px. Wired into header / skeleton / row cells viadata-priority. Below 720px the table also picks up a gradient-mask scroll cue (mask-image: linear-gradient(...)) on the right edge so users can see there's more content off-screen. Hide is implemented viadisplay: nonerather than dynamic grid-template recalc (documented cosmetic-gap caveat for tables with very wide tertiary columns).
Changed
- 44pt tap-target sweep on touch devices (R18-4, P1) — under
@media (pointer: coarse), the following compact controls now meet Apple's 44pt / Android's 48dp minimum:.btn-sm,.btn-icon-sm,.modal-close,.drawer-close,.section-card-action,.section-card-count,.dropdown-menu-item,.menu-item,.inbox-row .ix-action,.demo-tabs button. Implemented via padding + negative-margin so the visual size on mouse stays unchanged. - Modal mobile keyboard handling (R18-5, P1) — the
.modalpanel becomes aflexcolumn capped atcalc(100dvh - var(--s-5) * 2)(using dynamic viewport height so iOS Safari's collapsing toolbar doesn't push the bottom of long forms below the soft keyboard). The body slot scrolls independently (overflow-y: auto+-webkit-overflow-scrolling: touch). Below 600px the footer becomesposition: sticky; bottom: 0withbackground: var(--bg-sunken)so the primary action stays reachable inside long forms. - Drawer head shrink on mobile (R18-6, P1) — below 600px,
.drawer-headvertical padding shrinks and.drawer-titledrops to 18px so more body content is visible above the fold. - PageHeader mobile reflow (R18-8, P1) — at ≤640px the text block (eyebrow + title + summary) takes
flex-basis: 100%and the summary loses itsmax-width: 56chcap so long summaries don't crop. The actions container also takesflex-basis: 100%; child action buttons getflex: 1 1 autoso two buttons divide the row evenly. - InboxRow mobile chip stacking (R18-9, P2) — below 480px the row reflows from a five-column grid to a two-row stack (avatar/checkbox + title in row 1; body + due + chips in row 2). Compact density variant follows the same rule.
Notes
- R18-7 (P1, popover viewport clamp) — already shipped pre-round.
usePopoveralready clamps to viewport with an 8px gutter on both axes (packages/ui/src/lib/usePopover.ts:149). Verified, no change needed. - R18-10 (P2, RichTextEditor
<img>max-width) — already shipped pre-round..rte-content img { max-width: 100%; height: auto }already exists in_shared.css. Verified, no change needed. - All mobile rules use
@media (max-width: ...)or@media (pointer: coarse)— they don't fire on desktop and don't change desktop visuals. dvh(dynamic viewport height) requires Safari 15.4+ / Chrome 108+ / Firefox 101+. Falls back tovhsemantics on older browsers (no JS shim required).- Pair with
@magicblocksai/[email protected](the CSS package mirrors_shared.cssverbatim — every mobile rule lands there too).
Round 18 mapping
| spark-id | kit-id | what shipped | |---|---|---| | R18-1 (P0) | R18-1 | <SortableHandle> primitive + (pointer: coarse) always-visible CSS family | | R18-2 (P0) | R18-2 | <Kanban mobileLayout="scroll"> default + data-mobile-layout="stack" opt-in | | R18-3 (P0) | R18-3 | DataTableColumn.priority + gradient-mask scroll cue | | R18-4 (P1) | R18-4 | 44pt tap-target sweep on (pointer: coarse) | | R18-5 (P1) | R18-5 | Modal dvh cap + body overflow + sticky foot | | R18-6 (P1) | R18-6 | Drawer head shrink ≤600px | | R18-7 (P1) | — | Already shipped pre-round (verified usePopover) | | R18-8 (P1) | R18-8 | PageHeader text-block + actions reflow ≤640px | | R18-9 (P2) | R18-9 | InboxRow chip stacking ≤480px | | R18-10 (P2) | — | Already shipped pre-round (verified .rte-content img) |
Spark migration
- Delete any local hover-reveal
.row-handle/DragHandleshadow; swap to<SortableHandle id={card.id} actions={[…]} />. - For pipeline kanbans (Deals, Onboarding) — no migration needed (new default matches the desired behaviour). For triage kanbans where the old single-column stack was wanted, add
<Kanban mobileLayout="stack">. - For DataTable consumers — annotate columns with
priority: "tertiary"for "nice to have on desktop, hide on phone" andpriority: "secondary"for "OK on tablet, hide on phone." Defaults to"primary"(always visible) so existing tables keep working. - Delete any local mobile-only overrides for
.modal-foot { position: sticky }/.drawer-head { padding-y: smaller }/ inbox-row chip stacking — kit owns those now.
[1.13.4] — 2026-05-06
Patch — docs-only correction to v1.13.3's R17-4 close-out (Spark Round 17 follow-up).
Documentation
LifecyclePill.md: "When to outgrow this" section — same pattern asRichTextEditor.md's outgrow section. Lists the four signals that mean a single-pill primitive isn't enough (phase-bucketed display / canonical phase model / multiple display modes per stage /PHASE · Sub-stagechip rendering) and points at Spark's<LifecycleStrip>+ phase model inapps/web/src/lib/lifecycle.tsas the canonical reference. Future kit consumers reading the doc find the limit-line and the proven pattern without rediscovering them from scratch.SPARK-FIXES-LOG.mdRound 17 close-out corrected. v1.13.3's note told Spark to delete their local<LifecyclePill>shadow now that the kit's union accepts the post-sale stages. That instruction was wrong. Spark replied: the shadow's role changed between R17-1 being filed and being shipped. Pre-R17 it existed as a type-union workaround; post-R17 it's a thin wrapper around<LifecycleStrip mode="inline">rendering the unifiedPHASE · Sub-stagechip ("CUSTOMER · Success Sprint") that landed in their lifecycle-unification commit. Deleting it would regress the phase-bucketed display they just shipped. The R17-4 row + a new "Why the shadow stays" subsection now reflect the actual close-out.
Re-classification (no code change)
- Option 3 (kit-side
<LifecycleStrip>phase primitive) moves from "deferred for a third consumer" to "deferred — Spark consumer ready, awaiting kit absorption." My v1.13.3 framing was wrong on two counts: (1) Spark IS the second consumer (they built the local primitive ahead of the kit), not a future one; (2) the phase-bucketed display is a real production pattern in their lifecycle-unification commit, not a hypothetical. The kit may absorb<LifecycleStrip>if a second consumer beyond Spark hits the same wall. Until then, Spark'sapps/web/src/components/LifecycleStrip.tsx+ the phase model inapps/web/src/lib/lifecycle.tsis the canonical reference.
Notes
- Pair with
@magicblocksai/[email protected](no source change in CSS; lockstep version bump only). - No React API changes. No CSS changes. Documentation correction round.
[1.13.3] — 2026-05-06
Patch — chapter-11 narrative fixes + Spark Round 17 R17-1 (LifecyclePill widening).
Added
- 9 post-sale lifecycle stages on
LifecycleValue(R17-1) —qualified,discovery,proposal,contracted,onboarding,success_sprint,expansion,churn,winback. Pre-1.13.3 the union was HubSpot-funnel-only (lead/prospect/mql/sql/customer/user/churned/disqualified), which forced B2B CRM consumers with operational customer-success surfaces to shadow the kit's<LifecyclePill>with their own. Each new stage maps to one of the kit's existing badge / pill tints — no new tints introduced. Spark's localLifecyclePillshadow can be deleted on adoption.
This is Option 1 of Spark's three proposed paths (widen the union vs. make generic vs. ship a <LifecycleStrip> phase primitive). The cheaper additive widen lands now; if a third consumer needs vocabulary that doesn't fit, the generic / phase-primitive options remain on the deferred list.
Fixed (chapter 11 narrative components)
.dormant-mine"last touch" cell stayed at the cold value (e.g."47 days") even when the row's pill animated through Sent → Replied. Visually contradictory: a row showing "Replied" claiming "47 days last touch". Added a stacked-spans pattern matching the existing.dm-pill-lblshape — cold label fades out at 34% of the 14s loop and"0 days"fades in until the cycle restarts. Per-row--ikeeps the new column in lock-step with the pill animation.prefers-reduced-motion: reducesnapshots the end state ("0 days")..stress-scoreboardnon-MagicBlocks row was a flat wall of red which read as "100% fail" instead of "60% complete, 40% lost". Replaced the blanket.is-fail span { background: var(--error) }rule with per-dot[data-state="pass"|"fail"]selectors set by the inline JS. Now both rows show a green/red mix proportional to the actual stat: MagicBlocks row is 24 green + 1 red (97.5% pass), Single-prompt row is 15 green + 10 red (60% pass with the existing fade-tail opacity). Updated the snippet tabs (HTML / CSS / React) to stay in sync.
Resolved (no kit code change)
- §11.14
<RevenueCalculator>"not showing on the site" — diagnosed as a stale deployedkit-islands.jsbundle missingRevenueCalculatorfrom its registry. Local source has the registry binding (RevenueCalculator: Acin the minified bundle); the deployed bundle from a prior commit didn't. Shipping this patch triggers Cloudflare's auto-deploy + rebuilds the islands bundle, which restores hydration. Visitors were seeing the static fallback markup with"—"placeholder outputs — non-interactive but not broken.
Notes
- Pair with
@magicblocksai/[email protected]. - The chapter-11 fixes are visible immediately on the kit-site after redeploy. The lifecycle widening is purely additive — no consumer-visible default change.
[1.13.2] — 2026-05-06
Patch — Spark Round 15 follow-ups (R15-4 + R15-5).
Added
<SectionCard icon>slot (R15-5) — leading icon cell rendered in the header band before the title. Pass any 14–18px SVG; the kit colours it--fg-dimso it reads as a quiet leading marker. Vertical-aligned with the title + count chip via the band'salign-items: center. Mirrors the<PageHeader icon>slot's contract. Default-variant only — the hero variant usesavatarinstead. Documented inpackages/ui/docs/SectionCard.md.
Fixed
- R15-4:
<Modal size>prop was a silent no-op — the React component already setdata-sizeon the root and the backdrop, but@magicblocksai/cssshipped no.modal[data-size="…"]selectors, so every modal rendered at the bare 460px default regardless of what consumers passed. Added the four size variants with viewport-min()guards so small-screen modals hug the edges instead of overflowing horizontally: -sm→min(380px, calc(100vw - var(--s-5) * 2))-md→min(560px, calc(100vw - var(--s-5) * 2))← default size, was effectively 460px -lg→min(720px, calc(100vw - var(--s-5) * 2))-xl→min(960px, calc(100vw - var(--s-5) * 2))
Visible default change: any consumer passing <Modal size="md"> (or omitting the prop, since "md" is the default) jumps from the silent 460px to 560px. Consumers passing lg / xl jump from 460px → 720 / 960. The bare .modal { max-width: 460px } rule stays as a fallback for raw-HTML consumers using <div class="modal"> without a data-size attribute. Spark's local override block can be removed.
Notes
- Pair with
@magicblocksai/[email protected]. - The Modal default-size jump (460 → 560px) is the only consumer-visible change. Modals that genuinely should stay narrow (confirm dialogs, simple prompts) should pass
<Modal size="sm">to land at 380px.
[1.13.1] — 2026-05-06
Patch — Spark Round 15 (token alias + docs).
Added
--font-sansTailwind theme alias (R15-3) — bothtailwind.preset.cjs(v3) andtheme.css(v4) now exposesansas an alias for the kit's body face, sofont-sansutility classes resolve to--f-bodyfor muscle-memory consumers. Pairs with the new--f-sansCSS custom-property alias in@magicblocksai/[email protected].
Documentation
RichTextEditor.md: "When to outgrow this" section (R15-1). The kit's source comment foreshadowed a future TipTap engine swap; Spark hit that wall, evaluated the trade-off, and stayed local. The doc now explicitly documents the limits of the kit's contenteditable + execCommand engine, the four signals that mean you've outgrown it (mention atoms, inline media nodes, schema-driven validation, slash-command palettes), and a reference for how Spark built<SparkEditor>on TipTap 3.x as a consumer-side wrapper. The kit may absorb a TipTap-based<RichTextEditorPro>(or rev<RichTextEditor>to v2 with engine config) once a second consumer hits the same wall — until then the engine swap stays consumer-side because the ~140KB gzipped bundle cost shouldn't fall on every kit consumer.INSTALL-FROM-NPM.md: explicit pointer to the CSS variables reference (R15-3). Adds a "CSS variables — single source of truth" callout pointing atbrand.magicblocks.ai/tokens#variables(filterable list of every CSS custom property the kit declares). Spark scanned the npm exports and missed the website page; the explicit pointer should prevent the same trip-up for future consumers.
Notes
- Pair with
@magicblocksai/[email protected]. - No React API surface changes — patch release.
- R15-2 (
<DenseRow>) explicitly carried forward as deferred per Spark's note ("local row layout there is stable so this stays deferred until another consumer appears").
[1.13.0] — 2026-05-05
Spark CRM Round 14 — bulk-selection on <InboxRow> + new <InlineDateField> primitive.
Added
<InboxRow selectable selected onSelectChange>(R14-1) — first-class bulk-selection slot on the kit's existing inbox row. Renders a leading 16px checkbox column. The kit handles the click target (clicks on the checkbox don't fireonOpen), the row tinting (.is-selectedadds--accent-softbackground), and the keyboard model (Spacetoggles selection instead of opening whenselectable;Enteralways opens).onSelectChange(next, shiftKey, id)passes the Shift modifier so consumers can implement Linear / GitHub-style range-select against their own "last anchor" id. Documented inpackages/ui/docs/Inbox.md.
Replaces the consumer-side wrap-with-sibling-checkbox pattern (Spark's apps/web/src/routes/TasksPage.tsx had a 32px-left grid + sibling <input type="checkbox"> outside the row's interactive surface). Both Spark's Todos page + project tasks list collapse to the kit prop on adoption.
<InlineDateField>(R14-2) — edit-in-place date field for detail-page rails. Click the resting label → swap to a native<input type="date">; Enter / blur commits, Escape cancels. Default smart relative formatter ("Today","in 3d","3d late") with tone-driven colour (warning today, error overdue).formatprop overrides the formatter;clearable={false}for required-date fields. Documented atpackages/ui/docs/InlineDateField.md.
Replaces the local DueDateField pattern Spark was carrying in IssueDetailPage.tsx and the equivalent "click-to-edit a date in a rail" pattern other consumers were rolling. Visual + keyboard contract matches Linear / Notion / Asana inline date fields.
Notes
- Pair with
@magicblocksai/[email protected](_shared.cssmirror picks up the.ix-selectfamily + selected-row tint + the.inline-date-fieldfamily). <InlineDateField>joins the kit-chrome carve-out (no per-component chapter demo; documented atpackages/ui/docs/InlineDateField.md).<InboxRow>selection is additive — existing consumers withoutselectablesee no change. TheSpace-keypress behaviour change (toggle selection vs. open) is gated onselectablebeing truthy.
[1.12.0] — 2026-05-05
Spark CRM Round 13 — biggest single release since v1.10.0. 9 items across two of Spark's local rounds (R11-2/3/4 + R12-1/2/3/4/5/6).
Added
<MultiSelect>(R12-3) — chip-rendered multi-value selector with searchable popover. Backspace at empty input removes last chip.closeOnSelect: falseby default (multi-pick is iterative). OptionalmaxChipsfor overflow+Nchips. Works sync OR async viaonSearchChangefor server-side option resolution. Documented atpackages/ui/docs/MultiSelect.md. Replaces consumer-side comma-separated<Input>fallbacks for multiselect custom-field types.<PageHeader>(R12-4) — canonical "eyebrow · title · summary · actions" rhythm for product-app routes. Bakes the type scale, gap, mobile wrap behaviour, and the icon-in-h1 pattern.levelprop (1/2/3) for nested chrome. Documented atpackages/ui/docs/PageHeader.md. Replaces ~38 hand-rolled.spark-page-headinstances across Spark's routes.<MiniCardLink>+<MiniCardLinkList>+<MiniCardMeta>(R12-5) — linked-entity row pattern for<SectionCard>body slots (companies on a contact page, tickets on a deal page). Polymorphic root:<a>whento,<button>whenonClick,<div>otherwise.LinkComponentfor SPA routing. Documented atpackages/ui/docs/MiniCardLink.md.<AgendaList>+<AgendaRow>+<AgendaTitle>+<AgendaMeta>+<DateBand>(R12-6) — time-grouped list for calendar / upcoming / agenda surfaces. Generic<AgendaList<T>>withgroups+renderItem;<AgendaRow>is the canonical row (time-prefix · body · meta) with the same polymorphic root pattern as<MiniCardLink>.<DateBand>is the standalone date-band heading — usable above any list. Documented atpackages/ui/docs/AgendaList.md. The<DateBand>upstream from the kit's deferred-candidate flag.<AppShell mobileNavOpen>+onMobileNavOpenChange+drawerWidth(R12-2) — opt-in slide-in drawer for the sidebar at the mobile breakpoint. Bakes the slide animation, scrim overlay, and--app-shell-drawer-wtoken. Consumer-controlled state (useState+ hamburger click). Auto-close on route change + Esc + scroll-lock are documented as a tiny consumer-side hook composinguseEscapeKey+useScrollLockfrom the kit's lib/. Documented in the existingpackages/ui/docs/AppShell.md(TODO removed).<DashboardNavGroup forceOpen>(R11-4) — override the persisted state without disturbing it. The canonical pattern: detect "this group contains the currently-active route" router-side, lift it asforceOpen. Manual toggle clicks no-op while forced. WhenforceOpenflips back to false, the group returns to the user's last toggled preference. Documented inDashboardNavGroup.md.<Inbox banded>prop +.inbox.inbox-bandedCSS (R11-3) — opt-in row alternation for long inbox lists. Hover state unchanged.<InboxRow secondaryActionVisibility>(R12-1) —"always"(default — quiet at rest, lifts on hover) or"hover"(legacy Gmail-style hover-only). Pre-1.12.0 the secondary-action button was effectively invisible at rest — un-discoverable on touch and on first scan.<Avatar tone>prop (R11-2) —"neutral"(default) /"accent"/"info"/"success"/"warning"/"error". Backed by new.av-tone-*CSS modifiers.
Changed
.avships with a default fill (R11-2) — visible to consumers. Pre-1.12.0.avwas background-less; on a same-tone page wash (e.g.--warm-1) initials rendered invisible. Default nowbackground: var(--bg-warm); color: var(--fg-soft). Image children clip viaoverflow: hiddenso this fill is invisible behind avatars rendered as<img>. Five.av-tone-*modifiers ship alongside..inbox-row .ix-actionrest state (R12-1) — visible to consumers. Wasopacity: 1but background-less + transparent border (effectively invisible at rest). Nowcolor: var(--fg-faint); opacity: 0.7at rest, lifts toopacity: 1on hover. Opt back into the legacy hover-only behaviour via the newsecondaryActionVisibility="hover"prop or the.inbox.inbox-actions-on-hoverclass.
Notes
- Pair with
@magicblocksai/[email protected]. - All five new components join the kit-chrome carve-out (no per-component chapter demos; all documented at
packages/ui/docs/). - Two consumer-visible visual changes — both opt-out via class or prop. Marketing demos using bare
.avor.ix-actionshould re-check on upgrade; product apps benefit immediately.
[1.11.1] — 2026-05-05
Patch — Spark Round 12.
Fixed
- R12-1:
<ActivityTimeline>rail extended past day boundaries. The.act-row::before1px hairline paintedtop: 0; bottom: 0on every row. On the first row of each day group, thetop: 0stub extended UP into the row's top padding, visually bridging the gap to the previous day's last-row stub — the rail looked continuous past the date divider. Added.act-divider + .act-row::before { top: 50% }so the rail starts at the icon centre on the first row of each day. Pairs with the existing:last-child::before { bottom: 50% }so the rail terminates cleanly at both ends of each day group.
Notes
- Pair with
@magicblocksai/[email protected](_shared.cssmirror picks up the one-line CSS patch). - Spark hot-patched locally in
apps/web/src/index.css— that override can be removed on adoption.
[1.11.0] — 2026-05-05
Spark CRM Round 11 — collapsible nav-group wrapper for long product-app sidebars.
Added
<DashboardNavGroup>(R11-1) — wraps a.dash-nav-labelheading + a stack of.dash-nav-itemrows in a<details>element so the section can be collapsed by clicking the heading. Built on native<details>/<summary>so the no-JS path works in SSR / printable / no-script environments. OptionallocalStoragepersistence keeps the open state across reloads (persistKey="crm.nav.today"). Controlled mode viaopen+onOpenChangefor "collapse all" header buttons or cross-sidebar sync. Documented atpackages/ui/docs/DashboardNavGroup.md.
Long sidebars (CRMs, product apps with many sub-areas) get hard to scan past ~10 rows. Linear / Slack / Notion all let users collapse sections — <DashboardNavGroup> ships that pattern as a kit primitive so consumers stop reaching for raw <details> + custom CSS.
Lazy useState initialiser reads localStorage synchronously on first client render, so the group doesn't flash open-then-close (or vice versa) during hydration. SSR-safe: returns defaultOpen on the server, reads localStorage on the client during the first render.
Pure-CSS markup also works without React: ``html <details class="dash-nav-group" open> <summary class="dash-nav-label">Today</summary> <a class="dash-nav-item" href="/overview">Overview</a> … </details> ``
Notes
- Pair with
@magicblocksai/[email protected](_shared.cssmirror picks up the.dash-nav-groupfamily). - Joins the kit-chrome carve-out (no per-component chapter demo; consumer-facing CRM primitive).
- Mixing
<DashboardNavGroup>with bare.dash-nav-labels in the same sidebar is fully supported — only the sections you want collapsible need to be wrapped.
[1.10.0] — 2026-05-04
Spark CRM Round 10 — two new CRM-canonical primitives.
Added
<MentionInput>(R10-1) — async@-mention textarea. Type the trigger char (default@) → popover anchored below the textarea (Linear / GitHub pattern, no caret-pixel measurement) showing items resolved by yoursearch(query)callback. Sync return for static lists,Promisefor async APIs. Inserts@Labelmarkers into the body;extractMentions(body)recovers the ids at save time. Configurabletriggerchar (use#for channels,/for slash commands),formatMentionfor custom marker shapes,onMention(item)for tracking which entities were mentioned.Cmd/Ctrl+EnterfiresonSubmit. Documented atpackages/ui/docs/MentionInput.md. Replaces consumer-side<MentionTextarea>workarounds.
<EmailThread>(R10-2) — full email viewer (NOT chat bubbles). Per-message header with sender + Details toggle (To / Cc / Bcc / Date dropdown). Latest message expanded by default; older messages collapsed inline as one-line summaries; click to expand. Quoted history (<blockquote>, Gmail'sgmail_quotediv, "On … wrote:" pattern) auto-collapses behind a "Show trimmed content" chevron. Attachment chips at the bottom of each body. Optionalopens/clicks/repliedtracking icons next to the timestamp. Reply / Reply all / Forward action bar viaonReply/onReplyAll/onForwardprops;renderComposer(latest)slot for an inline reply composer. Documented atpackages/ui/docs/EmailThread.md.
Security: the html field is rendered via dangerouslySetInnerHTML — pre-sanitise on the consumer side (DOMPurify, sanitize-html, etc). The kit is intentionally agnostic about which sanitiser you use. Documented prominently in EmailThread.md.
Use <EmailThread> for email; use <ConversationPreview> / <MessageBubble> for chat-style UI. Email reads as paragraph text from a named sender at a fixed time, with quoted history and attachments — chat-bubble UI applied to email reads as messaging, not as mail. Gmail / Front / Spark / Linear all draw the same line.
Notes
- Pair with
@magicblocksai/[email protected](_shared.cssmirror picks up the new.mention-input+.mention-picker*family and the.email-thread+.email-message*family). - Both new components join the kit-chrome carve-out (no per-component chapter demo; verified via the kit-site Astro build +
pnpm check:docs). - Both
'use client'(stateful — picker open/highlight/async items for MentionInput; per-message expand/collapse + details toggle + quoted-history toggle for EmailThread).
[1.9.3] — 2026-05-04
Patch — Spark Round 9. Two CSS-only fixes for the SectionCard + Inbox composition.
Fixed
- R9-1: nested list-root chrome doubled up inside
<SectionCard>. When<Inbox>(or<Checklist>,<DataTable>,<Calendar>,<Panel>) sits directly inside<SectionCard>'s body, the inner primitive's own border + radius + paper bg painted on top of the SectionCard's identical chrome. Added a direct-child reset (section-card-body > .inbox, etc.) that strips border / radius / background / overflow / shadow from nested list-roots. Generalises to every "list-root" primitive consumers commonly nest. Direct-child only, so deeper-nested primitives that legitimately want their own chrome stay untouched. - R9-2:
<InboxRow state="overdue">red dot was clipped + bled through the.inboxborder-radius. Replaced the 6px::beforedot atleft: 0with a Linear-style 3px left-rail accent (box-shadow: inset 3px 0 0 var(--error)). No layout shift, the parent.inbox'soverflow: hiddenclips the rail cleanly at the rounded corners, and "needs attention" rows read at-a-glance from across the screen in a way the small dot never did.
Notes
- Pair with
@magicblocksai/[email protected](_shared.cssmirror). - All consumers using
<SectionCard>+<Inbox>together (or<Inbox state="overdue">rows) should bump.
[1.9.2] — 2026-05-03
Patch — Spark Round 8.
Fixed
- R8-1: kit
<Button>in the<SectionCard>action slot pushed the header band ~21px taller. The default.btnis 38.5px tall vs the band's natural ~17px text height; withalign-items: centerthe band grew to fit. Added a scoped reset for.section-card-action .btn(compact 5×12px padding, 12.5px font, sm radius, no primary shadow) so any kit button slotted as the action stays sized to the band. Consumer-side custom buttons that don't use the.btnclass are untouched.
Added
- R8-2:
"snoozed"value onTicketStatusValue. Renders with the.pill-purpletint to read as "set aside for later" — distinct fromclosed(muted-grey) andpending(warning-yellow). Mirrors theInboxRowstate="snoozed"semantic without sharing visual styling withclosed. Spark's local fallback to a neutral<Chip>can be removed.
Notes
- Pair with
@magicblocksai/[email protected](_shared.cssmirror picks up the.section-card-action .btnreset).
[1.9.1] — 2026-05-03
Patch — Spark Round 7 hot-fix.
Fixed
- R7-1:
<SectionCard variant="hero">rendered avatar stacked above title. The default-variant.section-card { flex-direction: column }rule (correct for the header-band variant) leaked into the hero variant via shared classes, so hero tiles rendered avatar-on-top-of-title instead of beside-it. Addedflex-direction: rowto.section-card-heroto re-assert the horizontal layout. Spark's local override can be removed.
Added
- R7-2:
<SectionCard variant="hero" LinkComponent={Link}>. Swap the default<a href>for a custom Link component (Next.js<Link>, React Router<Link>via shim, TanStack Router<Link>via shim) so the hero tile triggers client-side navigation instead of a full reload. Frameworks that name the proptoinstead ofhref(React Router, TanStack) need a tiny shim — documented atpackages/ui/docs/SectionCard.md.
Notes
- Pair with
@magicblocksai/[email protected](lockstep —_shared.cssmirror picks up theflex-direction: rowfix). - All consumers using
<SectionCard variant="hero">should bump.
[1.9.0] — 2026-05-03
Spark CRM Round 6 — surface hierarchy. 3 changes that make the kit's "cards on a page" pattern work without local overrides.
Added
<AppShell pageWash>prop +--app-page-bgtoken (R6-1). Opt into a recessed page wash so cards visually lift off the background. Defaultfalse(page bg matches card bg, the back-compat behaviour). Whentrue, page bg flips to--warm-1in light /--inkin dark — the standard "cards pop off a recessed page" pattern every CRM-style consumer hits. Pairs naturally with the new Card shadow default below.<Card flat>prop +.card-flatclass (R6-2). Opt back into the hairline-only card for inline contexts that don't need the shadow.<SectionCard variant="hero">(R6-3). Clickable single-row context tile (avatar · title · sub) for the "Company / Primary Contact" pattern on detail pages, "Associated work" rows, etc. New props:variant,sub,avatar,avatarStyle,href,onClick,target,rel. Renders as<a href>whenhrefis set,<button>whenonClickis set,<div>otherwise. Hover lifts tovar(--sh-2)with an accent border; focus shows the standard kit ring.
Changed
<Card>ships withbox-shadow: var(--sh-1)by default (R6-2) — visible to consumers. On a same-colour page bg, hairline + radius alone makes cards visually disappear. The small drop shadow gives the "card feels like a card" treatment every product app reaches for. Marketing-grid contexts that intentionally want flat cards (.feat-grid,.kpi-grid, etc.) opt out via<Card flat>or.card-flat.
Notes
- Pair with
@magicblocksai/[email protected]. - All changes are additive props or new defaults — no breaking React API changes. The Card shadow default is the only consumer-visible visual change; opt out per-card via
flat. - The
<AppShell pageWash>+<Card>shadow pair work together: page recesses, cards lift. Use both for the canonical cards-pop-off-page CRM look.
[1.8.0] — 2026-05-03
Spark CRM Round 5 — 5 issues across 2 bugs, 1 default-styling change, and 2 layout-token / prop additions. All kit-wide improvements.
Added
<AppShell topbarHeight>prop +--app-topbar-htoken (R5-4). AppShell now exposes its topbar height as a CSS custom property so consumers can writecalc(100vh - var(--app-topbar-h))instead of hardcodingcalc(100vh - 56px)magic numbers. Default56px, override via the prop or the variable directly. Sidebar width is also aliased as--app-shell-side-w(alongside the existing--app-shell-side) for naming consistency.<Kanban fillHeight>prop +--kanban-col-min-h/--kanban-col-max-htokens (R5-5). Drops the demo's 360–520px column height caps to0/noneand setsheight: 100%on both the kanban root and its columns. The HubSpot-style "deals view fills the viewport" look. Granular alternative: set the two CSS variables directly for specific values.
Changed
- Refined default scrollbars (R5-3) — visible to consumers.
@magicblocksai/cssnow ships slim, theme-aware, hover-lift scrollbars (10px, rounded thumbs, transparent track, ink-tint in light / cream-tint in dark). Native scrollbars looked industrial against the rest of the kit's chrome. Opt out via<body data-scrollbars="native">— restores the OS default for users who've configured custom scrollbar widths via OS / browser preferences.
Fixed
- R5-1:
<AppShell scrollMode="page">sidebar rules survived past the mobile breakpoint. Theposition: sticky; max-height: 100vhrule applied even when.app-shell-sidewas alreadydisplay: noneon mobile, forcing consumer slide-in nav drawers to fightposition: fixed !important; max-height: none !important; width !important; top !importantoverrides. Now gated to@media (min-width: 961px)so mobile slide-in panels work without the override war. - R5-2:
.app-shell-sidepainted no background in dark mode. v1.7.0 (R3-2) scoped the warm-cream sidebar bg to light mode only; dark-mode in-grid sidebars were fine because they inherited paper from the surrounding.app-shell, but a repositioned sidebar (slide-in panel, drawer, overlay) rendered transparent. Now.app-shell-sidepaintsvar(--bg-paper)unconditionally; the light-mode warm override still wins underbody:not([data-theme="dark"]).
Notes
- Pair with
@magicblocksai/[email protected]. - All changes are CSS-only, additive React props, or new defaults — no breaking changes for existing consumers (the scrollbar styling change is the only consumer-visible default; opt out via
data-scrollbars="native").
[1.7.1] — 2026-05-02
Patch — Spark Round 4 (early hot-fix). Two issues caught during Round 3 close-out, both shipped together.
Fixed
- R4-1:
<AppShell scrollMode="page">clobbered both overflow axes on.app-shell-main. v1.7.0 setoverflow: visible, which let a horizontally-scrolling child (e.g.<Kanban direction="horizontal">on /deals) expand the grid column past viewport width and drag the sticky topbar off-screen. Now the page-scroll mode keepsoverflow-x: hiddento contain horizontal scrollers and only releasesoverflow-yso vertical page scroll bubbles to<html>/<body>. Also addedmin-width: 0so the1frgrid track actually constrains (defaultmin-width: automakes it grow to intrinsic content width). - R4-2: DataTable recipe in
docs/DataTable.mdused invented prop names. The original recipe usedid/header/accessor/sortValue/rowKey/selected/onSelectedChange— none of which exist on the actual exported types. Rewrote the recipe against the real API:key/label/render/rowId/selectable/selectedIds/onSelectChangeplus separatesortBy: stringandsortDir: "asc" | "desc". Added a note thatsortablealways sorts onrow[key]directly (no per-columnsortValueaccessor in v1.7.x — flag as a gap if real CRM cases need it).
Notes
- Pure CSS + docs change; no React API change. Pair with
@magicblocksai/[email protected]. - All consumers on v1.7.0 should bump — R4-1 affects anyone using
scrollMode="page"with a horizontally-scrolling child; R4-2 affects anyone copy-pasting from the DataTable recipe.
[1.7.0] — 2026-05-02
Spark CRM Round 3 — 7 issues addressed (2 dark-mode bugs, 2 layout-mode props, 1 new component, 1 row-action affordance, 1 docs recipe).
Added
<SectionCard>(R3-5) — kit-chrome primitive for the canonical CRM detail-page section pattern. Warm-banded header (title·count·action) over an unpadded body region (so list primitives like<Inbox>/<DataTable>sit edge-to-edge).padBodyprop for prose / KV-pair bodies;emptyStateprop for the no-content case. Documented atpackages/ui/docs/SectionCard.md.<Kanban direction="horizontal">(R3-3) — opt-in horizontal-scroll layout for pipelines with many stages. Default"grid"mode unchanged. Pair with--kanban-col-count(column count token, default4) for the grid mode and--kanban-col-width(default280px) for the horizontal mode.<AppShell scrollMode="page">(R3-4) — opt-in for whole-page scrolling. Default"columns"mode unchanged. In"page"mode the sidebar becomesposition: sticky; top: 0; align-self: start; max-height: 100vh; overflow-y: auto, the topbar stays sticky, and<html>/<body>own the scrollbar — the right shape for long content where browser scroll-restoration, mobile scroll-into-view, and "scroll-to-top" should behave the way users expect from a webpage.<InboxRow secondaryAction>(R3-6) —{ icon, label, onClick }slot for a row-level secondary action button alongside complete/snooze. The discoverable, touch-friendly alternative toonContextMenufor "open in side tray" / "copy link" / etc.- DataTable sortable-CRM-table recipe (R3-7) — full code recipe in
packages/ui/docs/DataTable.mdcovering the canonical/leads//broadcasts//sequencesshape: sortable + sticky header, click-row-to-navigate, bulk-select column, mobile horizontal-scroll, server-side sort/pagination notes.
Fixed
- R3-1: dark-mode
--bg-warmcollapsed to--bg-paper. Both tokens were#2A3050, so any warm-tinted band (table headers, list section heads, panel-head, footer chrome) read as flat in dark mode. Lifted--bg-warmto#323858(~+5% over paper) so warm bands read as elevated. Light mode unchanged. - R3-2:
.app-shell-sidepinned ink-coloured tokens regardless of theme. Sidebar stayed warm-cream when the rest of the app went dark — broken for any product-app dark mode. The warm-pin block (--fg,--fg-soft,--fg-dim,--hair,background) is now scoped tobody:not([data-theme="dark"]). Dark mode falls through to the global semantic tokens. Marketing chrome still gets the warm look; product apps get a properly-themed sidebar.
Notes
- Pair with
@magicblocksai/[email protected](lockstep —_shared.cssmirror picks up the dark-mode + Kanban + AppShell + SectionCard CSS changes). - All fixes are CSS-only or additive prop / new-component changes — no breaking changes for existing consumers.
[1.6.0] — 2026-05-02
Compatibility — Tailwind v4 + React 19 + TypeScript 5.7.
Added
@magicblocksai/ui/theme.css— new export for Tailwind v4 consumers. CSS-first config: drop@import "@magicblocksai/ui/theme.css"after@import "tailwindcss"and every brand token (bg-paper,text-fg-soft,gap-s-4,rounded-lg,shadow-sh-2,font-display, …) becomes a utility class. Includes a@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *))so the kit'sbody[data-theme="dark"]strategy works under v4. Tailwind v3 consumers continue usingtailwind.preset.cjsunchanged.tailwindcsspeerDep markedoptionalviapeerDependenciesMeta— vanilla-CSS-only consumers (using@magicblocksai/csswithout React) no longer get a phantom Tailwind warning.
Changed
- React 19 type compatibility. Bumped devDeps to
react@^19,react-dom@^19,@types/react@^19,@types/react-dom@^19. The shipped.d.tsnow resolves cleanly against React 19's stricter types. PeerDeps stay at>=18so React 18 consumers still install — runtime is unchanged. - TypeScript 5.7 in devDeps. Authoring-only bump; no consumer impact.
- Tailwind 4 added to devDeps for parity with the new theme.css path.
- tsup bumped to
^8.5.1(latest patch).
Fixed (React 19 type breaks)
Checklist:ChecklistPropsnowOmit<HTMLAttributes<HTMLDivElement>, "title" | "onToggle">— React 19's HTMLAttributes types include the new HTML<dialog>toggleevent, which collided with ouronToggle: (id: string) => voidprop signature.KitCommandPalette:JSX.Element→React.JSX.Element(the globalJSXnamespace was removed in React 19's types).
Notes
@magicblocksai/uiand@magicblocksai/cssbumped together to1.6.0for clarity, even though the CSS package didn't change in source — version-lockstep keeps the install story simple.- Pair with
@magicblocksai/[email protected].
[1.5.2] — 2026-05-02
Patch — narrowed dark-mode elevation to overlays only.
Fixed
- v1.5.1 over-applied the elevation cue. The drop shadow + inset top highlight were added to content containers (
.card,.panel,.data-table,.calendar,.checklist,.inbox) along with true overlays. On detail pages every section ended up looking like a floating card. Narrowed the rule to overlays only:.modal,.drawer,.popover,.menu,.cmdk,.combobox-popover,.nav-chapters-panel. Content containers stay flat — they sit on the page with the lifted--bg-paper+ 1px hair border for separation, no shadow.
Notes
- Pure CSS change; no React component changes. Bumped in lockstep with
@magicblocksai/[email protected]. - Light mode unchanged. The
--bg-paperlift,--hairopacity bump, and shadow re-aliases from 1.5.1 all stay.
[1.5.1] — 2026-05-02
Patch — dark-mode contrast for elevated surfaces.
Fixed
- Modals, popovers, list containers, and inboxes blended into the page in dark mode. The previous
--bg-paper(#232842) was only ~6% lighter than the ink page background (#191E32) — modals like the "Pick a segment" dialog and list cards like the inbox / leads / tickets list visibly disappeared into the page. Lifted--bg-paperto#2A3050(~12% lift, clearly elevated). Bumped--hairopacity 0.14 → 0.18 so list-row separators are visible against the new lifted paper. Added a subtle inset top highlight (inset 0 1px 0 rgba(255,255,255,0.05)) to elevated chrome (modal, drawer, popover, menu, cmdk, combobox-popover, nav-chapters-panel, inbox, checklist, data-table, calendar, card, panel) — standard "light-from-above" elevation cue. - Dark-mode drop shadows were nearly invisible. The
--sh-1through--sh-4tokens usedrgba(25, 30, 50, …)(the ink colour) which is essentially invisible against the ink page. Re-aliased in dark mode to usergba(0, 0, 0, …)with deeper alpha (0.30 / 0.32 / 0.40 / 0.50). Modals now sit on a visible drop shadow.
Notes
- Light mode unaffected. Paper white on warm-cream page is already plenty of contrast. All overrides are scoped to
body[data-theme="dark"]. - Pair with
@magicblocksai/[email protected](the_shared.cssmirror).
[1.5.0] — 2026-05-02
Spark CRM Round 2. Restored the marketing-icon family that v1.4.0 inadvertently dropped, added 3 task-type icons, and gave the <Inbox> row some breathing room based on visual feedback against the live demo.
Added — icons (13 new exports)
- Restored 10 marketing-feature icons that v1.3 documented in JSDoc but v1.4.0 didn't ship:
BoltIcon,InboxIcon,LinkIcon,MailIcon,MenuIcon,PlatformIcon,RouteIcon,ShieldIcon,SyncIcon,TargetIcon. Same monoline / 18×18 / 1.5px /currentColorstyle as the Round 1 CRM-nav set. - 3 task-type icons for CRM-style task lists:
PhoneIcon(call),ClockIcon(sla_check / time tracking),EyeIcon(review / approval).
Changed — <Inbox> row vertical rhythm
Visual feedback against https://brand.magicblocks.ai/components/14-app-pipeline#inbox: the row felt cramped because it was a fixed height: 52px with two-line content (title + sub-line) crammed in, no vertical padding, 28px avatar pushing against the title, 2px title→sub gap.
height→min-heightso rows grow when content needs itpadding: 10px var(--s-4)— symmetric vertical breathing- Avatar 28→32, font 11→12 — reads at the same weight as the title now
- Title→sub gap 2→4px
- Sub-line line-height 1.2→1.3 (matches title)
- Compact density override:
padding: 6px var(--s-4)sobody[data-density="compact"]still hits the ~36px target
Comfortable rows now feel ~60px tall vs the old fixed 52px — closer to a production CRM inbox.
Migration notes
- The inbox change is technically breaking for consumers pinning the exact 52px height via custom CSS.
min-heightaccommodates anything ≥52px, so most consumers inherit the new rhythm without code changes. - Pair with
@magicblocksai/[email protected](the_shared.cssmirror).
[1.4.1] — 2026-05-02
Patch — popover positioning bug for real this time.
Fixed
- Popovers no longer park at viewport
(0,0)when their content is portal-rendered. The v1.4.0 fix inusePopover.ts(rAF retry when refs are null) tried to catch the same bug but the timing assumptions about React render ordering didn't always hold. Spark hit it on every dropdown menu open. The real root cause waslib/Portal.tsx's deferred-mount pattern (useState(false)+useEffect) which leftcontentRef.currentnull during the consumer's first effect cycle. Two-file fix: -lib/Portal.tsx— switched touseState(() => typeof document !== "undefined")lazy init. Mounts synchronously on first client render; returns null on the server. Refs populate during the first commit. -lib/usePopover.ts— switched fromuseEffecttouseLayoutEffect(with SSR fallback). Coords compute before paint, not after. Eliminates even the brief 0,0 flash. The rAF retry stays as a belt-and-braces safety net for consumers using a custom Portal target that isn't in the DOM yet at toggle time.
Affects every popover-based component without API changes: <Menu>, <DropdownMenu>, <Popover>, <Combobox>, <DatePicker>, <DateRangePicker>, <MergeTagInput>, <MergeTagTextarea>, <KitChapterNav> mega-dropdown, <KitCommandPalette>, <DialogHost>.
Notes
@magicblocksai/cssunchanged — stays at1.4.0.- Spark consumes via
pnpm update @magicblocksai/ui@^1.4.1. LocalPopoverMenu.tsxworkaround can be deleted;<DropdownMenu>/<Menu>work directly withplacementandoffsetdoing what the docs say.
[1.4.0] — 2026-05
Spark CRM migration follow-up. One new primitive (<AppShell>), four bug fixes, and a small batch of CSS clean-ups. Mirrored in @magicblocksai/[email protected] (the kit's _shared.css is the single source of truth so the CSS package gets bumped in lockstep).
Added — components
<AppShell>— full-viewport app chrome. Sibling to<DashboardShell>, but built for whole-app surfaces (100vh, no border, no border-radius) instead of embedded marketing demos. Two-column grid[sideWidth] 1frwith an optional stickytopbarslot in the main column. Sidebar fills viewport height with its own scroll; main column scrolls independently.sideWidthprop or--app-shell-sidecustom property to override. ARIA:<aside role="navigation">+<main role="main">. Mobile (≤960px): sidebar hidden — consumers should layer their own hamburger drawer. Carve-out from the per-component chapter-demo gate viaKIT_CHROMEinscripts/check-coverage.mjs.
Added — Card prop
<Card narrow>— opt-in modifier that re-applies the legacy 320px max-width cap. Maps to the new.card-narrowclass so HTML / Astro / 11ty consumers get the same hook. See "Fixed" below for why the default changed.
Fixed
usePopoverno longer parks portal-rendered content at(0, 0). When the popover content was rendered via<Portal>,Portal's SSR-safe deferred-mount pattern meantcontentRef.currentwas stillnullthe first timeusePopover's effect ran. The compute would bail out, no listeners fired again, and the menu sat at the viewport top-left forever. The compute now retries onrequestAnimationFrameuntil both refs are populated. Affects<Menu>/<DropdownMenu>/<Popover>/ anything else built onusePopover. (Fixes Spark issue #2.)<DropdownMenu>now anchors to its trigger. Same root cause as the popover fix above —<DropdownMenu>is an alias of<Menu>, and the Spark CRM team was hitting the portal-late-mount race on every click. Default placement remainsbottom-start.<Card>no longer shipsmax-width: 320pxby default. The cap was making<Card>useless as a list container. Drop the baremax-widthfrom.card; restore it via a new opt-in.card-narrowclass (or<Card narrow>from React). Chapter 5's three-card surface demo now setscard-narrowexplicitly so the visual reference stays the same. (Fixes Spark issue #6.).dash-nav-item > span:first-child { flex: 1 }is gone. The bare-span selector ate every direct<span>child — a leading icon<span>would stretch to fill the row, and a trailing badge<span>would do the same. Replaced with an explicit.dash-nav-item-labelclass consumers wrap the label text in.<DashboardShell>'s default sidebar markup now emits<span class="dash-nav-item-label">for every label; chapter 10's demo HTML and React snippet were updated to match. (Fixes Spark issue #7.)
Changed
.dash-nav-itemvertical padding bumped from 8px → 7px, exposed as--dash-nav-item-pyso consumers can override without rewriting the wholepaddingshorthand. Default is plenty of breathing room without making nav rows feel airy. (Fixes Spark issue #8.)- Small
margin-top: 4pxbetween a.dash-nav-labelheading and the first.dash-nav-itemunderneath it — keeps group headings from sitting flush against the first row.
Removed
./stylesfrompackages/ui/package.jsonexports. The map referenced./dist/styles.css, which never shipped. All CSS lives in@magicblocksai/css/dist/magicblocks.css(per the v1.3.0 handoff); the broken alias was just a footgun. (Fixes Spark issue #1.)
CSS
Net-new and changed CSS in components/_shared.css (mirrored verbatim in @magicblocksai/[email protected]):
- Added under "v1.4.0 — AppShell":
.app-shell+.app-shell-side+.app-shell-main+.app-shell-topbar. New custom properties--app-shell-side(default 248px) and--z-topbar(default 30). - Added
.card-narrow(opt-in 320px cap, see above). - Added
.dash-nav-item-labeland.dash-nav-label + .dash-nav-item { margin-top: 4px }. - Changed
.cardno longer setsmax-width: 320px. - Changed
.dash-nav-itempadding shorthand split into per-axis withpadding-top: var(--dash-nav-item-py, 7px); padding-bottom: var(--dash-nav-item-py, 7px). - Removed
.dash-nav-item > span:first-child { flex: 1 }.
Migration notes
- Breaking-ish but quiet:
.cardno longer caps at 320px. If your layout depended on the cap (most marketing pricing-grid demos do), addcard-narrowto the affected nodes — or passnarrowto<Card>if you're using the React component. Chapter 5's surface demo is the canonical example. - Breaking for
.dash-nav-itemconsumers who wrote raw HTML: wrap the label text in<span class="dash-nav-item-label">…</span>so it absorbs the row's free space. Without the label class the label collapses to its content width and the row no longer pushes badges to the right edge.<DashboardShell>'s default sidebar markup already does this; the change shows up in the chapter 10 demo too. - No code change required for
<DropdownMenu>/<Menu>/<Popover>consumers. The fix is internal tousePopover.
[1.2.0] — 2026-05
Tier-1 batch shipped to unblock the Spark CRM migration. Six consumer-facing primitives + four standalone hooks. No chapter demos — these are kit-chrome / consumer primitives that live alongside Modal / Popover / Toaster. Gate exemption added via KIT_CHROME in scripts/check-coverage.mjs.
Added — components
<DataTable>— sortable, sticky-header, cursor-paginated table built on a CSS grid (so the sticky header works inside any scrollable container). Selection column with indeterminate "select all", keyboard navigation (↑/↓/Space/Enter),aria-sorton sortable headers, skeleton loading rows, and a "Load more" button driven bynextCursor. Default empty state is a centred "No results" message; passemptyto override.<FilterChipGroup>— multi-select chip cluster with leading "All" chip and optional "+N more" overflow popover. Wraps the existing.chipstyles; adds.chip-button,.chip-active,.chip-count,.chip-overflowmodifiers. Uses<Popover>for the overflow.<DatePicker>— single-date picker with single-month calendar in a popover. No external date library.<DateRangePicker>— date-range picker with preset rail + two-month calendar. Six default presets exported asDEFAULT_DATE_RANGE_PRESETS(Last 7 / 30 / 90 days, This / Last month, This year). Trigger label switches to the active preset name when the selection matches a preset.<DialogHost>+confirm()/alert()/prompt()— singleton dialog API on top of<Modal>. Mount one<DialogHost />at the app root, then callconfirm({...})from anywhere (returnsPromise<boolean>).destructive: trueswitches the confirm button toButton variant="danger". Esc / backdrop click resolve as cancelled.useConfirm()— hook returning a stable callable with.alert()/.prompt()methods.
Added — hooks
useDebounce<T>(value, ms)— returns the input value aftermsms have elapsed without further changes. Cleans up on unmount.useLocalStorage<T>(key, fallback)—useStatesynced towindow.localStorage. SSR-safe (returnsfallbackon the server and the first client render, hydrates on mount). Listens to thestorageevent for cross-tab sync.
Added — internal
lib/calendar.ts— tiny calendar helpers (startOfDay,addMonths,buildMonthMatrix,formatMonthYear, etc.) used by both pickers. No external date library —Date+Intl.DateTimeFormatonly.lib/CalendarGrid.tsx— internal calendar grid component shared by<DatePicker>and<DateRangePicker>. Not exported from the package barrel.lib/dialogStore.ts— module-level singleton store poweringconfirm()/alert()/prompt(). Pub/sub pattern mirroringtoastStore.
CSS
Net-new CSS appended to components/_shared.css under the heading "@magicblocksai/ui v1.2.0 — CRM primitives":
.data-table+.data-table-grid+.data-table-row+.data-table-cell+.data-table-head+.data-table-th+.data-table-sort+.data-table-skel-block(skeleton rows, animation suppressed underprefers-reduced-motion).filter-chip-group+.chip-button+.chip-active+.chip-count+.chip-overflow.calendar+.calendar-head+.calendar-month+.calendar-nav+.calendar-weekdays+.calendar-weekday+.calendar-grid+.calendar-day(with.calendar-day-outside/.calendar-day-in-range/.calendar-day-selectedmodifiers).date-picker+.date-range-picker+.date-picker-trigger+.date-picker-trigger-ic+.date-picker-placeholder+.date-picker-popover+.date-range-popover+.date-range-presets+.date-range-preset+.date-range-preset-active+.date-range-calendars.dialog-host+.dialog-body+.dialog-input
Toast — already shipped
The brief listed toast.success(...) / toast.error(...) / useToast() as Tier 1, but these were already shipped in 1.0+. No code change in this batch — the existing toast callable, useToast() hook, and toastStore singleton in Toaster.tsx already provide the documented API.
Notes for consumers
- All ten additions are purely additive — no breaking changes.
- Mount one
<DialogHost />at your app root (alongside<Toaster />) soconfirm()/alert()/prompt()have somewhere to render. <DataTable>uses CSS grid rather than<table>so the sticky header and column-width controls behave consistently across scroll containers. The wrapper still hasrole="table"and rows still haverole="row"for screen readers.
[1.1.0] — 2026-04
Added
- Drop-in chapter-demo defaults across ~80 components. Every demo-shaped component (
<ChatCompare />,<JourneyMap />,<EngineBlock />,<HeroLiveDemo />,<HeroScene />,<DormantMine />,<IntegrationHub />,<ContrastPair />,<FeatureCluster />,<PricingPage />,<DashboardShell />,<DetailShell />,<SageDrawer />,<Kanban />,<Inbox />,<PipelineBar />,<Checklist />,<NotFoundPage />,<ConfettiTrigger />,<TopNav />,<SideNav />,<SiteFooter />,<Breadcrumbs />,<Tabs />,<Stepper />,<Timeline />,<Triptych />,<LogoWall />,<PressStrip />,<StatsStrip />,<TestimonialStrip />,<TeamGrid />,<OnboardingStep />,<Sparkline />,<PricingCard />,<ProductDashboard />,<KeyValue />,<KpiTile />,<DashboardTile />,<CalloutCard />,<EmptyAppCard />,<EmptyStatePage />,<MediaCard />,<CodeBlock />,<LeakCard />,<MrrChart />,<StatCard />,<Section />,<SettingsShell />,<StageChat />,<SLARing />,<DeviceFrame />,<Pagination />,<RiskBadge />,<StatBadge />,<HandoffCard />,<ProfileCard />,<KbSuggestionCard />,<ConversationPreview />,<ClusterMap />,<EdgeRace />,<RaceTimeline />,<Scoreboard />,<BrandTimeline />,<CounterRail />,<BulkBar />,<Carousel />,<Checklist />,<ComparisonTable />,<ActivityTimeline />,<EmptyState />,<Faq />,<FounderGrid />,<DemoTabs />,<ComposeDrawer />,<CommandPalette />,<FeatureCard />,<FeatureTable />,<GuardianShield />,<EcosystemRings />) now renders the chapter-demo content with zero props. Pass props to override; defaults take over only on the zero-prop call. ChatCompareScenarioswitching.<ChatCompare scenario="auto" />swaps the canonical mortgage refi conversation for the auto scenario in one prop. Six scenarios bundled:mortgage,insurance,solar,home-services,auto,fintech.- AGENTS.md "Drop-in defaults" section documents the zero-prop usage pattern and the zero-prop-only guard semantics for AI coding agents.
- CHANGELOG.md (this file).
Fixed
- Phantom-default content bug across 43 multi-prop components. The previous round used destructuring defaults (
prop = DEFAULT_X), which filled in chapter content for every prop the consumer didn't pass — meaning<StatCard label="Handoffs" value="14%" />rendered a phantom+94%delta from the chapter demo. Replaced with a "zero-prop-only" guard so consumers passing partial props get only what they passed; chapter content fills only when the consumer passes nothing. Reference fix: commit9b274b4. StatBadge.numandSparkline.valueswere generic placeholders (42and[22, 24, …, 41]) that broke the project's "real content, not placeholders" rule. Re-sourced from the canonical chapter demos:StatBadgefrom §11 Triptych "02 · Run" (Avg. / 5s / First response);Sparklinefrom §7.17's React snippet ([82, 81, 83, 84, 86, 86, 87]).- Markup-equivalence test gate now covers 23 fixtures (was 22). One net-new pass:
CounterRail. Three fixtures added but skip-flagged with documented drift (BrandTimeline,ContrastPair,FeatureTable).
Notes for consumers
- Behaviour is fully back-compatible. Every component still accepts the same props it accepted in 1.0; the only change is that previously-required props are now optional with a chapter-sourced default. Existing call-sites continue to work unchanged.
- The "zero-prop-only guard" means consumers writing
<HandoffCard initials="JS" />will see only the initials they passed — the rest of the card (name, role, tags, facts) stays unrendered, matching "if missing, hide" semantics. To get the full chapter demo, call<HandoffCard />with no props. - The AGENTS.md change is a docs-only update for AI coding agents reading the package; it doesn't affect runtime.
[1.0.0] — 2026-04
Added
- Phase 5 milestone: full surface (chapters 02–16), modular CSS imports via
@magicblocksai/css, all gates green. - Markup-equivalence regression gate (
pnpm --filter @magicblocksai/ui test). 'use client'directive coverage gate.- Authoring rules in
CLAUDE.mdandAUTHORING.md.
Older versions
See packages/ui/AGENTS.md § "Versioning" for the full milestone table back to 0.0.1.