@thewhatmatters/wmds 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/CONSUMING.md +3 -2
  3. package/bin/wmds-check.mjs +108 -10
  4. package/dist/components/molecules/Breadcrumb/Breadcrumb.d.ts +59 -0
  5. package/dist/components/molecules/Breadcrumb/Breadcrumb.d.ts.map +1 -0
  6. package/dist/components/molecules/Breadcrumb/Breadcrumb.js +188 -0
  7. package/dist/components/molecules/Breadcrumb/breadcrumbStyles.d.ts +21 -0
  8. package/dist/components/molecules/Breadcrumb/breadcrumbStyles.d.ts.map +1 -0
  9. package/dist/components/molecules/Breadcrumb/breadcrumbStyles.js +11 -0
  10. package/dist/components/molecules/Dropdown/DropdownItem.d.ts +7 -2
  11. package/dist/components/molecules/Dropdown/DropdownItem.d.ts.map +1 -1
  12. package/dist/components/molecules/Dropdown/DropdownItem.js +40 -24
  13. package/dist/components/organisms/ChatDock/ChatDock.d.ts +9 -18
  14. package/dist/components/organisms/ChatDock/ChatDock.d.ts.map +1 -1
  15. package/dist/components/organisms/ChatDock/ChatDock.js +411 -411
  16. package/dist/components/organisms/ChatDock/ChatDockGate.d.ts +19 -9
  17. package/dist/components/organisms/ChatDock/ChatDockGate.d.ts.map +1 -1
  18. package/dist/components/organisms/ChatDock/ChatDockGate.js +152 -123
  19. package/dist/components/organisms/ChatDock/chatDockGateContext.d.ts +11 -0
  20. package/dist/components/organisms/ChatDock/chatDockGateContext.d.ts.map +1 -0
  21. package/dist/components/organisms/ChatDock/chatDockGateContext.js +9 -0
  22. package/dist/components/organisms/ChatDock/chatDockGateStyles.d.ts +15 -10
  23. package/dist/components/organisms/ChatDock/chatDockGateStyles.d.ts.map +1 -1
  24. package/dist/components/organisms/ChatDock/chatDockGateStyles.js +3 -3
  25. package/dist/components/organisms/ChatDock/chatDockStyles.d.ts +10 -4
  26. package/dist/components/organisms/ChatDock/chatDockStyles.d.ts.map +1 -1
  27. package/dist/components/organisms/ChatDock/chatDockStyles.js +9 -6
  28. package/dist/components/organisms/ChatDock/chatDockViewport.d.ts +6 -0
  29. package/dist/components/organisms/ChatDock/chatDockViewport.d.ts.map +1 -1
  30. package/dist/components/organisms/ChatDock/chatDockViewport.js +14 -1
  31. package/dist/components/organisms/Dialog/Dialog.js +34 -34
  32. package/dist/components/organisms/Sheet/Sheet.js +38 -38
  33. package/dist/components/organisms/SiteNav/SiteNav.js +86 -86
  34. package/dist/components/organisms/Toast/Toast.d.ts.map +1 -1
  35. package/dist/components/organisms/Toast/Toast.js +11 -5
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +90 -88
  39. package/dist/lib/compositionAudit.d.ts.map +1 -1
  40. package/dist/lib/dialogOverlay.d.ts +5 -0
  41. package/dist/lib/dialogOverlay.d.ts.map +1 -1
  42. package/dist/lib/dialogOverlay.js +29 -15
  43. package/dist/styles.css +1 -1
  44. package/docs/component-contracts.md +4 -2
  45. package/docs/components.md +7 -6
  46. package/docs/exports.json +17 -1
  47. package/docs/patterns/components-accordion--flush.tsx +1 -1
  48. package/docs/patterns/components-avatar--dropdown-row.tsx +1 -1
  49. package/docs/patterns/components-badge--outline-tags.tsx +1 -1
  50. package/docs/patterns/components-badge--with-avatar.tsx +1 -1
  51. package/docs/patterns/components-breadcrumb--editorial.tsx +9 -0
  52. package/docs/patterns/components-breadcrumb--long-path.tsx +9 -0
  53. package/docs/patterns/components-breadcrumb--path.tsx +9 -0
  54. package/docs/patterns/components-breadcrumb--router-links.tsx +10 -0
  55. package/docs/patterns/components-button-button--copy-button.tsx +1 -1
  56. package/docs/patterns/components-button-button--destructive-action.tsx +1 -1
  57. package/docs/patterns/components-button-button--external-link.tsx +1 -1
  58. package/docs/patterns/components-button-button--ghost-action.tsx +1 -1
  59. package/docs/patterns/components-button-button--inverse-action.tsx +1 -1
  60. package/docs/patterns/components-button-button--link-render.tsx +1 -1
  61. package/docs/patterns/components-button-button--multi-state-badge.tsx +1 -1
  62. package/docs/patterns/components-button-button--outline-mono.tsx +1 -1
  63. package/docs/patterns/components-button-button--primary-action.tsx +1 -1
  64. package/docs/patterns/components-button-button--row-hug.tsx +1 -1
  65. package/docs/patterns/components-button-button--row-layout.tsx +1 -1
  66. package/docs/patterns/components-button-button--secondary-action.tsx +1 -1
  67. package/docs/patterns/components-button-button--suggestion-pills.tsx +1 -1
  68. package/docs/patterns/components-button-button--with-count.tsx +1 -1
  69. package/docs/patterns/components-button-button--with-icon.tsx +1 -1
  70. package/docs/patterns/components-button-floatingactionbutton--vertical-actions.tsx +1 -1
  71. package/docs/patterns/components-calembed--pattern-skip-in-footer.tsx +1 -1
  72. package/docs/patterns/components-card-card--body-slot-occupancy-history.tsx +1 -1
  73. package/docs/patterns/components-card-card--headerless-media.tsx +1 -1
  74. package/docs/patterns/components-card-card--outlined-layout.tsx +1 -1
  75. package/docs/patterns/components-card-card--pinned-height.tsx +1 -1
  76. package/docs/patterns/components-card-selectablecard--selectable-cards-pattern.tsx +1 -1
  77. package/docs/patterns/components-chart--audience-activity-heatmap.tsx +1 -1
  78. package/docs/patterns/components-chart--cartesian-no-data-gaps.tsx +1 -1
  79. package/docs/patterns/components-chart--occupancy-history-in-card.tsx +1 -1
  80. package/docs/patterns/components-chart--occupancy-in-card.tsx +1 -1
  81. package/docs/patterns/components-chart--post-reach-distribution.tsx +1 -1
  82. package/docs/patterns/components-chart--ranked-audience-breakdown.tsx +1 -1
  83. package/docs/patterns/components-chart--unit-grid-composition.tsx +1 -1
  84. package/docs/patterns/components-chatdock--pattern-chat-dock.tsx +1 -1
  85. package/docs/patterns/components-chatdock--pattern-start-project-gate.tsx +54 -11
  86. package/docs/patterns/components-chatqa--pattern-qa-with-reply.tsx +1 -1
  87. package/docs/patterns/components-checkbox-checkbox--default.tsx +1 -1
  88. package/docs/patterns/components-checkbox-checkbox--with-count.tsx +1 -1
  89. package/docs/patterns/components-checkbox-checkbox--with-description.tsx +1 -1
  90. package/docs/patterns/components-checkbox-checkboxgroup--default.tsx +1 -1
  91. package/docs/patterns/components-checkbox-checkboxgroup--option-counts.tsx +1 -1
  92. package/docs/patterns/components-descriptionlist--details.tsx +1 -1
  93. package/docs/patterns/components-descriptionlist--post-metadata.tsx +1 -1
  94. package/docs/patterns/components-dialog--alert-dialog-destructive.tsx +1 -1
  95. package/docs/patterns/components-dialog--default-dialog.tsx +1 -1
  96. package/docs/patterns/components-dialog--form-dialog.tsx +1 -1
  97. package/docs/patterns/components-displaycontrols--page-utilities.tsx +1 -1
  98. package/docs/patterns/components-field--horizontal-input.tsx +1 -1
  99. package/docs/patterns/components-field--horizontal-text-area.tsx +1 -1
  100. package/docs/patterns/components-field--multi-control.tsx +1 -1
  101. package/docs/patterns/components-footerreveal--marketing-hero-pattern.tsx +1 -1
  102. package/docs/patterns/components-footerreveal--marketing-hero-ruled-pattern.tsx +1 -1
  103. package/docs/patterns/components-footerreveal--marketing-hero-with-gallery-intro.tsx +1 -1
  104. package/docs/patterns/components-footerreveal--marketing-page-pattern.tsx +1 -1
  105. package/docs/patterns/components-footerreveal--ruled-grid-footer-pattern.tsx +1 -1
  106. package/docs/patterns/components-footerreveal--sequenced-marketing-hero.tsx +1 -1
  107. package/docs/patterns/components-herointro--two-line-intro.tsx +1 -1
  108. package/docs/patterns/components-herotilestack--marketing-hero-pattern.tsx +1 -1
  109. package/docs/patterns/components-herotilestack--marketing-hero-text-sequence.tsx +1 -1
  110. package/docs/patterns/components-herotilestack--marketing-hero-with-gallery-intro.tsx +1 -1
  111. package/docs/patterns/components-indexlist--post-index.tsx +1 -1
  112. package/docs/patterns/components-intakeconfirmation--booked-pattern.tsx +1 -1
  113. package/docs/patterns/components-intakeconfirmation--emailed-pattern.tsx +1 -1
  114. package/docs/patterns/components-intakeform--about-you-pattern.tsx +1 -1
  115. package/docs/patterns/components-intakemodal--intake-modal-pattern.tsx +1 -1
  116. package/docs/patterns/components-kbd--command-row.tsx +1 -1
  117. package/docs/patterns/components-kbd--numbered-choices.tsx +1 -1
  118. package/docs/patterns/components-kbd--shortcut.tsx +1 -1
  119. package/docs/patterns/components-moremenu--action-menu.tsx +1 -1
  120. package/docs/patterns/components-moremenu--in-card-header.tsx +1 -1
  121. package/docs/patterns/components-navlist--side-nav-settings.tsx +1 -1
  122. package/docs/patterns/components-pageheader--app-header-pattern.tsx +1 -1
  123. package/docs/patterns/components-pageheader--page-section-header.tsx +1 -1
  124. package/docs/patterns/components-pageheader--toolbar-header.tsx +1 -1
  125. package/docs/patterns/components-panel--end-detail-rail.tsx +1 -1
  126. package/docs/patterns/components-pillgroup--pill-group-pattern.tsx +1 -1
  127. package/docs/patterns/components-promptbar--pattern-prompt-bar.tsx +1 -1
  128. package/docs/patterns/components-prose--article-body.tsx +1 -1
  129. package/docs/patterns/components-radio-radiogroup--default.tsx +1 -1
  130. package/docs/patterns/components-scrollhorizontal--project-gallery-pattern.tsx +1 -1
  131. package/docs/patterns/components-scrollhorizontal--with-intro.tsx +1 -1
  132. package/docs/patterns/components-sectioncaption--column-caption.tsx +1 -1
  133. package/docs/patterns/components-sectioncaption--with-action.tsx +1 -1
  134. package/docs/patterns/components-sheet--bottom-sheet.tsx +1 -1
  135. package/docs/patterns/components-sheet--end-sheet.tsx +1 -1
  136. package/docs/patterns/components-sitenav--brand-and-cta.tsx +1 -1
  137. package/docs/patterns/components-sitenav--centered-links.tsx +1 -1
  138. package/docs/patterns/components-sitenav--compact.tsx +1 -1
  139. package/docs/patterns/components-sitenav--marketing-header.tsx +1 -1
  140. package/docs/patterns/components-sitenav--mega-menu.tsx +1 -1
  141. package/docs/patterns/components-stat--insights-row.tsx +1 -1
  142. package/docs/patterns/components-stepprogress--step-progress-pattern.tsx +1 -1
  143. package/docs/patterns/components-switch--default.tsx +1 -1
  144. package/docs/patterns/components-switch--settings-row.tsx +1 -1
  145. package/docs/patterns/components-tab--settings-pages.tsx +1 -1
  146. package/docs/patterns/components-taskrows--action-details.tsx +1 -1
  147. package/docs/patterns/components-taskrows--capsules.tsx +1 -1
  148. package/docs/patterns/components-taskrows--card-with-status-rows.tsx +1 -1
  149. package/docs/patterns/components-taskrows--controlled-expand.tsx +1 -1
  150. package/docs/patterns/components-taskrows--custom-slots.tsx +1 -1
  151. package/docs/patterns/components-taskrows--status-list.tsx +1 -1
  152. package/docs/patterns/components-taskrows--tag-chips.tsx +1 -1
  153. package/docs/patterns/components-textlink--external-destination.tsx +1 -1
  154. package/docs/patterns/components-textlink--inline-prose.tsx +1 -1
  155. package/docs/patterns/components-textlink--quiet-title.tsx +1 -1
  156. package/docs/patterns/components-textlink--supporting-copy.tsx +1 -1
  157. package/docs/patterns/components-textsequence--default.tsx +1 -1
  158. package/docs/patterns/components-textsequence--reduced-motion.tsx +1 -1
  159. package/docs/patterns/components-textsequence--shapes.tsx +1 -1
  160. package/docs/patterns/components-toast--stacked-notifications.tsx +1 -1
  161. package/docs/patterns/components-tooltip--controlled.tsx +1 -1
  162. package/docs/patterns/components-tooltip--disabled-control.tsx +1 -1
  163. package/docs/patterns/components-tooltip--icon-action.tsx +1 -1
  164. package/docs/patterns/components-tooltip--keyboard-shortcut.tsx +1 -1
  165. package/docs/patterns/components-tooltip--placement.tsx +1 -1
  166. package/docs/patterns/foundations-scroll-fade--horizontal-rail.tsx +1 -1
  167. package/docs/patterns/foundations-scroll-fade--overflowing-list.tsx +1 -1
  168. package/docs/patterns/guides-chart-explorations--comparison-gallery.tsx +1 -1
  169. package/docs/patterns/guides-filter-panel--filtered-index.tsx +41 -17
  170. package/docs/patterns/guides-overlay-flows--notification-preferences.tsx +1 -1
  171. package/docs/patterns/guides-post-page--post-page.tsx +39 -4
  172. package/docs/patterns/guides-profile-typography--editorial-profile.tsx +1 -1
  173. package/docs/patterns/index.json +33 -1
  174. package/package.json +1 -1
