@servicetitan/json-render-react 0.2.0 → 0.3.0

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 (86) hide show
  1. package/dist/catalog/context/catalog-prompt.md +229 -0
  2. package/dist/catalog/context/schema.json +56 -0
  3. package/dist/catalog/core/catalog-prompt.md +104 -90
  4. package/dist/catalog/core/schema.json +0 -1
  5. package/dist/catalog/marketing/catalog-prompt.md +104 -90
  6. package/dist/catalog/marketing/schema.json +0 -1
  7. package/dist/context/catalog/catalog-def.d.ts +50 -0
  8. package/dist/context/catalog/context-card.catalog.d.ts +19 -0
  9. package/dist/context/catalog/context-card.d.ts +8 -0
  10. package/dist/context/catalog/context-data-table.catalog.d.ts +34 -0
  11. package/dist/context/catalog/context-data-table.css.d.ts +2 -0
  12. package/dist/context/catalog/context-data-table.d.ts +6 -0
  13. package/dist/context/catalog/context-file-list.catalog.d.ts +11 -0
  14. package/dist/context/catalog/context-file-list.css.d.ts +16 -0
  15. package/dist/context/catalog/context-file-list.d.ts +6 -0
  16. package/dist/context/catalog/context-list.catalog.d.ts +48 -0
  17. package/dist/context/catalog/context-list.css.d.ts +16 -0
  18. package/dist/context/catalog/context-list.d.ts +6 -0
  19. package/dist/context/catalog/context-metric-grid.catalog.d.ts +22 -0
  20. package/dist/context/catalog/context-metric-grid.css.d.ts +13 -0
  21. package/dist/context/catalog/context-metric-grid.d.ts +6 -0
  22. package/dist/context/catalog/context-note-list.catalog.d.ts +15 -0
  23. package/dist/context/catalog/context-note-list.d.ts +6 -0
  24. package/dist/context/catalog/context-overview.catalog.d.ts +8 -0
  25. package/dist/context/catalog/context-overview.css.d.ts +2 -0
  26. package/dist/context/catalog/context-overview.d.ts +6 -0
  27. package/dist/context/catalog/context-panel.catalog.d.ts +6 -0
  28. package/dist/context/catalog/context-panel.css.d.ts +1 -0
  29. package/dist/context/catalog/context-panel.d.ts +6 -0
  30. package/dist/context/catalog/context-tabular-list.catalog.d.ts +19 -0
  31. package/dist/context/catalog/context-tabular-list.css.d.ts +17 -0
  32. package/dist/context/catalog/context-tabular-list.d.ts +6 -0
  33. package/dist/context/catalog/index.d.ts +10 -0
  34. package/dist/context/catalog/schemas.d.ts +151 -0
  35. package/dist/context/components/collapsible-card.css.d.ts +41 -0
  36. package/dist/context/components/collapsible-card.d.ts +14 -0
  37. package/dist/context/components/context-ai-summary.css.d.ts +5 -0
  38. package/dist/context/components/context-ai-summary.d.ts +4 -0
  39. package/dist/context/components/context-chip-tone.d.ts +10 -0
  40. package/dist/context/components/context-note-list.css.d.ts +10 -0
  41. package/dist/context/components/context-render-error-boundary.d.ts +21 -0
  42. package/dist/context/constants.d.ts +6 -0
  43. package/dist/context/index.d.ts +4 -0
  44. package/dist/context/validate-context-spec-deep.d.ts +9 -0
  45. package/dist/context/validate-context-spec.d.ts +11 -0
  46. package/dist/core/catalog/alert.catalog.d.ts +2 -0
  47. package/dist/core/catalog/button.catalog.d.ts +3 -0
  48. package/dist/core/catalog/card.catalog.d.ts +2 -0
  49. package/dist/core/catalog/catalog-def.d.ts +33 -19
  50. package/dist/core/catalog/checkbox.catalog.d.ts +1 -0
  51. package/dist/core/catalog/chip.catalog.d.ts +1 -0
  52. package/dist/core/catalog/flex.catalog.d.ts +13 -0
  53. package/dist/core/catalog/grid.catalog.d.ts +4 -0
  54. package/dist/core/catalog/input.catalog.d.ts +1 -0
  55. package/dist/core/catalog/link.catalog.d.ts +2 -0
  56. package/dist/core/catalog/listbox.catalog.d.ts +1 -0
  57. package/dist/core/catalog/radio-group.catalog.d.ts +1 -0
  58. package/dist/core/catalog/schemas.d.ts +33 -19
  59. package/dist/core/catalog/switch.catalog.d.ts +1 -0
  60. package/dist/core/catalog/text.catalog.d.ts +0 -8
  61. package/dist/core/catalog/textarea.catalog.d.ts +1 -0
  62. package/dist/generate.d.ts +4 -4
  63. package/dist/index.d.ts +6 -3
  64. package/dist/json-render-react.js +1820 -705
  65. package/dist/json-render-react.js.map +1 -1
  66. package/dist/marketing/catalog/catalog.d.ts +1 -1
  67. package/dist/marketing/catalog/index.d.ts +1 -1
  68. package/dist/marketing/catalog/scoped-catalogs.d.ts +4 -0
  69. package/dist/registry-utils.d.ts +1 -1
  70. package/dist/registry.d.ts +1 -0
  71. package/dist/renderers/context-spec-renderer.d.ts +6 -0
  72. package/dist/renderers/sectioned-context-renderer.css.d.ts +1 -0
  73. package/dist/renderers/sectioned-context-renderer.d.ts +36 -0
  74. package/dist/renderers/spec-renderer.d.ts +6 -1
  75. package/dist/renderers/types.d.ts +2 -1
  76. package/dist/renderers/wrap-context-registry.d.ts +3 -0
  77. package/dist/scoped-catalog.d.ts +64 -0
  78. package/dist/styles.css +1 -1
  79. package/dist/test/setup-dom.d.ts +1 -0
  80. package/dist/types.d.ts +6 -3
  81. package/package.json +4 -2
  82. package/dist/core/catalog/badge.catalog.d.ts +0 -12
  83. package/dist/core/catalog/badge.css.d.ts +0 -1
  84. package/dist/core/catalog/badge.d.ts +0 -8
  85. package/dist/marketing/catalog/teams.d.ts +0 -4
  86. package/dist/team-catalog.d.ts +0 -26