@@ -34,9 +34,11 @@ The site assistant. At rest, a **PromptBar** pinned to the bottom of the page gr
34
34
 
35
35
  **Reply row.** Under each finished reply, a row of up to three parts, each shown on its own: **Copy** when the message has `copyText` (it writes that text to the clipboard, swaps its icon to a check and its name to `labels.copied` for two seconds, and announces it in a status region); thumbs up and down when `onMessageFeedback` is passed and the message carries `feedback` (**IconButton** `pressed` toggles named `labels.helpful` / `labels.notHelpful`, one vote at a time; `feedback` is `"up" | "down" | null` — `null` until the visitor votes — and pressing the active thumb calls `onMessageFeedback(message, null)`; leave `feedback` out on replies that take no vote, such as a form's summary); and the score when the message has `meta` (`label` as muted caption text, not a control; `description` follows it for screen readers). Visitor turns and the greeting never have a row. Where the pointer can hover, the row shows while the reply is hovered or holds focus (fast tier); on touch screens it is always shown. Either way it keeps its height, so the thread never moves. While `thinking` is on, the latest reply is still arriving: its row keeps its place but shows nothing. Buttons are **IconButton** `md` (44px) below `md` and `sm` (36px) from `md`, pulled out so the first glyph lines up with the reply text. **Pattern — chat dock** holds the votes: `ask` resolves with `string | { reply, followUps?, meta?, copyText?, startProject? }` (`copyText` defaults to the reply; replies start with `feedback: null`), and each vote goes to its required `onFeedback` prop. It also holds Start a project: `startProject` / `onStartProjectChange` (a site button sets it; the Start a project suggestion and an `ask` result with `startProject` open it) and `renderStartProject`, which returns the gate.
36
36
 
37
- **Gate.** `gate` puts a multi-step form in the composer's place — **ChatDock.Gate** (`ChatDockGate`), for Start a project. While it is set and the window is open, the composer, the suggestion rows, the follow-ups, and the disclaimer step aside; the conversation stays above it. Setting it opens the window (closed, or mid-conversation) and moves focus to the gate; clearing it brings the composer back and returns focus to it. The composer and the gate share one cell and crossfade (fast tier) while the area eases between their heights (medium tier); both stay mounted, so a gate keeps its progress when the visitor closes the window with it up, and it is there when the window reopens. The gate hugs each step up to a cap — the window less its header, 7rem of conversation, and the space under the gate, never below 12rem — so the last lines of the latest reply stay in view; past the cap its body scrolls. Escape belongs to the gate: **ChatDock.Gate** calls `onClose` on it, and ChatDock does not fold the window on Escape while a gate is up (a second Escape, at the composer, does). **ChatDock.Gate** is a **Card** (`as="section"`, `padding="none"`, `variant="surface"`): `title` (names the gate) and `subtitle`; at the end of the header **IconButton** previous (disabled on step 1), the progress (`labels.progress`, "2 of 4", announced politely), next (disabled without `onNext` or while `canContinue` is false), and close; the step in **Card.Body** on the inset well, sliding between steps (fast tier) while the body eases to the new height (medium tier); **Card.Footer** with `footerStart` (for example **CalEmbed.Skip**), Cancel, and the primary action (`continueLabel`, shown when `onNext` is passed). Controls are 44px below `md` and 36px from `md`. Number keys belong to the pattern through **useKbdChoiceKeys** with `scope` set to the gate, so digits act only while focus is in it. **Pattern — start a project gate** fills four steps — services as numbered **Checkbox** rows, budget as numbered **Radio** rows, **IntakeForm**, **CalEmbed** — and on booking or skipping calls `onSubmit`, fires confetti from the control used, and hands the conversation the answers (**ChatQa**) and a confirmation line.
37
+ **Gate.** `gate` hands the window to a multi-step form — **ChatDock.Gate** (`ChatDockGate`), for Start a project. While it is set and the window is open, the window is the form: the gate fills it, and the window's header, the conversation, the composer, the suggestion rows, the follow-ups, and the disclaimer fade out (fast tier) and wait, hidden and out of the tab order, behind it. The fixed window is modal meanwhile: `aria-modal="true"`, a scrim (`bg-overlay`) over the page and **SiteNav** (the dock rises to `z-[60]`), everything outside the dock `inert`, the page's scroll locked, and Tab cycling inside the window. Setting it opens the window (closed, or mid-conversation) and moves focus to the gate; clearing it fades the gate out and brings the conversation and the composer back, with focus in the composer. Both stay mounted, so the gate keeps its progress when the window folds and is there when it reopens. The gate's header close (named by ChatDock's `labels.close`), Escape, and a click on the scrim fold the window and keep the gate; the gate's Cancel (`onClose`) leaves it. **ChatDock.Gate** is a flush, ghost **Card** that fills the window (`as="section"`, `padding="none"`): one header — `title` (names the gate) and `subtitle`, and at its end **IconButton** previous (disabled on step 1), the progress (`labels.progress`, "2 of 4", announced politely), next (disabled without `onNext`, while `canContinue` is false, and on the last step), and close — over a hairline; the step in **Card.Body**, which takes the height left and scrolls, sliding between steps (fast tier) and starting each at the top; a hairline; a status line with `labels.pending` ("Sending…") while `pending`, or an `error` line (`role="alert"`, `text-error`); and **Card.Footer** with `footerStart` (for example **CalEmbed.Skip**), Cancel, and the primary action (`continueLabel`, shown when `onNext` is passed — on the last step an action such as "Send" or "Try again"), on the window's bottom edge. `pending` disables previous, next, Cancel, and the primary action and sets `aria-busy`. Controls are 44px below `md` and 36px from `md`. On its own, outside **ChatDock**, the gate's close and Escape call `onClose`. Number keys belong to the pattern through **useKbdChoiceKeys** with `scope` set to the gate, so digits act only while focus is in it. **Pattern — start a project gate** fills four steps — services as numbered **Checkbox** rows, budget as numbered **Radio** rows, **IntakeForm**, **CalEmbed** — and on booking or skipping awaits `onSubmit` (a promise) with `pending` on: when it resolves it fires confetti from the control used and hands the conversation the answers (**ChatQa**) and a confirmation line; when it rejects it keeps every answer, shows the error, and offers Try again.
38
38
 
39
- **Phones.** Below `md` the window fills the part of the screen a keyboard leaves visible (`visualViewport`), and the composer and a gate sit at its bottom edge.
39
+ **Phones.** Below `md` the window fills the part of the screen a keyboard leaves visible (`visualViewport`); the composer, or a gate's footer, sits at its bottom edge, and a gate's footer clears the home indicator (`env(safe-area-inset-bottom)`).
40
+
41
+ **Hydration.** The server and the hydration render read reduced motion as off, so the closed window's inline style matches the server's; it switches right after, while the window is hidden.
40
42
 
41
43
  The app owns `messages`, `thinking`, `followUps`, the votes, the gate's open state, `onSend`, and `onSuggestionSelect`; ChatDock never fetches or parses Markdown (`content` is text or rendered Markdown). `placement="inline"` is for previews only. Copy **Components/ChatDock → Pattern — chat dock**. See **ADR-0039**.
42
44
 
@@ -1,6 +1,6 @@
1
1
  # @thewhatmatters/wmds — component contracts
2
2
 
3
- Version 0.4.1. Generated from the WMDS repository's AGENTS.md (Storybook-first) by `scripts/generate-package-docs.mjs` — do not edit here.
3
+ Version 0.4.3. Generated from the WMDS repository's AGENTS.md (Storybook-first) by `scripts/generate-package-docs.mjs` — do not edit here.
4
4
 
5
5
  Storybook paths such as **Components/Button/Button → Pattern — …** name a story; its Show code is in `patterns/` (see `patterns/index.json`). Longer contracts for FooterReveal, HeroTileStack, TextSequence, ScrollHorizontal, RiveHand, and PromptBar are in `component-contracts.md`. ADR numbers refer to decision records in the WMDS repository.
6
6
 
@@ -29,6 +29,7 @@ Storybook paths such as **Components/Button/Button → Pattern — …** name a
29
29
  - **ScrollHorizontal:** marketing gallery directly under the hero — `items`, optional `heading`, `intro` (**`ScrollHorizontal.Intro`**), `expandLast`, `expanded`; `className` on the root only. A sticky window translates the row from the first card to the last; `expandLast` then grows the last tile to full bleed. Reduced motion is a native horizontal scroller. Paste **Components/ScrollHorizontal → Pattern — project gallery**. See **ADR-0036**. Full contract: **docs/component-contracts.md → ScrollHorizontal**.