@@ -46,29 +46,28 @@ For lists where users can add/remove items (todos, carts, etc.), use pushState a
46
46
 
47
47
  IMPORTANT: State paths use RFC 6901 JSON Pointer syntax (e.g. "/todos/0/title"). Do NOT use JavaScript-style dot notation (e.g. "/todos.length" is WRONG). To generate unique IDs for new items, use "$id" instead of trying to read array length.
48
48
 
49
- AVAILABLE COMPONENTS (21):
50
-
51
- - Text: { content: string, variant?: "headline" | "body" | "eyebrow", size?: "small" | "medium" | "large" | "xlarge", subdued?: boolean, el?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" } - Display text. variant: headline=bold title, eyebrow=small label, body=default. size: small/medium/large/xlarge (headline and body only). subdued: deemphasized body text. el: heading element for variant=headline (default h3).
52
- - Card: { background?: "strong" | "stronger", padding?: "0" | "xsmall" | "small" | "medium" | "large" } - White surface with padding; groups related content. background: strong | stronger for elevated surfaces. padding: 0 | xsmall | small | medium (default) | large. [accepts children]
53
- - Flex: { direction?: "row" | "column", gap?: "1" | "2" | "3" | "4" | "5" | "6", alignItems?: "center" | "flex-start" | "flex-end" | "stretch" | "baseline", justifyContent?: "center" | "flex-start" | "flex-end" | "space-between" | "space-around" | "space-evenly", wrap?: "wrap" | "nowrap" | "wrap-reverse", grow?: number, shrink?: number, inline?: boolean } - Flex layout container. gap: 1=4px 2=8px 3=12px 4=16px 5=20px 6=24px. grow: 1=fill available width. alignItems: vertical alignment. justifyContent: horizontal distribution. [accepts children]
54
- - Grid: { templateColumns?: string, gap?: "1" | "2" | "3" | "4" | "5" | "6", bordered?: boolean } - CSS Grid layout. templateColumns: CSS grid-template-columns value — "repeat(3, 1fr)" for equal columns, "200px 1fr" for fixed+flex two-column (e.g. key-value tables), "1fr 1fr 80px" for multi-column tables. Children flow into grid cells left-to-right, top-to-bottom. gap: 1=4px … 6=24px. bordered: true renders a bordered table with row separators and cell padding — use for key-value and data tables. [accepts children]
55
- - Alert: { title: string, status?: "info" | "success" | "warning" | "danger", dismissible?: boolean } - Alert/notification banner. dismissible: true adds a close button that emits a "close" event. [accepts children]
56
- - Divider: { vertical?: boolean, spacing?: "0" | "1" | "2" | "3" | "4" | "5" | "6" | "8" | "12" | "half", alignContent?: "start" | "center" | "end" } - Divider between sections. vertical: true for vertical separator. spacing: gap around the divider. alignContent: alignment of content within the divider.
57
- - Icon: { name: "check-circle" | "in-progress" | "radio-button-unchecked", size?: "small" | "medium" | "large" | "xlarge", color?: string } - Anvil2 icon. name: check-circle=completed checkmark, in-progress=spinning partial circle, radio-button-unchecked=empty circle (pending)
58
- - Badge: { content?: string, offset?: { x?: string, y?: string } } - Unread notification count on a Button or Link. Wrap the interactive element as a child of Badge (the renderer adds position:relative). content: numerical count only (e.g. 3, 99+); omit for dot-only. offset: {x, y} CSS values to fine-tune position on non-square anchors like links. [accepts children]
59
- - Chip: { label: string, size?: "small" | "medium", color?: string, icon?: "check-circle" | "in-progress" | "radio-button-unchecked", removable?: boolean } - Inline label pill for metadata, tags, or categoriesuse for job statuses, not notification counts (use Badge for those). size: small | medium (default). color: any CSS color. icon: optional leading icon. removable: true adds a close button (not available for size=small).
60
- - Link: { label: string, href: string, appearance?: "primary" | "secondary", target?: "_blank" | "_self", ghost?: boolean } - Inline navigation link. href: destination URL. appearance: primary (default) | secondary. target: _blank opens in new tab (shows external-link icon). ghost: quieter styling (primary only).
61
- - ProgressBar: { label: string, value?: number, description?: string, indeterminate?: boolean } - Progress bar showing task completion. value: 0–100 (percentage). Omit value and set indeterminate=true for unknown progress.
49
+ AVAILABLE COMPONENTS (20):
50
+
51
+ - Text: { content: string, variant?: "headline" | "body" | "eyebrow", size?: "small" | "medium" | "large" | "xlarge", subdued?: boolean } - Display text. variant: headline=bold title, eyebrow=small uppercase label, body=default (default variant). size: small | medium (default) | large | xlarge — applies to headline and body only (ignored for eyebrow). subdued: deemphasized secondary text (body only). Use eyebrow+headline pairs for KPI/stat labels above values.
52
+ - Card: { title?: string, description?: string, background?: "strong" | "stronger", padding?: "0" | "xsmall" | "small" | "medium" | "large" } - White surface that groups a logical section of related content. title: optional headline rendered at the top of the card. description: optional subdued line below the title. Children render below the header and are stacked vertically with consistent spacing — you do not need to wrap them in a Flex. Prefer the built-in title/description over standalone Text children for the heading. background: strong | stronger for nested/elevated surfaces. padding: 0 | xsmall | small | medium (default) | large. Do not nest a Card inside another Card. [accepts children]
53
+ - Flex: { direction?: "row" | "column", gap?: "0" | "1" | "2" | "3" | "4" | "5" | "6" | "8" | "12" | "half", alignItems?: "center" | "flex-start" | "flex-end" | "stretch" | "baseline", justifyContent?: "center" | "flex-start" | "flex-end" | "space-between" | "space-around" | "space-evenly", alignSelf?: "auto" | "center" | "flex-start" | "flex-end" | "stretch" | "baseline", wrap?: "wrap" | "nowrap" | "wrap-reverse", grow?: number, shrink?: number, basis?: string, inline?: boolean } - Flex layout container. direction: row (default) | column — always set column when stacking children vertically. gap (spacing token, ~4px per step): half=2px 1=4px 2=8px 3=12px 4=16px 5=20px 6=24px 8=32px 12=48px. alignItems: cross-axis alignment of children. justifyContent: main-axis distribution. When this Flex is itself a child of another Flex, use grow (1=fill available space), shrink, basis (CSS flex-basis e.g. "200px"), and alignSelf to control how it flexes. [accepts children]
54
+ - Grid: { templateColumns?: string, gap?: "0" | "1" | "2" | "3" | "4" | "5" | "6" | "8" | "12" | "half", bordered?: boolean } - CSS Grid layout. templateColumns: CSS grid-template-columns value — "repeat(3, 1fr)" for equal columns, "200px 1fr" for fixed+flex two-column (e.g. key-value tables), "1fr 1fr 80px" for multi-column tables. Children flow into grid cells left-to-right, top-to-bottom. gap (spacing token, ~4px per step): 1=4px 2=8px … 6=24px 8=32px 12=48px (default 2). bordered: true renders a bordered table with row separators and cell padding — use for key-value and data tables. [accepts children]
55
+ - Alert: { title: string, status?: "info" | "success" | "warning" | "danger", dismissible?: boolean, aiMark?: boolean } - Inline notification banner with a required title. status: info (default) | success | warning | danger — danger for errors, warning for cautions, success for confirmations. Put the main message in the alert's children (rendered below the title). dismissible: true adds a close button that emits a "close" event. [accepts children] [events: close]
56
+ - Divider: { vertical?: boolean, spacing?: "0" | "1" | "2" | "3" | "4" | "5" | "6" | "8" | "12" | "half", alignContent?: "start" | "center" | "end" } - Horizontal (default) or vertical rule that separates content sections. vertical: true for a vertical separator between side-by-side content. spacing (space around the divider, ~4px per step): 0 (default) | half | 1–6 | 8 | 12. alignContent: alignment of any divider content (start | center | end). Place between logical sections — not as a top/bottom border of a container.
57
+ - Icon: { name: "check-circle" | "in-progress" | "radio-button-unchecked", size?: "small" | "medium" | "large" | "xlarge", color?: string } - Small status icon. name (only these three are available): check-circle=completed/success checkmark, in-progress=partial circle (active/working), radio-button-unchecked=empty circle (pending/not started). size: small | medium (default) | large | xlarge. color: any CSS color (defaults to the current text color). Commonly paired with status Text in lists and checklists.
58
+ - Chip: { label: string, size?: "small" | "medium", color?: string, icon?: "check-circle" | "in-progress" | "radio-button-unchecked", removable?: boolean } - Inline label pill for metadata, tags, or categories use for job statuses, not notification counts (use a Button/Link badge prop for those). size: small | medium (default). color: any CSS color. icon: optional leading status icon. removable: true adds a close button that emits a "close" event (ignored when size=small). [events: close]
59
+ - Link: { label: string, href: string, appearance?: "primary" | "secondary", target?: "_blank" | "_self", ghost?: boolean, badge?: string } - Inline navigation link (renders an <a>). Emits a "press" event when clicked handle navigation in on.press; href and target are inputs for the host/router rather than native browser navigation. href: destination URL for the host/router. appearance: primary (default) | secondary. target: _blank marks the link as external (shows external-link icon) and lets the host open a new tab if desired. ghost: quieter styling (primary only). badge: optional notification count shown as a superscript (e.g. "5"); use an empty string for a dot-only indicator. [events: press]
60
+ - ProgressBar: { label: string, value?: number, description?: string, indeterminate?: boolean } - Horizontal progress bar showing task completion. label is required (the accessible name for the bar). value: 0–100 percentage filled. Omit value and set indeterminate=true when progress cannot be measured (do not set both). description: optional helper text shown below the bar.
62
61
  - BarChart: { data: Array<{ category: string, value: number }>, orientation?: "vertical" | "horizontal", theme?: "monochrome" | "categorical" | "semantic", height?: number, unit?: string } - Bar chart (vertical or horizontal) with Anvil2 theme. data: [{category, value}]. theme: monochrome (default, ≤4 vars), categorical (5-9 distinct), semantic (success/neutral/warning/danger order). unit: tooltip suffix.
63
62
  - DonutChart: { data: Array<{ category: string, value: number }>, theme?: "monochrome" | "categorical" | "semantic", showLegend?: boolean, height?: number, unit?: string } - Donut chart with optional legend and Anvil2 theme. data: [{category, value}]. theme: monochrome (≤4 vars), categorical (5-9 distinct), semantic (status values).
64
63
  - LineChart: { data: Array<unknown>, series?: Array<string>, theme?: "monochrome" | "categorical" | "semantic", showLegend?: boolean, showDots?: boolean, height?: number, unit?: string } - Line chart for tracking trends over time with optional multiple series. data: array of objects with a "category" key (x-axis label) and one numeric key per series (e.g. [{category: "Jan", revenue: 100, cost: 60}]). series: explicit list of series keys to plot (inferred from data keys if omitted, max 6). theme: monochrome (1-2 series), categorical (3-6 distinct). showLegend: true when multiple series (default true for 2+ series). showDots: true adds data-point markers (default true). unit: tooltip value suffix.
65
- - Button: { label: string, appearance?: "primary" | "secondary" | "ghost", size?: "xsmall" | "small" | "medium" | "large", loading?: boolean, disabled?: boolean } - Clickable button. Every button automatically submits form data when pressed the element key is used as the actionId to identify which button was clicked. loading: true shows a spinner. disabled: true prevents interaction. size: xsmall | small | medium (default) | large.
66
- - Checkbox: { label: string, checked?: boolean, disabled?: boolean } - Checkbox input with label. Use $bindState on checked to track state.
67
- - Input: { label: string, placeholder?: string, type?: "text" | "password" | "email" | "tel" | "url" | "number", value?: string, disabled?: boolean } - Free-text input field for user-typed values (names, notes, numbers, etc). type: text (default), password (masked), email, tel (phone), url, number. Use $bindState on value to bind to state. Prefer this over Select when the value is not from a fixed list.
68
- - Textarea: { label: string, placeholder?: string, description?: string, moreInfo?: string, value?: string, rows?: number, disabled?: boolean } - Multi-line free-text input for long values (notes, descriptions, email body). Use $bindState on value to bind to state. rows: visible row count (default 3).
69
- - Listbox: { label: string, options: Array<{ value: string, label: string }>, value?: string, disabled?: boolean } - Listbox for choosing from a fixed list of known options. Use only when options are predefined. For free-text user input use Input instead.
70
- - RadioGroup: { label: string, options: Array<{ value: string, label: string }>, value?: string, disabled?: boolean } - Group of radio buttons for single selection. Use $bindState on value to capture selection.
71
- - Switch: { label: string, checked?: boolean, disabled?: boolean } - Toggle switch for binary on/off settings. Prefer over Checkbox for enabling/disabling features. Use $bindState on checked to track state.
64
+ - Button: { label: string, appearance?: "primary" | "secondary" | "ghost" | "danger", size?: "xsmall" | "small" | "medium" | "large", loading?: boolean, disabled?: boolean, badge?: string } - Clickable button. Emits a "press" event when clicked; inside a form the host treats press as a submit and uses the element key as the actionId, so no explicit on.press wiring is needed for form submits. appearance: primary | secondary (default) | ghost | danger (destructive actions like delete/cancel). size: xsmall | small | medium (default) | large. loading: true shows a spinner and blocks interaction. disabled: true prevents interaction. badge: optional notification count shown as a superscript (e.g. "3", "99+"); use an empty string for a dot-only indicator. [events: press]
65
+ - Checkbox: { label: string, checked?: boolean, disabled?: boolean } - Checkbox for selecting zero or more independent options that are submitted with a form (one Checkbox per option). For a single on/off setting with immediate effect prefer Switch. Bind state with $bindState on checked; emits a "change" event when toggled. disabled: true prevents toggling. [events: change]
66
+ - Input: { label: string, placeholder?: string, type?: "text" | "password" | "email" | "tel" | "url" | "number", value?: string, disabled?: boolean } - Free-text input field for user-typed values (names, notes, numbers, etc). type: text (default), password (masked), email, tel (phone), url, number. Bind value with $bindState so the typed value is captured in state; emits a "change" event on every keystroke. Prefer this over Listbox when the value is not from a fixed list. disabled: true prevents editing. [events: change]
67
+ - Textarea: { label: string, placeholder?: string, description?: string, moreInfo?: string, value?: string, rows?: number, disabled?: boolean } - Multi-line free-text input for long values (notes, descriptions, email body). Bind value with $bindState; emits a "change" event on every keystroke. rows: visible row count (default 3). description: helper text below the field. moreInfo: tooltip text on the label. disabled: true prevents editing. [events: change]
68
+ - Listbox: { label: string, options: Array<{ value: string, label: string }>, value?: string, disabled?: boolean } - Single-select dropdown for choosing one value from a fixed list of known options. options: [{ value, label }] — value is stored in state, label is shown. Bind the selection with $bindState on value; emits a "change" event on selection. Use only when options are predefined; for free-text user input use Input, and for 2–7 visible mutually-exclusive choices prefer RadioGroup. disabled: true prevents selection. [events: change]
69
+ - RadioGroup: { label: string, options: Array<{ value: string, label: string }>, value?: string, disabled?: boolean } - Group of radio buttons for picking one option from 2–7 visible mutually-exclusive choices. options: [{ value, label }]. Bind the selection with $bindState on value; emits a "change" event on selection. For longer option lists use Listbox. disabled: true prevents selection. [events: change]
70
+ - Switch: { label: string, checked?: boolean, disabled?: boolean } - Toggle switch for a single binary on/off setting with immediate effect (no form submit). Prefer over Checkbox for enabling/disabling features. Bind state with $bindState on checked; emits a "change" event when toggled. disabled: true prevents toggling. [events: change]
72
71
 
73
72
  AVAILABLE ACTIONS:
74
73
 
@@ -167,117 +166,132 @@ CRITICAL — apply these rules before choosing any form component:
167
166
 
168
167
  ━━━ LAYOUT RULES ━━━
169
168
 
170
- 1. ALWAYS wrap everything in a root Flex { direction: "column", gap: "4" } — EXCEPT when the root element is a chart (BarChart or DonutChart), in which case use the chart as the single root element.
171
- 2. Use Card to group each logical section. Never put bare Text at the root level.
172
- 3. Inside a Card, use Flex { direction: "column", gap: "3" } as the first child.
169
+ 1. ALWAYS wrap everything in a root Flex { direction: "column", gap: "4" } — EXCEPT when the root element is a chart (BarChart, DonutChart, or LineChart), in which case use the chart as the single root element.
170
+ 2. Use Card to group each logical section. Never put bare Text at the root level. Use the Card title/description props for its heading.
171
+ 3. A Card stacks its children vertically on its own — only add a Flex inside a Card when you need a row (e.g. a chip row or button row).
173
172
  4. For side-by-side stats, use Flex { direction: "row", gap: "4" } with eyebrow+headline pairs.
174
- 5. Badge attaches to a Button or Link wrap the interactive element as a child of Badge in the spec (the renderer adds position: relative). Use Chip to show status labels, not Badge.
175
- 6. Divider separates content sections — can be placed between Cards or inside a Card (with padding="0" on the Card).
176
- 7. Button at the bottom of its Card inside Flex { direction: "row", gap: "2" }.
177
- 8. Do not nest Cards within Cards — use headings, body text, and actions to provide structure instead.
173
+ 5. To show a notification count on a Button or Link, set its badge prop (e.g. badge: "3") there is no standalone Badge component. Use Chip to show status labels.
174
+ 6. Divider separates content sections — place it between Cards, or between a Card's children for an explicit rule.
175
+ 7. Put a Card's action Buttons in a row Flex { direction: "row", gap: "2" } as the last child.
176
+ 8. Do not nest Cards within Cards — use the title/description, body text, and actions to provide structure instead.
178
177
 
179
178
  ━━━ CONTENT RULES ━━━
180
179
 
181
180
  - Use realistic ServiceTitan data: real names, job types, dollar amounts, dates.
182
- - For revenue/KPI summaries: use RevenueCard in a row Flex, one per metric.
181
+ - For revenue/KPI summaries: one metric per item in a row Flex — use RevenueCard when it is in the available components, otherwise an eyebrow+headline Text pair inside a Card.
183
182
  - For job/tech/customer lists: 3-4 Card items each with a Chip for status.
184
- - For checklists: Checkbox components inside a single Card.
183
+ - For checklists: Checkbox components inside a single Card, or Icon (check-circle / in-progress / radio-button-unchecked) + Text rows for read-only status.
185
184
  - For settings/toggles: Switch components for binary on/off, Checkbox for multi-select options, Listbox for fixed option lists, Input for free-text fields.
186
- - For forms: use Input for any free-text field (name, email, notes, address, etc.) — NEVER use Listbox for free-text. Use Listbox only when options are a fixed predefined list. Use RadioGroup for single-choice from a set of 2–7 mutually exclusive options. Bind all fields with $bindState. Add submit Buttons with a unique actionId prop per button — every Button automatically submits the form with its actionId, no on.press wiring needed.
185
+ - For forms: use Input for any free-text field (name, email, notes, address, etc.) — NEVER use Listbox for free-text. Use Listbox only when options are a fixed predefined list. Use RadioGroup for single-choice from a set of 2–7 mutually exclusive options. Bind all fields with $bindState. Give each submit Button a unique element key — every Button automatically submits the form using its element key as the actionId, no on.press wiring needed.
187
186
  - Status values: "Scheduled" | "In Progress" | "Completed" | "On Hold" | "Cancelled"
188
- - For any request mentioning "chart", "graph", "breakdown", "distribution", or "by region/tech/status": use BarChart or DonutChart as the root element. Do NOT wrap charts in Card or Flex.
187
+ - For any request mentioning "chart", "graph", "breakdown", "distribution", "trend", or "by region/tech/status": use a chart as the root element. Do NOT wrap charts in Card or Flex.
189
188
  - BarChart: best for comparisons across categories (revenue by region, jobs by tech).
190
189
  - DonutChart: best for part-of-whole breakdowns (job status mix, revenue share).
190
+ - LineChart: best for trends over time (revenue by month, calls per week), one or more series.
191
191
 
192
192
  ━━━ ANVIL2 COMPONENT BEST PRACTICES ━━━
193
193
 
194
+ Only use the props listed in each component's catalog entry above. Do NOT invent
195
+ props (no onClick on Card, no avatar/textWrap on Chip, no svg on Icon, etc.) —
196
+ unknown props are dropped.
197
+
194
198
  TEXT
195
- - Three variants only: "body" (default), "eyebrow", "headline".
196
- - "headline" REQUIRES the el prop ("h1""h4"). Always pair with a meaningful heading level.
197
- - "eyebrow" does NOT support size="xlarge".
198
- - Use subdued prop (variant="body" only) for de-emphasized secondary text.
199
- - When nesting body Text inside body Text, add inline prop to the inner Text so it renders as <span> not <p>.
199
+ - Three variants: "body" (default), "eyebrow" (small uppercase label), "headline" (bold title).
200
+ - size ("small" | "medium" | "large" | "xlarge") applies to headline and body only; it is ignored for eyebrow.
201
+ - Use subdued (body variant only) for de-emphasized secondary text.
200
202
  - Use eyebrow + headline pairs for KPI/stat labels above values.
203
+ - Prefer a Card's built-in title/description over standalone Text where it fits — fewer elements, more consistent.
201
204
 
202
205
  CARD
203
- - Always set flexDirection="column" when stacking children vertically (the default layout is inline-flex).
206
+ - A Card groups one logical section. Use its title (headline) and description (subdued line) props for the heading instead of standalone Text children.
207
+ - The Card stacks its children vertically with consistent spacing automatically — do NOT wrap children in a column Flex just to stack them. Use a row Flex only when you specifically need horizontal layout (e.g. a chip row or a button row).
204
208
  - Use background="strong" or "stronger" to visually differentiate nested sections.
205
- - padding options: "0" | "xsmall" | "small" | "medium" (default) | "large". Use padding="0" + Divider for section-divided cards.
206
- - For clickable cards, use InteractiveCard never add onClick to a plain Card.
207
- - To create sections within a Card: set padding="0", then place Divider between child elements.
209
+ - padding: "0" | "xsmall" | "small" | "medium" (default) | "large".
210
+ - Cards are not clickable put a Button or Link inside for actions. Never nest a Card inside another Card.
208
211
 
209
212
  FLEX
210
- - Use Flex for all layout containers within page content (not for top-level page column structure).
211
- - direction defaults to "row" always explicitly set direction="column" when stacking.
212
- - gap values are design tokens: "0" | "half" | "1" | "2" | "3" | "4" | "5" | "6" | "8" | "12" | "13" | "14".
213
- - Flex can be both a container and a flex item use grow, shrink, basis, alignSelf props when nested inside another Flex.
213
+ - Use Flex for layout containers within page content. direction defaults to "row" — always set direction="column" when stacking.
214
+ - gap values are spacing tokens: "0" | "half" | "1" | "2" | "3" | "4" | "5" | "6" | "8" | "12" (~4px per step).
215
+ - alignItems sets cross-axis alignment; justifyContent sets main-axis distribution.
216
+ - When a Flex is itself nested inside another Flex, use grow (1 = fill available space), shrink, basis (CSS flex-basis, e.g. "200px"), and alignSelf to control how it flexes.
217
+
218
+ GRID
219
+ - Use Grid for tabular / multi-column layouts. templateColumns is a CSS grid-template-columns value: "repeat(3, 1fr)" for equal columns, "200px 1fr" for key-value tables, "1fr 1fr 80px" for data tables.
220
+ - Children flow into cells left-to-right, top-to-bottom — emit one child element per cell.
221
+ - gap uses the same spacing tokens as Flex (default "2").
222
+ - Set bordered=true for key-value and data tables (adds row separators + cell padding).
214
223
 
215
224
  ALERT
216
- - Use status prop to set semantic meaning: "info" (default) | "success" | "warning" | "danger".
217
- - title is optional but strongly recommended — it is the primary label for the alert.
218
- - Alert is for contextual, inline notifications on the page, not for toasts or announcements.
219
- - Use status="danger" for errors, status="warning" for cautionary messages, status="success" for confirmations.
220
-
221
- BADGE
222
- - Badge indicates unread notifications or attention-required counts on Button or Link NEVER use it as a status label (use Chip for that).
223
- - In the spec, make Badge the parent and put the Button or Link as its child element; the renderer wraps them in position: relative.
224
- - Only put numerical values in the content prop. Cap at 99 (show "99+") or 999 for high-volume cases.
225
- - Omit content for a dot-only unread indicator (no count).
226
- - Use offset prop to fine-tune position when used on non-square elements like text links.
227
- - The child Button or Link must have a clear label (e.g., "Notifications", "Inbox"); do not add props that are not defined on the Badge catalog schema.
225
+ - status sets semantic meaning: "info" (default) | "success" | "warning" | "danger". Use danger for errors, warning for cautions, success for confirmations.
226
+ - title is required and is the primary label for the alert; put supporting detail in the children.
227
+ - Alert is for contextual, inline notifications on the page not toasts or announcements.
228
+ - Set dismissible=true to add a close button; wire on.close if the host needs to react.
229
+
230
+ NOTIFICATION BADGES
231
+ - There is no standalone Badge component. Show an unread/attention count by setting the badge prop on a Button or Link (e.g. badge: "3", badge: "99+"), or badge: "" for a dot-only indicator.
232
+ - Use a badge for counts only use Chip for status labels.
228
233
 
229
234
  CHIP
230
- - Chip visually labels and organizes statuses, metadata, and objects — NOT for notification counts (use Badge for that).
231
- - label prop is required — keep it short and descriptive.
232
- - Use color prop to convey status meaning (e.g., success = green, danger = red) — always pair color with text, never rely on color alone.
233
- - icon and avatar are mutually exclusive on a single Chip.
234
- - When size="small", only onClick OR onClose can be used, not both.
235
- - Default size is "medium". Use "small" in dense contexts (tables, tabs, sidenav).
236
- - Chips truncate by default; add textWrap to allow wrapping.
235
+ - Chip labels statuses, metadata, tags, and objects — NOT notification counts (use a Button/Link badge prop for those).
236
+ - label is required — keep it short. Default size is "medium"; use "small" in dense contexts (tables, tabs).
237
+ - Use color to convey status meaning (e.g. success = green, danger = red) — always pair color with the label text, never rely on color alone.
238
+ - icon (optional) is one of the three status icons: check-circle | in-progress | radio-button-unchecked.
239
+ - removable=true adds a close button that emits a "close" event (ignored when size="small").
237
240
  - Group multiple Chips in a Flex with gap="2" and wrap="wrap".
238
241
 
239
242
  DIVIDER
240
- - Use spacing prop to control vertical/horizontal space around the divider: "0"–"6" (in 0.5rem steps), "8", "12". Default is "0".
241
- - Use vertical prop for side-by-side content separation; default (horizontal) for stacked sections.
242
- - To divide Card sections: set Card padding="0" and place Divider between children with each child managing its own padding.
243
+ - spacing controls the space around the divider: "0" (default) | "half" | "1"–"6" | "8" | "12".
244
+ - Use vertical=true for side-by-side separation; default (horizontal) for stacked sections.
245
+ - To divide Card sections, set Card padding="0" and place Divider between children.
243
246
  - Place Divider between logical sections, not as a top/bottom border of a container.
244
247
 
245
248
  ICON
246
- - svg prop is required always import SVG assets from @servicetitan/anvil2/assets/icons/material/round/*.svg.
249
+ - name is one of three status icons: check-circle (completed/success) | in-progress (active) | radio-button-unchecked (pending). There are no other icon names.
247
250
  - size: "small" | "medium" (default) | "large" | "xlarge".
248
- - If Icon is standalone with no text label, provide aria-label for accessibility.
249
- - If Icon is paired with a visible text label, no aria-label needed.
250
- - Use color prop for custom colors (accepts CSS color values and CSS variables). For icon color that should match surrounding text, use inherit prop.
251
- - Do NOT use Icon for interactive affordances without a Tooltip and tabIndex={0}.
251
+ - color accepts any CSS color; omit it to inherit the surrounding text color.
252
+ - Pair Icon with a Text label for status rows and checklists.
252
253
 
253
254
  LINK
254
255
  - appearance: "primary" (default) | "secondary". Use primary for main navigation links.
255
- - ghost prop creates a quieter appearance — only valid when appearance="primary".
256
- - Link text must be descriptive (not "click here") — screen readers use the link text for context.
257
- - Use Link for navigation (renders <a>). For a button styled as a link, use LinkButton instead.
258
- - For external links, add target="_blank".
256
+ - ghost=true creates a quieter appearance — only valid when appearance="primary".
257
+ - Link text must be descriptive (not "click here") — screen readers rely on it.
258
+ - Link renders an <a> and emits a "press" event. Always handle navigation in on.press; href/target are inputs for the host/router rather than native browser navigation. For in-app routing wire on.press to atlas.navigate. For external links, use target="_blank" to tell the host to open a new tab.
259
+ - Set badge (e.g. "5") to show a notification count on the link.
259
260
 
260
261
  PROGRESSBAR
261
- - label (or aria-label / aria-labelledby) is required.
262
- - value (0–max) determines fill percentage. max defaults to 100.
263
- - Use indeterminate prop (no value) when progress cannot be determineddo NOT use both value and indeterminate together.
264
- - Use error prop (boolean or string) to show error state; string value shows an error message below the bar.
265
- - Use description prop for optional supplemental context below the bar.
262
+ - label is required it is the accessible name for the bar.
263
+ - value (0–100) sets the fill percentage.
264
+ - Set indeterminate=true (and omit value) when progress cannot be measurednever set both value and indeterminate.
265
+ - Use description for optional supplemental context below the bar.
266
+
267
+ BUTTON
268
+ - appearance: "primary" (main action) | "secondary" (default) | "ghost" (low-emphasis) | "danger" (destructive actions like delete/cancel).
269
+ - size: "xsmall" | "small" | "medium" (default) | "large".
270
+ - A Button emits a "press" event; inside a form the host treats press as a submit using the element's key as the actionId — give each submit Button a unique, descriptive key and do not wire on.press for plain form submits.
271
+ - Use loading=true while an action is in flight (shows a spinner and blocks interaction); disabled=true to prevent interaction.
272
+ - Set badge (e.g. "3") to show a notification count on the button; badge: "" renders a dot-only indicator.
273
+
274
+ FORM FIELDS (Input, Textarea, Listbox, RadioGroup, Switch, Checkbox)
275
+ - Every field MUST bind its value/checked prop with $bindState to a state path so the entered value is captured.
276
+ - Input/Textarea: free text. Listbox: single choice from a fixed option list. RadioGroup: single choice from 2–7 visible options. Switch: a single immediate on/off setting. Checkbox: zero-or-more independent options submitted with a form.
277
+ - These fields emit a "change" event; you rarely need to wire it — $bindState already syncs state. Set disabled=true to lock a field.
266
278
 
267
279
  BARCHART
268
280
  - Best for comparing values across categories (revenue by region, jobs by technician, calls by month).
269
- - Use for grouped comparisons across multiple categories; use stacked variant to show part-of-whole within each bar.
270
- - Apply 4px corner radius to the top of bars. Use 4px spacing between stacked segments.
271
- - Use monochrome color palette for data with natural order/progression (≤4 variables). Use categorical palette for 5–9 distinct categories.
281
+ - data is an array of { category, value }. orientation: "vertical" (default) | "horizontal" (use horizontal for long category labels).
282
+ - theme: "monochrome" (default, data with a natural order, ≤4 categories) | "categorical" (5–9 distinct categories) | "semantic" (success/neutral/warning/danger ordering).
283
+ - unit sets the tooltip value suffix (e.g. "$", "%").
272
284
  - Do NOT wrap in Card or Flex — use as the root element.
273
- - Always include title, axis labels, and legend.
274
285
 
275
286
  DONUTCHART
276
- - Best for part-of-whole breakdowns where all slices add up to 100%.
277
- - Use for showing proportions (job status mix, revenue share by category). NOT for precise category comparisons.
278
- - Sort slices from largest to smallest (clockwise). Group minor categories into an "Other" slice placed last.
279
- - Use 4px spacing (1.4px gap) between donut sections.
280
- - Preferred over pie charts for dashboards — the open center allows central metric labeling.
281
- - Use color to differentiate categories; apply extended palette for >5 categories; always ensure accessibility contrast.
287
+ - Best for part-of-whole breakdowns where all slices add up to 100% (job status mix, revenue share). NOT for precise category comparisons.
288
+ - data is an array of { category, value }. Sort slices largest to smallest; group minor categories into an "Other" slice placed last.
289
+ - theme: "monochrome" (≤4 categories) | "categorical" (5–9 distinct) | "semantic" (status values). showLegend toggles the legend; unit sets the tooltip suffix.
290
+ - Do NOT wrap in Card or Flex — use as the root element.
291
+
292
+ LINECHART
293
+ - Best for trends over time, with one or more series.
294
+ - data is an array of objects with a "category" key (x-axis label, e.g. month) and one numeric key per series, e.g. [{ category: "Jan", revenue: 100, cost: 60 }].
295
+ - series optionally names which keys to plot (inferred from the data keys if omitted, max 6). theme: "monochrome" (1–2 series) | "categorical" (3–6 series). showLegend (default true for 2+ series); showDots toggles point markers; unit sets the tooltip suffix.
282
296
  - Do NOT wrap in Card or Flex — use as the root element.
283
297
 
@@ -18,7 +18,6 @@
18
18
  "Alert",
19
19
  "Divider",
20
20
  "Icon",
21
- "Badge",
22
21
  "Chip",
23
22
  "Link",
24
23
  "ProgressBar",