30
30
  - **RiveHand:** decorative hand on the marketing headline (`@rive-app/react-canvas`) — `hand` (`point` \| `rock`), `size`, `inline`, `active`, `idle`, `entrance`; `aria-hidden` by default. Apps serve two files beside each other: `/rive/interactive-icon-set.riv` and `/rive/rive.wasm` (`riveHandWasmSrc`, copied from `node_modules/@rive-app/canvas/rive.wasm`). Reduced motion draws one static frame. Paste **Components/HeroTileStack → Pattern — marketing hero**. See **ADR-0034**. Full contract: **docs/component-contracts.md → RiveHand**.
31
31
  - **SiteNav:** marketing site header — `start` | `middle` | `end` slots; **page chrome** (`placement="fixed"`, optional `scrollContainer`) vs **specimen** (`placement="inline"`); expanded is **in flow** (scrolls away); after scroll past `collapseAt` (default **50% of scrollport**) a **narrower hug pill pins 1rem from the top** (`compactLayout="grid"` optional); mega panel stays **grid-wide** above a light dim; mega body: **SiteNav.Featured** / **ReadRow** / **MenuMedia** / **MenuLinkGrid** (paste **Pattern — mega menu**); below `md` mega sections stack; **SiteNav.Links** / **Link** / **Menu** / **MenuSection** / **MenuLink** (Base UI NavigationMenu) with Tab-style **More** overflow when the middle track is tight; `mobile` → **Sheet** `side="end"`. Compose **Button** `render={<a/>}` for anchors. Brand: circular **IconButton** via `icon` + `aria-label` (or wordmark **Button**). Not app chrome. See **ADR-0028**.
32
+ - **Breadcrumb:** the path to the current page — `items` (`{ label, href }` from the root; the last, without `href`, is the current page, `aria-current="page"`). Above `maxItems` (default 4, min 3) the first item and the last `maxItems - 2` stay and the middle folds into a "…" **IconButton** that opens a **Dropdown.Menu** of links — a disclosure (focus to the first link, arrows move, Tab leaves, Escape returns focus), not an ARIA menu; the same on phones. Long labels truncate (full label in the name and `title`). `separator` `chevron` (default) / `slash`; `variant` `sans` (body) / `mono` (eyebrow caps — editorial pages, beside **SectionCaption**). `renderLink={(item) => <Link href={item.href} />}` routes every crumb and menu link. `className` is layout only. Not main navigation (**SiteNav**) or tabs (**Tab**). Paste **Components/Breadcrumb → Pattern — path**, **Pattern — long path**, **Pattern — router links**, or **Pattern — editorial**. See **ADR-0042**.
32
33
  - **NavList:** sectioned secondary nav — **`sections`** + **`activeId`** / **`onSelect`**, or **NavList.Section** + **NavList.Item**. Inset pills via **Button** `layout="nav"` + **ButtonIcon**; optional counts; **`labelAlign="brand"`** reserves a 56px first-section band. Not primary navigation. See **ADR-0019**.
33
34
  - **FloatingActionButton:** compact vertical action menu — `items` (2–4) + `onAction`; optional trigger/open icons, labels, and backdrop. Composes **IconButton** for trigger/actions; Motion `offsetPath` opens straight above the trigger; visible labels stay beside every icon. `className` is placement only. See **ADR-0024**.
34
35
  - **Tab:** responsive peer-page/view navigation — **Tab.Group** + **Tab** (`tablist` / `tab`); inactive labels are regular-weight `text-muted`, active labels are medium-weight `text-fg` with the shared Motion bottom rule, and the muted pill fill is hover-only; optional `count`. Width overflow reserves a **More** Dropdown; selecting a hidden tab promotes it into the final visible slot before More. Not **SegmentedControl** (settings choice) or **Chip** (filter). See **ADR-0022**.
@@ -37,12 +38,12 @@ Storybook paths such as **Components/Button/Button → Pattern — …** name a
37
38
  - **Accordion:** generic expand/collapse — **leading**, **label**, **trailing**, built-in chevron, `motion-collapse` panel. Variants: `list`, `capsule`, `plain`; **`flush`** (plain only) drops the inset so labels line up with surrounding text — filter side panels. A closed panel turns `visibility: hidden` once it folds, so Tab skips its controls. **TaskRows** is the task-status pattern on top.
38
39
  - **DescriptionList:** a list of facts — `dl` > **DescriptionList.Item** (`div` > `dt` `name` + `dd` value). `layout` `inline` (value beside the name, 2 : 3 tracks, default) or `stacked` (value under it — tags, actions), per list or per item. `rule` `solid` (default) / `dotted` (quieter — beside caption rules) / `none`. `variant` `sans` (caption name, body value) or `mono` (eyebrow name, mono caps value, tabular figures — post metadata). Values take text, `<time>`, **Badge** tags, or **Button** actions; several wrap in a row. Not form fields (**Field**), metrics (**Stat**), or status rows (**TaskRows**). Paste **Components/DescriptionList → Pattern — post metadata** or **Pattern — details**. See **ADR-0041**.
39
40
  - **SectionCaption:** the small uppercase mono caption over a page column ("/ Metadata", "/ Article", "/ Filters") — `type-eyebrow` on a `border-border-emphasized` rule; the "/" `marker` is `aria-hidden`. `as` `h2` (default — pair `id` with the region's `aria-labelledby`) or `p` (labels a column that has its own heading); `end` holds a **Button** `size="xs"` (Clear all) without growing the row; `rule` / `marker` can be turned off. Write `children` in sentence case. Copy **Components/SectionCaption → Pattern — column caption**. See **ADR-0041**.
40
- - **Prose:** long-form content set from plain HTML elements (rendered markdown) — headings, paragraphs, lists, quotes, inline code, code blocks, rules, images, figures, and tables, with the spacing between them. `size` `lg` (default — `type-reading`, headings a step up) or `md` (`type-body`, denser notes); `measure` `reading` (40rem, default) or `none`; `as` `div` / `article` / `section`. Wide tables scroll in their own box. Map markdown `#` to `h2` under a page `h1`, and links to **TextLink** (`external` for other sites). Never add classes to the elements inside. Paste **Components/Prose → Pattern — article body**; the full page is **Guides/Post page → Pattern — post page**. See **ADR-0041**.
41
- - **IndexList:** editorial index for list pages (blog, case studies) — **IndexList.Item** rows with `meta` (a `<time>`), a linked `title` (`href`; a quiet **TextLink** in an `h2` by default — `titleAs`), and an optional `preview` an **IconButton** disclosure expands under the title. Optional `captions={{ meta, title }}` in sentence case (`type-eyebrow`, the "/" marker and the **SectionCaption** rule, `aria-hidden`) share the rows' tracks. Two tracks (7.5rem meta, title) once the list is 32rem wide (container query); one column below, captions hidden. `size` `lg` (type-display-3, default) / `md` / `sm`. `empty` replaces the rows when there are none. `className` is layout only. Not tabular data — **Table** is planned. Paste **Components/IndexList → Pattern — post index**; with filters, **Guides/Filter panel → Pattern — filtered index**. See **ADR-0040**.
41
+ - **Prose:** long-form content set from plain HTML elements (rendered markdown) — headings, paragraphs, lists, quotes, inline code, code blocks, rules, images, figures, and tables, with the spacing between them. `size` `lg` (default — `type-reading`, headings a step up) or `md` (`type-body`, denser notes); `measure` `reading` (40rem, default) or `none`; `as` `div` / `article` / `section`. Wide tables scroll in their own box. Map markdown `#` to `h2` under a page `h1`, and links to **TextLink** (`external` for other sites). Never add classes to the elements inside. Paste **Components/Prose → Pattern — article body**; the full page is **Guides/Post page → Pattern — post page** (`metadataSide` `start` / `end` and `metadataColumns` 4 / 3 place the panel — never edit its classes). See **ADR-0041**.
42
+ - **IndexList:** editorial index for list pages (blog, case studies) — **IndexList.Item** rows with `meta` (a `<time>`), a linked `title` (`href`; a quiet **TextLink** in an `h2` by default — `titleAs`), and an optional `preview` an **IconButton** disclosure expands under the title. Optional `captions={{ meta, title }}` in sentence case (`type-eyebrow`, the "/" marker and the **SectionCaption** rule, `aria-hidden`) share the rows' tracks. Two tracks (7.5rem meta, title) once the list is 32rem wide (container query); one column below, captions hidden. `size` `lg` (type-display-3, default) / `md` / `sm`. `empty` replaces the rows when there are none. `className` is layout only. Not tabular data — **Table** is planned. Paste **Components/IndexList → Pattern — post index**; with filters, **Guides/Filter panel → Pattern — filtered index** (a post's `facets` take one option or several per group; `defaultSelection` opens the page filtered, `selection` + `onSelectionChange` hold it in the URL). See **ADR-0040**.
42
43
  - **Card:** `Card.Header`, `Card.Body`, `Card.Footer`. Layout cards (`padding="none"`): header/footer on the shell; **`Card.Header`** is two horizontal slots (`start` | `end`) at **16px** (`px-4`). **`end` is always trailing** (`ml-auto`) — overflow / **MoreMenu** stays on the right even when `start` is omitted. **`Card.Body` is a square composition slot** with a **2px** horizontal gutter — no inner pad, no radius, **no default fill**. The **occupant** dictates how the body region looks; use **`cardLayoutBodyOccupantInsetXClasses`** (`px-3.5` / 14px) + **`cardLayoutBodyOccupantPadYClasses`** (`py-[16px]` / literal 16px vertical) when body content should align with header/footer (2px + 14px = 16px horizontal). Shape config lives in **`src/theme/shape.css`**: shell **`--radius-card-shell: 16px`**, body occupant **`--radius-card-body: 14px`**. Radius-only media → **`cardLayoutBodyOccupantRadiusClasses`**; inset gray/chart wells → **`cardLayoutBodyOccupantWellClasses`** or **`cardLayoutBodyOccupantDotGridWellClasses`**. A headerless layout **Card.Body** densifies the shell top to 2px (`pt-[2px]`) — same density as footerless / `bodyTerminal` bottom; **`headerless`** is only an override for wrapped slot composition. A direct terminal **`Card.Body`** automatically fills stretched cards and gets the 2px bottom shell inset; **`bodyTerminal`** is only an override for wrapped slot composition. Pin a layout height on the root (`className`) with Header + Body + Footer — Header/Footer stay put; **Card.Body** scrolls (2px gutter unchanged; no new variant). See **Components/Card/Card → Pattern — pinned height**. `variant="surface"` is elevated; **`variant="outlined"`** uses **`shadow-soft-card`** with no stroke (same elevation as layout `surface`). Radius stays **`--radius-card-shell`**. Default **`shape="rounded"`**. **`shape="flush"`** only when a parent owns outer chrome. Simple cards: `padding="md"`.
43
44
  - **Input:** pill-only. Optional leading `icon`; trailing `endBadge` or status / `loading`. Hero search with inset button → **Search** molecule. See **ADR-0006**.
44
45
  - **PromptBar:** wide prompt pill — inline **TextArea** (one line when empty, grows through 3 lines, then scrolls) and an inset **IconButton** `sm` send control. Enter sends; Shift+Enter inserts a newline; send stays disabled until the draft has text. Optional `start` (brand mark — **Avatar** `size="md"`) and `end` (extra inset **IconButton** `sm`) slots. No new Button role. Copy **Components/PromptBar → Pattern — prompt bar**. The site assistant on the marketing pages is **ChatDock**, not a hand-pinned PromptBar. Do not restyle **SiteNav**. Full contract: **docs/component-contracts.md → PromptBar**.
45
- - **ChatDock:** the site assistant — a **PromptBar** pinned to the bottom of the page grid (`--grid-max: 40rem`, under **SiteNav**) with the brand `mark`; hovering shows `suggestions` that carry a `prompt` as **Button** `secondary` pills above it; focusing, clicking, or typing opens a non-modal chat window around the same composer (shared overlay header with `title` / `subtitle` / close, `greeting`, `messages` (visitor turns on `bg-brand-tint`, replies as plain text), `thinking` row, suggestion rows — **Button** `layout="row"` — until the first message (a row that sends hands focus to the composer as the rows leave), `disclaimer` under the composer). One **PromptBar** serves both states: the window grows up out of the bar and Escape or close folds it back; either reverses the other midway. Below `md` it fills the screen, rising from the bottom edge. Reduced motion crossfades. `followUps` (same items as `suggestions`; `icon` not shown — inline rows lead with **CornerDownRight**, composer pills are text only) show under the latest reply only — hidden while `thinking`, gone the moment the visitor sends; `followUpsPlacement` is `inline` (default, rows in the thread, 44px on phones) or `composer` (pills above the composer). Under each finished reply, a row: **Copy** (`copyText`), thumbs (`onMessageFeedback`, on replies that carry `feedback` — `null` until the visitor votes; **IconButton** `pressed`), and a muted score (`meta: { label, description? }`, e.g. "Match 99%") — each part on its own; shown on hover or focus where the pointer can hover, always on touch; it keeps its place so the thread never moves, and stays empty while the latest reply is still arriving. Never put actions inside `content`. `gate` puts a multi-step form — **ChatDock.Gate** (header with previous · "2 of 4" · next · close, scrolling step body, footer with Cancel · Next) — in the composer's place, conversation above: setting it opens the window and takes focus; composer, suggestion rows, follow-ups, and disclaimer step aside; capped so the latest reply stays in view; Escape is the gate's; the window's close keeps it and its progress. Phones keep the window inside the area a keyboard leaves visible. Start a project runs as a gate, not a dialog — paste **Pattern — start a project gate** and return it from **Pattern — chat dock**'s `renderStartProject`. Never put a form inside `content`. The app owns `messages`, `followUps`, votes, the gate's open state, and `onSend`; suggestions without a `prompt` (for example **Start a project**) only call `onSuggestionSelect`. No full-page chat, no header send/expand arrow, no voice. `placement="inline"` is for previews only. Copy **Components/ChatDock → Pattern — chat dock** (it holds the follow-up state, the votes, and Start a project). See **ADR-0039**. Full contract: **docs/component-contracts.md → ChatDock**.
46
+ - **ChatDock:** the site assistant — a **PromptBar** pinned to the bottom of the page grid (`--grid-max: 40rem`, under **SiteNav**) with the brand `mark`; hovering shows `suggestions` that carry a `prompt` as **Button** `secondary` pills above it; focusing, clicking, or typing opens a non-modal chat window around the same composer (shared overlay header with `title` / `subtitle` / close, `greeting`, `messages` (visitor turns on `bg-brand-tint`, replies as plain text), `thinking` row, suggestion rows — **Button** `layout="row"` — until the first message (a row that sends hands focus to the composer as the rows leave), `disclaimer` under the composer). One **PromptBar** serves both states: the window grows up out of the bar and Escape or close folds it back; either reverses the other midway. Below `md` it fills the screen, rising from the bottom edge. Reduced motion crossfades. `followUps` (same items as `suggestions`; `icon` not shown — inline rows lead with **CornerDownRight**, composer pills are text only) show under the latest reply only — hidden while `thinking`, gone the moment the visitor sends; `followUpsPlacement` is `inline` (default, rows in the thread, 44px on phones) or `composer` (pills above the composer). Under each finished reply, a row: **Copy** (`copyText`), thumbs (`onMessageFeedback`, on replies that carry `feedback` — `null` until the visitor votes; **IconButton** `pressed`), and a muted score (`meta: { label, description? }`, e.g. "Match 99%") — each part on its own; shown on hover or focus where the pointer can hover, always on touch; it keeps its place so the thread never moves, and stays empty while the latest reply is still arriving. Never put actions inside `content`. `gate` hands the window to a multi-step form — **ChatDock.Gate**: one header (step title, subtitle, previous · "2 of 4" · next · close), a step body that scrolls, the footer (start slot, Cancel, Next) on the window's bottom edge, and `pending` ("Sending…") / `error` lines over it. Setting it opens the window and takes focus; the window's header, conversation, composer, rows, and disclaimer wait behind it, and the fixed window is modal (scrim, page inert, Tab kept inside). The gate's close, Escape, and the scrim fold the window and keep the gate with its progress; Cancel leaves it and the conversation comes back. Phones keep the window inside the area a keyboard leaves visible. Start a project runs as a gate, not a dialog — paste **Pattern — start a project gate** (its `onSubmit` returns a promise; a rejection keeps the answers and offers Try again) and return it from **Pattern — chat dock**'s `renderStartProject`. Never put a form inside `content`. The app owns `messages`, `followUps`, votes, the gate's open state, and `onSend`; suggestions without a `prompt` (for example **Start a project**) only call `onSuggestionSelect`. No full-page chat, no header send/expand arrow, no voice. `placement="inline"` is for previews only. Copy **Components/ChatDock → Pattern — chat dock** (it holds the follow-up state, the votes, and Start a project). See **ADR-0039**. Full contract: **docs/component-contracts.md → ChatDock**.
46
47
  - **TextArea:** element-radius multiline — same optional `label`, `description`, `status`, `message`, and `loading` as **Input**; default `resize="vertical"`. See **ADR-0006**.
47
48
  - **Field:** label/layout wrapper — `orientation="vertical" | "horizontal"`; never required. Validation stays on **Input** / **TextArea**. See **ADR-0006**.
48
49
  - **Checkbox:** boolean toggle — label row + optional `description`, `indeterminate`, `status` / `message`, and `loading`. **`count`** puts a muted number at the row's end (filter results, styled like **NavList** counts); screen readers hear it after the label in parentheses — "Guides (2)", or "Guides (2 posts)" with `countLabel`. Never write the count into `label`. See **ADR-0006**.
@@ -51,9 +52,9 @@ Storybook paths such as **Components/Button/Button → Pattern — …** name a
51
52
  - **Switch:** instant on/off — pill track + sliding thumb; `layout="settings"` for label-left rows. See **ADR-0006**.
52
53
  - **CheckboxGroup:** multi-select options — `values` / `onValuesChange`, vertical or horizontal layout, group validation band. **CheckboxGroup.Item** takes `count` / `countLabel` like **Checkbox**. A filter side panel — **Accordion** `flush` groups, counts, Clear all, a **Sheet** on phones — is **Guides/Filter panel → Pattern — filtered index**.
53
54
  - **Select:** pill trigger matched to **Input** shell + floating listbox (**Dropdown.Menu** / **Dropdown.Item**). **`size="sm"`** in Card headers beside **Chip sm**. Options support **`start`** / **`end`** slots for icons and shortcuts. See **Components/Select**, **Components/Dropdown**.
54
- - **Dropdown:** shared menu panel (`p-0.5` inset) + three-slot rows (`start` | label | `end`) — composed by **Select**; action menus via **MoreMenu**.
55
+ - **Dropdown:** shared menu panel (`p-0.5` inset) + three-slot rows (`start` | label | `end`) — composed by **Select**; action menus via **MoreMenu**. **Dropdown.Item** `render={<a href />}` makes a row a link (a menu of links — **Breadcrumb**'s collapsed path).
55
56
  - **MoreMenu:** kebab **IconButton** + floating **Dropdown** action menu — **Card.Header** `end` (right) only; never left-align overflow. Menu right-aligns to trigger; expanded trigger retains the ghost hover surface; **`items`** + **`onAction`**. Sizes **`xs` | `sm` | `md` | `lg`** (**IconButton** scale). Not for single-select — **Select** / **SegmentedControl**.
56
- - **Toast:** temporary notification organism — mount one **`Toaster`** at the app root and call **`toast.add()`** / `update()` / `dismiss()`. Six `position` values (top/bottom × left/center/right), default five-item layered deck (newest interactive in front, older surfaces offset behind), semantic tones, optional action (**Button**) and dismiss (**IconButton**). Errors announce as `alert`; other tones as `status`. `duration: null` persists. Blocking decisions use **AlertDialog**. See **ADR-0025**.
57
+ - **Toast:** temporary notification organism — mount one **`Toaster`** at the app root (it renders nothing on the server and mounts its list after hydration, so a layout can hold it) and call **`toast.add()`** / `update()` / `dismiss()`. Six `position` values (top/bottom × left/center/right), default five-item layered deck (newest interactive in front, older surfaces offset behind), semantic tones, optional action (**Button**) and dismiss (**IconButton**). Errors announce as `alert`; other tones as `status`. `duration: null` persists. Blocking decisions use **AlertDialog**. See **ADR-0025**.
57
58
  - **Confetti:** celebratory burst — mount one **`ConfettiProvider`**. After an async success, render the confirmation surface and call **`useConfettiOnMount(options?)`** there (once per mount; a ref skips StrictMode and re-renders). **`useConfetti().fire(options?)`** is the same burst when the moment is not a mount. `origin` is an element, a ref (center of the box), or a viewport `{ x, y }` point; default is the viewport center. Confirmation pages pass the top-center of the viewport and a wide `spread`. Overlapping bursts are kept. Removed `duration + 0.5s` after start. Reduced motion: `fire()` does nothing. Colors default to chart categorical 1–7 (`confettiDefaultColors`). There is no trigger component. **IntakeConfirmation** is the reference composition. See **ADR-0031**.
58
59
  - **Dialog:** modal overlay — portal scrim, focus trap, scroll lock, **medium** enter/exit; composes **Card** shell + **IconButton** close. **`Dialog.Content`** — `title`, `description`, `footer`, `size` (`sm` | `md` | `lg`); header/footer pinned, body scrolls; **no section hairlines** — brief/direct copy + `gap-4` section rhythm. **`AlertDialog`** — blocking confirm with cancel + confirm **Button** roles; `confirmRole="destructive"` for irreversible actions; scrim dismiss off by default.
59
60
  - **IntakeModal:** full-screen Start a project shell — typographic **WM** + **WhatMatters** wordmark (no separate logo asset), **IconButton** close (Lucide **X**, name **Close**), **StepProgress** (navy segments, pill label **Step N of 4**), pinned **Back** / **Continue** footer. Focus trap, Escape, scroll lock, `aria-modal`. **SelectableCard** is the multi-select grid (2 columns mobile, 3 from `lg`) with a `toggle` slot for hugged **SegmentedControl** and a **Badge** `iconOnly` check. **PillGroup** is the wrapping radio pills; **Not sure yet** uses existing **Badge** neutral `emphasis="muted"` — no new Badge emphasis. **IntakeForm** is **Field** + **Input** / **TextArea** (optional Link URL); required fields show `status` + `message` on blur; company and link stay optional; the details counter is the field description. **CalEmbed** is the calendar frame plus **TextLink** skip; with no embed mounted it shows a visitor-facing empty state (`emptyTitle`, `emptyDescription`), never developer notes. **CalEmbed.Skip** is that same skip; in a **Card** gate put it in **Card.Footer** and pass `skip={false}` so the in-body link is omitted. **IntakeConfirmation** (`booked` | `emailed`) calls **`useConfettiOnMount`** with `intakeConfettiColors` (includes `--color-brand`); optional `bookingTime` / `videoHref` / `googleCalendarHref` / `icsHref` reuse **TextLink** and **Button**. Copy **Components/IntakeModal → Pattern — intake modal** and compose the steps in the app. See **ADR-0038**.
package/docs/exports.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "@thewhatmatters/wmds",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "import": "import { Button } from \"@thewhatmatters/wmds\";",
5
5
  "styles": [
6
6
  "@thewhatmatters/wmds/styles.css",
@@ -67,6 +67,22 @@
67
67
  "components-badge--with-avatar"
68
68
  ]
69
69
  },
70
+ {
71
+ "name": "Breadcrumb",
72
+ "tier": "molecule",
73
+ "category": "Navigation",
74
+ "summary": "The path to the current page; a long path folds its middle into a menu of links.",
75
+ "storybook": {
76
+ "title": "Components/Breadcrumb",
77
+ "docs": "?path=/docs/components-breadcrumb--docs"
78
+ },
79
+ "patterns": [
80
+ "components-breadcrumb--editorial",
81
+ "components-breadcrumb--long-path",
82
+ "components-breadcrumb--path",
83
+ "components-breadcrumb--router-links"
84
+ ]
85
+ },
70
86
  {
71
87
  "name": "Button",
72
88
  "tier": "atom",
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — flush
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — flush
2
2
  // Storybook: Components/Accordion → Pattern — flush (?path=/story/components-accordion--flush)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — dropdown row
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — dropdown row
2
2
  // Storybook: Components/Avatar → Pattern — dropdown row (?path=/story/components-avatar--dropdown-row)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — outline tags
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — outline tags
2
2
  // Storybook: Components/Badge → Pattern — outline tags (?path=/story/components-badge--outline-tags)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — with avatar
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — with avatar
2
2
  // Storybook: Components/Badge → Pattern — with avatar (?path=/story/components-badge--with-avatar)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -0,0 +1,9 @@
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — editorial
2
+ // Storybook: Components/Breadcrumb → Pattern — editorial (?path=/story/components-breadcrumb--editorial)
3
+ // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
+
5
+ import { Breadcrumb, type BreadcrumbItemDef } from "@thewhatmatters/wmds";
6
+
7
+ export function PostBreadcrumb({ items }: { items: BreadcrumbItemDef[] }) {
8
+ return <Breadcrumb items={items} variant="mono" separator="slash" />;
9
+ }
@@ -0,0 +1,9 @@
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — long path
2
+ // Storybook: Components/Breadcrumb → Pattern — long path (?path=/story/components-breadcrumb--long-path)
3
+ // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
+
5
+ import { Breadcrumb, type BreadcrumbItemDef } from "@thewhatmatters/wmds";
6
+
7
+ export function DeepBreadcrumb({ items }: { items: BreadcrumbItemDef[] }) {
8
+ return <Breadcrumb items={items} maxItems={4} />;
9
+ }
@@ -0,0 +1,9 @@
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — path
2
+ // Storybook: Components/Breadcrumb → Pattern — path (?path=/story/components-breadcrumb--path)
3
+ // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
+
5
+ import { Breadcrumb, type BreadcrumbItemDef } from "@thewhatmatters/wmds";
6
+
7
+ export function PageBreadcrumb({ items }: { items: BreadcrumbItemDef[] }) {
8
+ return <Breadcrumb items={items} />;
9
+ }
@@ -0,0 +1,10 @@
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — router links
2
+ // Storybook: Components/Breadcrumb → Pattern — router links (?path=/story/components-breadcrumb--router-links)
3
+ // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
+
5
+ import Link from "next/link";
6
+ import { Breadcrumb, type BreadcrumbItemDef } from "@thewhatmatters/wmds";
7
+
8
+ export function RoutedBreadcrumb({ items }: { items: BreadcrumbItemDef[] }) {
9
+ return <Breadcrumb items={items} renderLink={(item) => <Link href={item.href} />} />;
10
+ }
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — copy button
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — copy button
2
2
  // Storybook: Components/Button/Button → Pattern — copy button (?path=/story/components-button-button--copy-button)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — destructive action
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — destructive action
2
2
  // Storybook: Components/Button/Button → Pattern — destructive action (?path=/story/components-button-button--destructive-action)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — external link
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — external link
2
2
  // Storybook: Components/Button/Button → Pattern — external link (?path=/story/components-button-button--external-link)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — ghost action
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — ghost action
2
2
  // Storybook: Components/Button/Button → Pattern — ghost action (?path=/story/components-button-button--ghost-action)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — inverse action
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — inverse action
2
2
  // Storybook: Components/Button/Button → Pattern — inverse action (?path=/story/components-button-button--inverse-action)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — link
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — link
2
2
  // Storybook: Components/Button/Button → Pattern — link (?path=/story/components-button-button--link-render)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — submit / async
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — submit / async
2
2
  // Storybook: Components/Button/Button → Pattern — submit / async (?path=/story/components-button-button--multi-state-badge)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — outline mono
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — outline mono
2
2
  // Storybook: Components/Button/Button → Pattern — outline mono (?path=/story/components-button-button--outline-mono)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — primary action
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — primary action
2
2
  // Storybook: Components/Button/Button → Pattern — primary action (?path=/story/components-button-button--primary-action)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — row (hug)
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — row (hug)
2
2
  // Storybook: Components/Button/Button → Pattern — row (hug) (?path=/story/components-button-button--row-hug)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — row
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — row
2
2
  // Storybook: Components/Button/Button → Pattern — row (?path=/story/components-button-button--row-layout)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — secondary action
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — secondary action
2
2
  // Storybook: Components/Button/Button → Pattern — secondary action (?path=/story/components-button-button--secondary-action)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — suggestion pills
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — suggestion pills
2
2
  // Storybook: Components/Button/Button → Pattern — suggestion pills (?path=/story/components-button-button--suggestion-pills)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — with count
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — with count
2
2
  // Storybook: Components/Button/Button → Pattern — with count (?path=/story/components-button-button--with-count)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — with icon
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — with icon
2
2
  // Storybook: Components/Button/Button → Pattern — with icon (?path=/story/components-button-button--with-icon)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — vertical actions
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — vertical actions
2
2
  // Storybook: Components/Button/FloatingActionButton → Pattern — vertical actions (?path=/story/components-button-floatingactionbutton--vertical-actions)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — skip in footer
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — skip in footer
2
2
  // Storybook: Components/CalEmbed → Pattern — skip in footer (?path=/story/components-calembed--pattern-skip-in-footer)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Example — body slot (occupancy history)
1
+ // @thewhatmatters/wmds@0.4.3 · Example — body slot (occupancy history)
2
2
  // Storybook: Components/Card/Card → Example — body slot (occupancy history) (?path=/story/components-card-card--body-slot-occupancy-history)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — headerless media
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — headerless media
2
2
  // Storybook: Components/Card/Card → Pattern — headerless media (?path=/story/components-card-card--headerless-media)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — outlined layout
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — outlined layout
2
2
  // Storybook: Components/Card/Card → Pattern — outlined layout (?path=/story/components-card-card--outlined-layout)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — pinned height
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — pinned height
2
2
  // Storybook: Components/Card/Card → Pattern — pinned height (?path=/story/components-card-card--pinned-height)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — selectable cards
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — selectable cards
2
2
  // Storybook: Components/Card/SelectableCard → Pattern — selectable cards (?path=/story/components-card-selectablecard--selectable-cards-pattern)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — matrix heatmap
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — matrix heatmap
2
2
  // Storybook: Components/Chart → Pattern — matrix heatmap (?path=/story/components-chart--audience-activity-heatmap)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — Cartesian no-data gaps
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — Cartesian no-data gaps
2
2
  // Storybook: Components/Chart → Pattern — Cartesian no-data gaps (?path=/story/components-chart--cartesian-no-data-gaps)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — occupancy history in Card
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — occupancy history in Card
2
2
  // Storybook: Components/Chart → Pattern — occupancy history in Card (?path=/story/components-chart--occupancy-history-in-card)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — occupancy KPI in Card
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — occupancy KPI in Card
2
2
  // Storybook: Components/Chart → Pattern — occupancy KPI in Card (?path=/story/components-chart--occupancy-in-card)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — distribution strip
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — distribution strip
2
2
  // Storybook: Components/Chart → Pattern — distribution strip (?path=/story/components-chart--post-reach-distribution)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — ranked audience breakdown
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — ranked audience breakdown
2
2
  // Storybook: Components/Chart → Pattern — ranked audience breakdown (?path=/story/components-chart--ranked-audience-breakdown)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — 100-unit composition
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — 100-unit composition
2
2
  // Storybook: Components/Chart → Pattern — 100-unit composition (?path=/story/components-chart--unit-grid-composition)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — chat dock
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — chat dock
2
2
  // Storybook: Components/ChatDock → Pattern — chat dock (?path=/story/components-chatdock--pattern-chat-dock)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — start a project gate
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — start a project gate
2
2
  // Storybook: Components/ChatDock → Pattern — start a project gate (?path=/story/components-chatdock--pattern-start-project-gate)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -54,12 +54,12 @@ export interface StartProjectIntake {
54
54
  export interface StartProjectGateProps {
55
55
  /** What the assistant already picked up from the conversation, or `{}`. */
56
56
  request: { services?: string[]; budget?: string };
57
- /** Closes the gate — Cancel, Close, or Escape. */
57
+ /** Leaves the gate — its Cancel. The gate's close and Escape fold the window and keep the gate, with its progress. */
58
58
  close: () => void;
59
59
  /** Ends the gate; the conversation shows the answers and the confirmation. */
60
60
  complete: (answers: ChatQaPair[], confirmation: string) => void;
61
- /** Sends the intake. */
62
- onSubmit: (intake: StartProjectIntake) => void;
61
+ /** Sends the intake. Resolve once it is sent; reject to keep the answers and offer Try again. */
62
+ onSubmit: (intake: StartProjectIntake) => Promise<void>;
63
63
  /** The booking calendar. Call `booked` once the visitor confirms a time. Leave it out to show the empty state. */
64
64
  renderCalendar?: (booked: () => void) => ReactNode;
65
65
  }
@@ -72,6 +72,9 @@ export function StartProjectGate({ request, close, complete, onSubmit, renderCal
72
72
  const [chosen, setChosen] = useState<string[]>(request.services ?? []);
73
73
  const [budget, setBudget] = useState<string | null>(request.budget ?? null);
74
74
  const [about, setAbout] = useState<IntakeAboutValues>(intakeAboutEmpty);
75
+ const [sending, setSending] = useState(false);
76
+ /** How the visitor chose to follow up when the send failed — Try again sends that again. */
77
+ const [failed, setFailed] = useState<"call" | "email" | null>(null);
75
78
 
76
79
  function toggleService(value: string) {
77
80
  setChosen((current) =>
@@ -94,10 +97,21 @@ export function StartProjectGate({ request, close, complete, onSubmit, renderCal
94
97
  const canContinue =
95
98
  step === 1 ? chosen.length > 0 : step === 2 ? budget != null : step === 3 ? isIntakeAboutValid(about) : true;
96
99
 
97
- function finish(followUp: "call" | "email") {
98
- onSubmit({ services: chosen, budget, about, followUp });
99
- // The burst starts at the control the visitor used — the booking or the skip.
100
+ async function finish(followUp: "call" | "email") {
101
+ if (sending) return;
102
+ // The burst starts at the control the visitor used — the booking, the skip, or Try again.
100
103
  const used = document.activeElement;
104
+ setSending(true);
105
+ setFailed(null);
106
+ try {
107
+ await onSubmit({ services: chosen, budget, about, followUp });
108
+ } catch {
109
+ // Every answer stays; the gate shows the error and Try again.
110
+ setFailed(followUp);
111
+ return;
112
+ } finally {
113
+ setSending(false);
114
+ }
101
115
  fire(used instanceof HTMLElement ? { origin: used } : undefined);
102
116
  const firstName = about.name.trim().split(" ")[0];
103
117
  const email = about.email.trim();
@@ -121,6 +135,7 @@ export function StartProjectGate({ request, close, complete, onSubmit, renderCal
121
135
  }
122
136
 
123
137
  const copy = steps[step - 1];
138
+ const lastStep = step === steps.length;
124
139
 
125
140
  return (
126
141
  <ChatDock.Gate
@@ -129,11 +144,33 @@ export function StartProjectGate({ request, close, complete, onSubmit, renderCal
129
144
  subtitle={copy.subtitle}
130
145
  step={step}
131
146
  stepCount={steps.length}
132
- onPrevious={() => setStep((current) => Math.max(1, current - 1))}
133
- onNext={step < steps.length ? () => setStep((current) => current + 1) : undefined}
147
+ onPrevious={() => {
148
+ setFailed(null);
149
+ setStep((current) => Math.max(1, current - 1));
150
+ }}
151
+ onNext={
152
+ !lastStep
153
+ ? () => setStep((current) => current + 1)
154
+ : failed != null
155
+ ? () => {
156
+ void finish(failed);
157
+ }
158
+ : undefined
159
+ }
160
+ continueLabel={lastStep && failed != null ? "Try again" : undefined}
134
161
  canContinue={canContinue}
162
+ pending={sending}
163
+ error={lastStep && failed != null ? "We couldn't send your answers. Check your connection and try again." : undefined}
135
164
  onClose={close}
136
- footerStart={step === 4 ? <CalEmbed.Skip onSkip={() => finish("email")} /> : undefined}
165
+ footerStart={
166
+ lastStep ? (
167
+ <CalEmbed.Skip
168
+ onSkip={() => {
169
+ void finish("email");
170
+ }}
171
+ />
172
+ ) : undefined
173
+ }
137
174
  >
138
175
  {step === 1 ? (
139
176
  <div className="flex w-full flex-col" role="group" aria-label="Services">
@@ -174,7 +211,13 @@ export function StartProjectGate({ request, close, complete, onSubmit, renderCal
174
211
  </div>
175
212
  ) : null}
176
213
  {step === 3 ? <IntakeForm values={about} onChange={setAbout} /> : null}
177
- {step === 4 ? <CalEmbed skip={false}>{renderCalendar?.(() => finish("call"))}</CalEmbed> : null}
214
+ {lastStep ? (
215
+ <CalEmbed skip={false}>
216
+ {renderCalendar?.(() => {
217
+ void finish("call");
218
+ })}
219
+ </CalEmbed>
220
+ ) : null}
178
221
  </ChatDock.Gate>
179
222
  );
180
223
  }
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — Q&A with reply
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — Q&A with reply
2
2
  // Storybook: Components/ChatQa → Pattern — Q&A with reply (?path=/story/components-chatqa--pattern-qa-with-reply)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — default
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — default
2
2
  // Storybook: Components/Checkbox/Checkbox → Pattern — default (?path=/story/components-checkbox-checkbox--default)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — with count
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — with count
2
2
  // Storybook: Components/Checkbox/Checkbox → Pattern — with count (?path=/story/components-checkbox-checkbox--with-count)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4
 
@@ -1,4 +1,4 @@
1
- // @thewhatmatters/wmds@0.4.1 · Pattern — with description
1
+ // @thewhatmatters/wmds@0.4.3 · Pattern — with description
2
2
  // Storybook: Components/Checkbox/Checkbox → Pattern — with description (?path=/story/components-checkbox-checkbox--with-description)
3
3
  // Show code — copy verbatim and keep this header; upgrades find pasted patterns by it.
4
4