@treeui/mcp 0.8.0 → 0.8.2

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.
package/dist/index.cjs CHANGED
@@ -50,9 +50,9 @@ module.exports = __toCommonJS(index_exports);
50
50
 
51
51
  // src/generated/treeui.catalog.json
52
52
  var treeui_catalog_default = {
53
- generatedAt: "0.30.0",
53
+ generatedAt: "0.34.1",
54
54
  packageName: "@treeui/vue",
55
- packageVersion: "0.30.0",
55
+ packageVersion: "0.34.1",
56
56
  decisionGuides: [
57
57
  {
58
58
  id: "single-choice",
@@ -175,7 +175,7 @@ var treeui_catalog_default = {
175
175
  ],
176
176
  notes: [
177
177
  "Editing, not display. For read-only key/value display use TDescriptionList.",
178
- "A write-only/sensitive mode (value never read back) is a closed contract on the queue, not yet shipped."
178
+ "A write-only/sensitive mode (value never read back) is a designed contract that is not yet shipped."
179
179
  ]
180
180
  },
181
181
  {
@@ -473,8 +473,8 @@ var treeui_catalog_default = {
473
473
  "Put the lockup in the `header-start` slot, not `header`. Filling `header-start` switches the header to the shell's own grid, so the mark tracks `--tree-app-shell-sidebar-width` and animates with the collapse \u2014 no local column math.",
474
474
  'The slot is scoped: use its `collapsed` binding to drop the wordmark and keep only the mark (`v-if="!collapsed"`), rather than hiding text with CSS.',
475
475
  'Logo is `TAvatar` (`size`, `alt`, `initials` fallback) or a plain `<img>` sized with `--tree-size-icon-*`; name and subtitle are `TText` (`weight`, `tone="muted"`, `truncate`) \u2014 never raw `<strong>`/`<span>` with a local class.',
476
- "TreeUI ships no `TBrand` component on purpose: the logo asset, the product name, and what survives collapse are product identity, which must stay in the consumer. See DECISIONS.md \u2192 Portability Boundary.",
477
- "Company and service logos (AWS, LocalStack, social networks) are brand assets and are not TreeUI icons \u2014 source them from the app's brand policy, not the icon registry."
476
+ "TreeUI ships no `TBrand` component on purpose: the logo asset, the product name, and what survives collapse are product identity, which must stay in the consuming application. See DECISIONS.md \u2192 Portability Boundary.",
477
+ "Company and service logos are brand assets, not TreeUI icons \u2014 source them from your own brand assets rather than the icon registry."
478
478
  ]
479
479
  },
480
480
  {
@@ -512,7 +512,7 @@ var treeui_catalog_default = {
512
512
  "`TNavMenuItem.icon` is `TIconInput`, so a component is accepted too. Prefer the name: `items` normally lives in a `ref`, a `computed`, or a route config, and a string is never made reactive \u2014 a component object there needs `markRaw` or Vue warns.",
513
513
  "An item with no icon (or an unregistered name) falls back to a letter marker, and a `console.warn` lists the valid names \u2014 once per name, in production builds too. Give every item an icon before shipping a collapsible rail, or the collapsed state is a column of letters.",
514
514
  "Icons the app needs but TreeUI does not ship are REGISTERED, not worked around: one `registerTreeIcons` call in `main.ts` and they are usable by name everywhere. Never hand-roll an `<svg>` \u2014 without the `absoluteStrokeWidth` correction its stroke scales with the icon (1.67px at size 20, 4px at 48), so it looks bolder than its neighbours at every size above 24.",
515
- "Check the name is not already taken before registering it \u2014 `hasTreeIcon(name)` answers that. Registering one of the 364 built-in names replaces that built-in everywhere, silently, which is a supported swap but never what you want when the goal was adding an icon.",
515
+ "Check the name is not already taken before registering it \u2014 `hasTreeIcon(name)` answers that. Registering one of the 370 built-in names replaces that built-in everywhere, silently, which is a supported swap but never what you want when the goal was adding an icon.",
516
516
  "Keep the icon vocabulary consistent: built-ins use TreeUI's descriptive kebab-case names, so register custom ones with the same convention.",
517
517
  "Icon registration is process-wide state. In component tests that register icons, call `resetTreeIcons()` in an `afterEach` so one case's icons do not leak into the next.",
518
518
  "Set `to` for the route target on each item \u2014 `TNavMenuItem` has no `href` and no `id`; identity is `value`.",
@@ -820,28 +820,41 @@ var treeui_catalog_default = {
820
820
  "Tab/Shift+Tab order, Escape to close, Enter/Space to activate, arrow keys where the pattern expects them.",
821
821
  "Overlays trap focus while open and restore it on close.",
822
822
  "Clickable targets stay at or above 44\xD744px.",
823
- "Form control boundaries clear 3:1 against every surface (WCAG 1.4.11); decorative edges are a separate token and are not held to it."
823
+ "Form control boundaries clear 3:1 against every surface (WCAG 1.4.11); decorative edges are a separate token and are not held to it.",
824
+ "A panel anchored to a trigger is rendered in a layer no ancestor can clip, and is pushed back inside the viewport \u2014 a control that is painted off-screen or behind a clip is not operable.",
825
+ "Every string a screen reader announces is a prop with a default, never a hard-coded literal; a control whose only name is English is unusable in any other language."
824
826
  ],
825
827
  components: [
826
- "TModal",
827
- "TDrawer",
828
- "TConfirmDialog",
829
- "TDropdown",
830
- "TTooltip",
831
828
  "TAccordion",
832
- "TFormField",
833
- "TTreeView",
834
- "TToastProvider",
829
+ "TAppShell",
835
830
  "TButton",
831
+ "TCanvasSurface",
832
+ "TCheckbox",
836
833
  "TCodeBlock",
834
+ "TConfirmDialog",
835
+ "TDatePicker",
836
+ "TDrawer",
837
+ "TDropdown",
838
+ "TFileUpload",
839
+ "TFormField",
840
+ "TIconTile",
841
+ "TImage",
837
842
  "TLinkTile",
838
843
  "TMenu",
844
+ "TModal",
839
845
  "TPopover",
840
- "TImage",
846
+ "TSelect",
841
847
  "TSkipLink",
842
- "TAppShell",
843
- "TCheckbox",
844
- "TText"
848
+ "TStatGroup",
849
+ "TSteps",
850
+ "TTable",
851
+ "TTabs",
852
+ "TText",
853
+ "TToast",
854
+ "TToastProvider",
855
+ "TToggleGroup",
856
+ "TTooltip",
857
+ "TTreeView"
845
858
  ]
846
859
  },
847
860
  {
@@ -901,26 +914,45 @@ var treeui_catalog_default = {
901
914
  "Spacing comes from the --tree-space-* scale, applied by layout components.",
902
915
  "Compose TPage, TStack, TGrid, and TSplit before writing a custom layout div.",
903
916
  "Forms align through TFormField + TStack + TGrid \u2014 labels, hints, and errors line up by contract.",
904
- "Reading widths and page gutters come from TContainer/TPage, not local max-widths."
917
+ "Reading widths and page gutters come from TContainer/TPage, not local max-widths.",
918
+ "A surface takes its container's width: it never grows to the min-content of something inside it. Card, modal and timeline surfaces declare an explicit minmax(0, 1fr) track, and text that can lack a break opportunity wraps, so scrolling and truncation belong to the descendant that asked for them \u2014 never to every sibling on the surface."
905
919
  ],
906
920
  components: [
907
- "TStack",
908
- "TGrid",
909
- "TSplit",
910
- "TContainer",
911
- "TSpacer",
912
- "TPage",
913
- "TPageHeader",
914
- "TFormField",
921
+ "TAppShell",
922
+ "TBadge",
915
923
  "TBrandLockup",
916
- "TList",
924
+ "TCanvasSurface",
925
+ "TCard",
926
+ "TCodeBlock",
927
+ "TConfirmDialog",
928
+ "TContainer",
917
929
  "TDescriptionList",
918
- "TSection",
930
+ "TEmptyState",
931
+ "TFormField",
932
+ "TGrid",
919
933
  "THero",
934
+ "THide",
935
+ "TIconTile",
936
+ "TLinkTile",
937
+ "TList",
938
+ "TModal",
939
+ "TNavMenu",
940
+ "TPage",
941
+ "TPageHeader",
920
942
  "TPageSurface",
943
+ "TSection",
921
944
  "TShow",
922
- "THide",
923
- "TEmptyState"
945
+ "TSpacer",
946
+ "TSparkline",
947
+ "TSplit",
948
+ "TStack",
949
+ "TStat",
950
+ "TStatGroup",
951
+ "TSteps",
952
+ "TTable",
953
+ "TTabs",
954
+ "TText",
955
+ "TTimeline"
924
956
  ]
925
957
  },
926
958
  {
@@ -947,6 +979,7 @@ var treeui_catalog_default = {
947
979
  "TColorSwatch",
948
980
  "TDescriptionList",
949
981
  "THero",
982
+ "TIconTile",
950
983
  "TImage",
951
984
  "TInput",
952
985
  "TKeyValueEditor",
@@ -960,6 +993,7 @@ var treeui_catalog_default = {
960
993
  "TSection",
961
994
  "TSelect",
962
995
  "TStat",
996
+ "TStatGroup",
963
997
  "TSwitch",
964
998
  "TTag",
965
999
  "TTagInput",
@@ -967,6 +1001,24 @@ var treeui_catalog_default = {
967
1001
  "TTextarea",
968
1002
  "TToast"
969
1003
  ]
1004
+ },
1005
+ {
1006
+ id: "branchline-icons",
1007
+ title: "Icons drawn to a contract",
1008
+ summary: "Branchline is the drawing contract behind every shipped icon: one grid, one weight, one closed modifier vocabulary. It is checked rather than reviewed \u2014 the build renders each glyph and fails when two names look alike at 16px or when a shape draws nothing.",
1009
+ rules: [
1010
+ "One grid: a 24x24 canvas with geometry inside a 1.5-22.5 trim area, so a 2u stroke never clips; four keylines carry the set \u2014 square 17.5, circle 18.5 diameter, wide 18x14, tall 14x18 \u2014 and positions land on a 0.25u step.",
1011
+ "One weight: 2u stroke, round caps and joins, currentColor and fill=none, all declared on the root <svg>. A child that sets stroke or fill breaks absoluteStrokeWidth and fails the build.",
1012
+ "Legible at the size it ships at: every pair of canonical icons is rendered at 16px and must differ by at least 5% of its ink. The rule is measured by branchline.test.ts, not judged by eye.",
1013
+ "A modifier gets territory of its own, and the base keeps its size: it is drawn inside bases that enclose space (shield, file, folder, calendar, ticket, message, square, badge, search) and in a cleared corner otherwise, where the base is redrawn at full size with that corner interrupted so 5.5u of canvas stays clear around the mark.",
1014
+ "Every shape earns its place: each node is removed and re-rendered, and a node that changes less than 1% of the glyph's ink fails the build. Geometry is never derived from the icon's name.",
1015
+ "A family shares one base, drawn once and reused literally; a variant changes exactly one thing, in one place, by one rule.",
1016
+ "The metaphor is the conventional one \u2014 an icon is not the place to be original. A concept with no settled picture composes one from Branchline primitives, drawn deliberately rather than generated.",
1017
+ "One concept, one canonical name, one drawing. A synonym is an alias that shares its target's geometry byte for byte, so mic and microphone can never drift apart."
1018
+ ],
1019
+ components: [
1020
+ "TIcon"
1021
+ ]
970
1022
  }
971
1023
  ],
972
1024
  setup: {
@@ -981,7 +1033,7 @@ var treeui_catalog_default = {
981
1033
  },
982
1034
  vueSetup: {
983
1035
  pluginInstall: {
984
- import: "import TreeUIPlugin from '@treeui/vue'",
1036
+ import: "import { TreeUIPlugin } from '@treeui/vue'",
985
1037
  usage: "app.use(TreeUIPlugin)"
986
1038
  },
987
1039
  individualImports: {
@@ -1604,7 +1656,7 @@ var treeui_catalog_default = {
1604
1656
  name: "showCollapseButton",
1605
1657
  type: "boolean",
1606
1658
  default: "true",
1607
- note: "Renders the built-in collapse toggle in the sidebar footer. Desktop only, and only when `collapsible` is set \u2014 no need to wire your own. Never rendered in the mobile drawer."
1659
+ note: "Renders the built-in collapse toggle in the sidebar footer. Desktop only, and only when `collapsible` is set \u2014 no need to wire your own. Never rendered in the mobile drawer, and never inside the auto-rail band (between `breakpoint` and `railBreakpoint`): there the width decides the state, so the button could not change anything, did nothing when pressed, and announced the manual preference instead of what was on screen. Its `aria-expanded` and `aria-label` are derived from the EFFECTIVE collapsed state, as the glyph already was."
1608
1660
  },
1609
1661
  {
1610
1662
  name: "collapseLabel",
@@ -1688,7 +1740,7 @@ var treeui_catalog_default = {
1688
1740
  },
1689
1741
  {
1690
1742
  name: "header-end",
1691
- description: "Trailing header region for actions/controls, pinned to the far end of the row. Filling it adds `has-end`; in the plain flex header the content region's grow already pushes it to the end, and in the `has-start` grid it becomes a content-sized third column with the trailing padding. Replaces the consumer's own `TStack justify=flex-end` wrapper inside `#header`.\n",
1743
+ description: "Trailing header region for actions/controls, pinned to the far end of the row. Filling it adds `has-end`; in the plain flex header the content region's grow already pushes it to the end, and in the `has-start` grid it becomes a content-sized third column with the trailing padding. Trailing alignment is the shell's own job, so header actions need no `TStack justify=flex-end` wrapper around them.\n",
1692
1744
  note: "AppShellSlotProps"
1693
1745
  },
1694
1746
  {
@@ -1742,6 +1794,7 @@ var treeui_catalog_default = {
1742
1794
  "menu_and_drawer_close_buttons_are_44px: true"
1743
1795
  ],
1744
1796
  behavior: [
1797
+ "scroller_is_a_containing_block: `.t-app-shell__main` (and `.t-app-shell__sidebar-body`) carry `position: relative`. Without it an absolutely positioned descendant with auto insets \u2014 `.t-visually-hidden`, which this library itself uses for the TProgress label \u2014 anchored to the initial block instead of the panel: it was neither clipped nor scrolled with the panel, and it extended the DOCUMENT's scroll. The shell is `100dvh`, so the document is not supposed to scroll at all; it did, leaving an empty band below the shell.",
1745
1798
  "fills_viewport_full_width_and_height: true",
1746
1799
  "resets_body_margin_while_a_shell_is_mounted: body:has(.t-app-shell) { margin: 0 } \u2014 a deliberate global rule, scoped so it only applies to pages that mount a shell",
1747
1800
  "header_spans_full_width_above_sidebar_and_content: true",
@@ -1829,7 +1882,8 @@ var treeui_catalog_default = {
1829
1882
  "brand-lockup"
1830
1883
  ],
1831
1884
  practices: [
1832
- "accessible-by-default"
1885
+ "accessible-by-default",
1886
+ "content-alignment"
1833
1887
  ]
1834
1888
  },
1835
1889
  {
@@ -2040,6 +2094,18 @@ var treeui_catalog_default = {
2040
2094
  "lg"
2041
2095
  ],
2042
2096
  description: "Changes min-height and font-size only; inline padding is constant across sizes."
2097
+ },
2098
+ {
2099
+ name: "truncate",
2100
+ type: "boolean",
2101
+ default: "false",
2102
+ description: "Keep the label on one line and clip it with an ellipsis. Opt-in: the default is to wrap, because a status pill that silently loses the end of its text is worse than a two-line pill. Pass `label` alongside it so the full text reaches the `title` tooltip."
2103
+ },
2104
+ {
2105
+ name: "label",
2106
+ type: "string",
2107
+ default: "undefined",
2108
+ description: "The badge text. Only needed with `truncate`, which puts it in `title`; otherwise use the default slot."
2043
2109
  }
2044
2110
  ],
2045
2111
  events: [],
@@ -2062,8 +2128,10 @@ var treeui_catalog_default = {
2062
2128
  "tone_remaps_variant_color_variables: true",
2063
2129
  "tone_overrides_variant_danger_palette: true",
2064
2130
  "tone_neutral_has_no_stylesheet_rule: true",
2131
+ 'tint_ink_is_the_on_soft_token: The `soft` and `danger` variants paint their label with the tone\'s `*-on-soft` colour, never the tone\'s full-strength colour. `*-on-soft` is derived to clear AA on that exact tint and deepens as the tint deepens; the full-strength tone on its own tint measured below AA for eight of the ten tone/theme pairs, including `variant="soft" tone="neutral"`, which is both defaults. The mapping is declared in `@treeui/tokens` as `NATIVE_BADGE_TONES` and the stylesheet is tested against it.',
2065
2132
  "icon_wrapper_rendered_only_when_slot_filled: true",
2066
- "icon_renders_before_default_slot: true"
2133
+ "icon_renders_before_default_slot: true",
2134
+ "never_wider_than_its_container: The badge carries `max-inline-size: 100%` and `min-inline-size: 0`, so a long label wraps inside the container instead of pushing it. Wrapped lines use the UI line height with block padding rather than `line-height: 1`, at which a second line's ascenders touched the pill's border."
2067
2135
  ],
2068
2136
  selection: {
2069
2137
  keywords: [],
@@ -2081,6 +2149,7 @@ var treeui_catalog_default = {
2081
2149
  practices: [
2082
2150
  "predictable-api",
2083
2151
  "state-clarity",
2152
+ "content-alignment",
2084
2153
  "token-driven"
2085
2154
  ]
2086
2155
  },
@@ -2271,7 +2340,7 @@ var treeui_catalog_default = {
2271
2340
  name: "href",
2272
2341
  type: "string",
2273
2342
  default: "undefined",
2274
- note: 'Anchor attributes (`href`, `target`, `rel`, `download`) are declared props and are only rendered when `as="a"`. They were fallthrough before, which typechecked as an error in a consumer\'s `strictTemplates` build even though a link button is the documented use of `as` (TREEUX-011, group 1).\n'
2343
+ note: 'Anchor attributes (`href`, `target`, `rel`, `download`) are declared props and are only rendered when `as="a"`. They were fallthrough before, which typechecked as an error in a consumer\'s `strictTemplates` build even though a link button is the documented use of `as`.\n'
2275
2344
  },
2276
2345
  {
2277
2346
  name: "target",
@@ -2686,6 +2755,116 @@ var treeui_catalog_default = {
2686
2755
  recipes: [],
2687
2756
  practices: []
2688
2757
  },
2758
+ {
2759
+ name: "TCanvasSurface",
2760
+ aliases: [],
2761
+ package: "@treeui/vue",
2762
+ kind: "data-display",
2763
+ category: "Data Display",
2764
+ status: "available",
2765
+ purpose: "A sized, repaintable, hit-testable canvas for a data-driven drawing \u2014 a diagram, a graph, a custom plot.",
2766
+ sourceFile: "packages/vue/src/components/TCanvasSurface.vue",
2767
+ storyFile: "apps/docs/src/stories/CanvasSurface.stories.ts",
2768
+ storyTitle: "Components/Data Display/Canvas Surface",
2769
+ manifestFile: "docs/ai/COMPONENTS/canvas-surface.yaml",
2770
+ props: [
2771
+ {
2772
+ name: "draw",
2773
+ type: "(frame: CanvasSurfaceFrame) => void",
2774
+ required: true,
2775
+ note: "Paints the whole surface. Called on mount, on resize and on every `redraw()`. The context arrives already scaled to CSS pixels, so the drawing never does device-ratio arithmetic; it is expected to clear what it does not repaint.\n"
2776
+ },
2777
+ {
2778
+ name: "height",
2779
+ type: "number | string",
2780
+ default: "320",
2781
+ note: "Number is px, string is any CSS length. A diagram has no intrinsic height, so it is declared."
2782
+ },
2783
+ {
2784
+ name: "cursor",
2785
+ type: "string",
2786
+ default: "undefined",
2787
+ note: "Pointer over the surface. This cannot come from a stylesheet: the sensitive area is a rectangle inside the bitmap and no selector knows where it is. Bind it to whatever the consumer's hit-test just decided.\n"
2788
+ },
2789
+ {
2790
+ name: "maxPixelRatio",
2791
+ type: "number",
2792
+ default: "2"
2793
+ },
2794
+ {
2795
+ name: "ariaLabel",
2796
+ type: "string",
2797
+ default: "undefined",
2798
+ note: "Sets role=img. A bitmap says nothing on its own."
2799
+ },
2800
+ {
2801
+ name: "redrawKey",
2802
+ type: "unknown",
2803
+ default: "undefined",
2804
+ note: "Repaint whenever this changes \u2014 pass the data, a version counter, or the theme. Deep-watched."
2805
+ }
2806
+ ],
2807
+ events: [],
2808
+ slots: [
2809
+ {
2810
+ name: "default",
2811
+ description: "The long form of the drawing \u2014 a list, a table, the same data as markup. It lives inside the `<canvas>` element, which the browser exposes to assistive technology and never paints.\n"
2812
+ }
2813
+ ],
2814
+ a11y: [
2815
+ "Give it an ariaLabel; a canvas is opaque to assistive technology.",
2816
+ "Put the same information in the default slot as markup \u2014 it is the accessible form, not a fallback.",
2817
+ "Anything clickable inside the drawing needs a real control elsewhere, or a keyboard user cannot reach it."
2818
+ ],
2819
+ behavior: [
2820
+ "repaints_on_demand_not_on_a_clock: true",
2821
+ "backing_store_follows_the_element: true",
2822
+ "pointer_events_reach_the_canvas: true",
2823
+ "ssr_safe: Nothing touches the DOM outside onMounted."
2824
+ ],
2825
+ selection: {
2826
+ keywords: [
2827
+ "canvas",
2828
+ "diagram",
2829
+ "graph",
2830
+ "node and edge",
2831
+ "custom drawing",
2832
+ "plot",
2833
+ "topology",
2834
+ "map",
2835
+ "hit testing"
2836
+ ],
2837
+ useWhen: [
2838
+ "The drawing is data-driven and no chart primitive expresses it \u2014 nodes and edges, a topology, a custom plot.",
2839
+ "Parts of the drawing are click or hover targets.",
2840
+ "It must repaint when the data, the locale or the theme changes."
2841
+ ],
2842
+ avoidWhen: [
2843
+ "A line, area or bar chart says it \u2014 use TChart, which brings axes, legend, tooltip and an accessible table.",
2844
+ "The drawing is ornament \u2014 use useDecorativeCanvas, which gates the loop on visibility and reduced motion.",
2845
+ "The shape is an inline trend \u2014 use TSparkline."
2846
+ ],
2847
+ alternatives: [
2848
+ {
2849
+ component: "TChart",
2850
+ when: "The data fits axes and categories."
2851
+ },
2852
+ {
2853
+ component: "useDecorativeCanvas",
2854
+ when: "It is decoration, not information."
2855
+ }
2856
+ ]
2857
+ },
2858
+ setup: {
2859
+ required: [],
2860
+ notes: []
2861
+ },
2862
+ recipes: [],
2863
+ practices: [
2864
+ "accessible-by-default",
2865
+ "content-alignment"
2866
+ ]
2867
+ },
2689
2868
  {
2690
2869
  name: "TCard",
2691
2870
  aliases: [],
@@ -2759,6 +2938,9 @@ var treeui_catalog_default = {
2759
2938
  "unnamed_section_root: The default `section` root has no accessible name, so assistive tech exposes it as generic rather than as a region. The component sets no `inheritAttrs: false` and has a single root, so `aria-label` / `aria-labelledby` passed by the consumer fall through to that root when a named region is wanted."
2760
2939
  ],
2761
2940
  behavior: [
2941
+ "surface_never_inflated_by_a_descendant: The surface and its body declare `grid-template-columns: minmax(0, 1fr)`. An implicit `auto` track takes its minimum from the min-content of its items, so one unbreakable descendant \u2014 a table, a code block, a machine string \u2014 behind any wrapper widened the whole surface and laid every sibling out past the border: `TText truncate` never engaged, the TTable wrapper's `overflow-x` never scrolled, and anything aligned to the end was painted outside the surface or over the neighbouring card.",
2942
+ "title_wraps_instead_of_widening_the_card: `.t-card__title` carries `overflow-wrap: anywhere`. A title without spaces used to set the card's min-content width, and the `#header` slot was no way out because the same track was still `auto`.",
2943
+ "actions_shrink_and_wrap: `.t-card__actions` is `flex-shrink: 1; min-inline-size: 0; flex-wrap: wrap`. It was `flex-shrink: 0`, so in a narrow card the controls were laid out past the border instead of moving to the next line \u2014 the header already wraps, and the actions now follow it.",
2762
2944
  "body_wrapper_class: t-card__body",
2763
2945
  "header_render_condition: The `header.t-card__header` element renders when ANY of the `header` slot, a truthy `title` prop, or the `actions` slot is present. All three branches are independent \u2014 actions alone still produces a header.",
2764
2946
  "footer_render_condition: The `footer.t-card__footer` element renders only when the `footer` slot is supplied.",
@@ -2801,6 +2983,7 @@ var treeui_catalog_default = {
2801
2983
  practices: [
2802
2984
  "interaction-feedback",
2803
2985
  "predictable-api",
2986
+ "content-alignment",
2804
2987
  "token-driven"
2805
2988
  ]
2806
2989
  },
@@ -2817,6 +3000,18 @@ var treeui_catalog_default = {
2817
3000
  storyTitle: "Components/Data Display/Chart",
2818
3001
  manifestFile: "docs/ai/COMPONENTS/chart.yaml",
2819
3002
  props: [
3003
+ {
3004
+ name: "axisValueFormat",
3005
+ type: "(value: number) => string",
3006
+ default: "undefined",
3007
+ description: "Formatter for the AXIS ticks only, falling back to `valueFormat`. The two have different jobs and the axis is the one with no room: a tooltip can spell `R$ 1.500.000.000,00` in full, while the same string on a y-axis at 320px eats the plot \u2014 and the space reserved for it is capped at 40% of the chart, so past that it clips. One formatter could not serve both."
3008
+ },
3009
+ {
3010
+ name: "yAxisWidth",
3011
+ type: "number",
3012
+ default: "undefined",
3013
+ description: "Fix the space reserved for the y-axis labels, in pixels. Left unset, the chart MEASURES the formatted ticks and reserves what they need \u2014 the margin used to be a flat 44px, which clipped a formatted `1,234,567`. Set it to line several charts up on a shared gutter."
3014
+ },
2820
3015
  {
2821
3016
  name: "type",
2822
3017
  type: "TChartType",
@@ -2961,6 +3156,7 @@ var treeui_catalog_default = {
2961
3156
  "point-click has no keyboard equivalent \u2014 do not make it the only way to reach a behavior."
2962
3157
  ],
2963
3158
  behavior: [
3159
+ "edge_x_labels_anchor_with_a_class_not_an_attribute: The first and last x labels carry `is-anchor-start` / `is-anchor-end`, not a `text-anchor` ATTRIBUTE. CSS always beats a presentation attribute, so `.t-chart__axis-label--x { text-anchor: middle }` silently won and the anchoring never applied \u2014 the last label kept rendering centred and hanging off the SVG.",
2964
3160
  "colors_auto_assign_from_chart_palette: --tree-color-chart-1..8, cycling by series index; per-series color overrides",
2965
3161
  "zero_baseline_for_bar_and_area: the y domain is extended to include 0 for bar and area; line uses the data extent. minY overrides it.",
2966
3162
  "y_domain_uses_nice_ticks: every type runs through niceScale, which rounds the min down and the max up to round numbers",
@@ -2987,7 +3183,7 @@ var treeui_catalog_default = {
2987
3183
  ],
2988
3184
  avoidWhen: [
2989
3185
  "A single value tells the story \u2014 use TStat.",
2990
- "You only need an inline trend with no axes \u2014 use TSparkline.",
3186
+ "You only need an inline trend with no axes \u2014 use TSparkline (`fluid` fills the card width).",
2991
3187
  "The data is a part-of-whole breakdown \u2014 use TDonutChart."
2992
3188
  ],
2993
3189
  alternatives: [
@@ -3159,6 +3355,17 @@ var treeui_catalog_default = {
3159
3355
  storyTitle: "Components/Data Display/CodeBlock",
3160
3356
  manifestFile: "docs/ai/COMPONENTS/code-block.yaml",
3161
3357
  props: [
3358
+ {
3359
+ name: "size",
3360
+ type: "TSize",
3361
+ default: "md",
3362
+ values: [
3363
+ "sm",
3364
+ "md",
3365
+ "lg"
3366
+ ],
3367
+ description: "Density. Scales the type size and the padding of the `<pre>` by tokens, like every other sized component. `md` is what the block has always shipped; `sm` is for a block inside a dense surface \u2014 a table cell, a list row \u2014 where the fixed type size dominated the row."
3368
+ },
3162
3369
  {
3163
3370
  name: "code",
3164
3371
  type: "string",
@@ -3207,7 +3414,10 @@ var treeui_catalog_default = {
3207
3414
  'scroll_region: The `<pre>` is `role="region"` + `tabindex="0"` + `aria-label` (from `label`) so keyboard users can scroll it \u2014 the library does this for you.',
3208
3415
  "copy_button_state: The copy button's aria-label reflects the copied state briefly."
3209
3416
  ],
3210
- behavior: [],
3417
+ behavior: [
3418
+ "copyable_reserves_the_toolbar_width: With the copy button rendered, `.t-code-block.is-copyable .t-code-block__pre` reserves its width as `padding-inline-end`. The toolbar floats over the code, so a wrapped line used to run underneath it and the characters behind the button were unreadable. This only became visible once `wrap` started breaking for real: before, the long token overflowed sideways and never reached the button.",
3419
+ 'wrap_reduces_the_min_content: `.t-code-block.is-wrap .t-code-block__pre` uses `overflow-wrap: anywhere`, not `break-word`. `break-word` breaks only once a width is already settled, so wherever the block\'s width came from its content \u2014 a grid `auto` track, a table cell, a flex item without `min-width: 0` \u2014 the longest run without a break opportunity became the minimum width and pushed the container instead of wrapping. That is precisely the case `wrap` exists for: a URL, a token, a credential. Same semantics as `TText wrap="anywhere"`.'
3420
+ ],
3211
3421
  selection: {
3212
3422
  keywords: [
3213
3423
  "code",
@@ -3245,6 +3455,7 @@ var treeui_catalog_default = {
3245
3455
  recipes: [],
3246
3456
  practices: [
3247
3457
  "accessible-by-default",
3458
+ "content-alignment",
3248
3459
  "token-driven"
3249
3460
  ]
3250
3461
  },
@@ -3766,6 +3977,7 @@ var treeui_catalog_default = {
3766
3977
  "icon_slot_marked_aria_hidden: true"
3767
3978
  ],
3768
3979
  behavior: [
3980
+ "surface_never_inflated_by_a_descendant: `.t-confirm-dialog` and its body declare `grid-template-columns: minmax(0, 1fr)`, and the icon variant uses `auto minmax(0, 1fr)`. It inherits TModal's title and description wrapping, which is what keeps confirm and cancel on the surface when the dialog names a long resource \u2014 it passes `title`/`description` as strings and exposes no `#header` slot, so there was no way around it from the API.",
3769
3981
  "built_on_modal_contract: true",
3770
3982
  "default_confirm_button_variant: danger",
3771
3983
  "default_cancel_button_variant: ghost",
@@ -3811,7 +4023,8 @@ var treeui_catalog_default = {
3811
4023
  "confirm-destructive-action"
3812
4024
  ],
3813
4025
  practices: [
3814
- "accessible-by-default"
4026
+ "accessible-by-default",
4027
+ "content-alignment"
3815
4028
  ]
3816
4029
  },
3817
4030
  {
@@ -4191,6 +4404,8 @@ var treeui_catalog_default = {
4191
4404
  "focus_is_not_trapped_and_tab_leaves_the_open_calendar: true"
4192
4405
  ],
4193
4406
  behavior: [
4407
+ 'panel_is_teleported_to_the_body: The calendar panel renders through `<Teleport to="body">` and is placed with `position: fixed` from the trigger\'s viewport rect by `useAnchoredLayer`. It used to be absolutely positioned inside the root, so inside a `TModal` it was trapped in the surface and clipped. Query it from `document.body` in tests.',
4408
+ 'month_title_capitalises_only_its_first_letter: `text-transform: capitalize` uppercased every word, which in most locales hits a particle in the middle of the title \u2014 pt-BR\'s "setembro de 2026" rendered as "Setembro De 2026". The rule is now `none` plus a `::first-letter` uppercase.',
4194
4409
  "value_format: YYYY-MM-DD",
4195
4410
  "controlled_and_uncontrolled_open: true",
4196
4411
  "open_state_emits_are_deduped: true",
@@ -4239,7 +4454,9 @@ var treeui_catalog_default = {
4239
4454
  ]
4240
4455
  },
4241
4456
  recipes: [],
4242
- practices: []
4457
+ practices: [
4458
+ "accessible-by-default"
4459
+ ]
4243
4460
  },
4244
4461
  {
4245
4462
  name: "TDateTimePicker",
@@ -4680,6 +4897,7 @@ var treeui_catalog_default = {
4680
4897
  "Provide a meaningful ariaLabel describing what the chart shows."
4681
4898
  ],
4682
4899
  behavior: [
4900
+ "legend_label_is_the_flexible_column: `.t-donut-chart__legend-label` is `flex: 1 1 0` with `min-width: 0` and an ellipsis, and `.t-donut-chart__legend-value` is `flex: 0 0 auto`. A 60-character label used to push the value out of the card. The full text is carried in `title`, since truncation hides data the chart is meant to show.",
4683
4901
  "colors_auto_assign_from_chart_palette: --tree-color-chart-1..8, cycling by visible-segment index; per-segment color overrides",
4684
4902
  "hovering_a_slice_dims_the_rest_and_updates_center: the other segments get is-dimmed, the hovered donut segment grows to thickness + 4, and its legend row gets is-active",
4685
4903
  "hovering_a_legend_row_drives_the_same_highlight: legend items carry the same pointerenter/pointerleave handlers as the segments",
@@ -5253,6 +5471,48 @@ var treeui_catalog_default = {
5253
5471
  storyTitle: "Components/Data Entry/FileUpload",
5254
5472
  manifestFile: "docs/ai/COMPONENTS/file-upload.yaml",
5255
5473
  props: [
5474
+ {
5475
+ name: "uploadingLabel",
5476
+ type: "string",
5477
+ default: "Uploading files",
5478
+ description: "Accessible name for the spinner shown while `loading`."
5479
+ },
5480
+ {
5481
+ name: "uploadedAnnouncement",
5482
+ type: "(name: string) => string",
5483
+ default: "(n) => `${n} uploaded.`",
5484
+ description: "What the live region announces when a file succeeds. A function, because the file name sits inside the sentence and its position differs by language."
5485
+ },
5486
+ {
5487
+ name: "failedAnnouncement",
5488
+ type: "(name: string) => string",
5489
+ default: "(n) => `${n} failed.`",
5490
+ description: "The same, for a failure. The error text is appended after it."
5491
+ },
5492
+ {
5493
+ name: "showRejections",
5494
+ type: "boolean",
5495
+ default: "true",
5496
+ description: "Render the built-in rejection messages. Turn it off when the product already surfaces `files-rejected` in its own alert \u2014 otherwise the user reads the same refusal twice, and the embedded copy is the one that cannot be placed where the product wants it. The event fires either way."
5497
+ },
5498
+ {
5499
+ name: "clearLabel",
5500
+ type: "string",
5501
+ default: "Clear all",
5502
+ description: "Label for the button that drops every selected file. It was hard-coded English with no way out."
5503
+ },
5504
+ {
5505
+ name: "removeLabel",
5506
+ type: "string",
5507
+ default: "Remove",
5508
+ description: "Label for a row's remove button."
5509
+ },
5510
+ {
5511
+ name: "removeAriaLabel",
5512
+ type: "(file: File) => string",
5513
+ default: "(f) => `Remove ${f.name}`",
5514
+ description: "Accessible name for a row's remove button. A function, not a string, because the file name sits INSIDE the sentence and its position differs by language \u2014 a prefix and a suffix cannot express that."
5515
+ },
5256
5516
  {
5257
5517
  name: "modelValue",
5258
5518
  type: "File[]",
@@ -5461,6 +5721,7 @@ var treeui_catalog_default = {
5461
5721
  "remove_and_retry_meet_the_44px_target: true"
5462
5722
  ],
5463
5723
  behavior: [
5724
+ "a_rejection_follows_the_active_language: A rejection is held as DATA (the file and the reason) and formatted during render. It used to be stored as its rendered string, frozen at refusal time, so switching language afterwards left the old sentence on screen \u2014 the one piece of copy in this component that could not be retranslated.",
5464
5725
  "accepts_files_from_input_drop_and_paste: true",
5465
5726
  "multiple_false_replaces_existing_selection: true",
5466
5727
  "accept_maxFiles_and_maxFileSize_apply_to_all_entry_points: true",
@@ -5512,7 +5773,9 @@ var treeui_catalog_default = {
5512
5773
  recipes: [
5513
5774
  "page-drop-target"
5514
5775
  ],
5515
- practices: []
5776
+ practices: [
5777
+ "accessible-by-default"
5778
+ ]
5516
5779
  },
5517
5780
  {
5518
5781
  name: "TFlag",
@@ -5825,6 +6088,12 @@ var treeui_catalog_default = {
5825
6088
  name: "dense",
5826
6089
  type: "boolean",
5827
6090
  default: "false"
6091
+ },
6092
+ {
6093
+ name: "balance",
6094
+ type: "boolean",
6095
+ default: "false",
6096
+ note: 'Let the last row divide itself among the items it has instead of leaving empty tracks to its right \u2014 five cards over four tracks read as "one card failed to load". `auto-fit` cannot do it: it collapses a track only when that track is empty on EVERY row, so the remainder row keeps the widths of the rows above it. Balanced mode lays out with flex, where each line sizes itself: full rows are identical to the grid, only the remainder is shared out. Ignored when `columns` is set, since a declared track count already answers the same question.\n'
5828
6097
  }
5829
6098
  ],
5830
6099
  events: [],
@@ -5838,6 +6107,7 @@ var treeui_catalog_default = {
5838
6107
  ],
5839
6108
  behavior: [
5840
6109
  "semantic_element_controlled_by_as: true",
6110
+ "balance_shares_the_last_row_instead_of_leaving_holes: true",
5841
6111
  "fixed_columns_supported: true",
5842
6112
  "auto_fit_columns_supported: true",
5843
6113
  "dense_mode_uses_grid_auto_flow_dense: true"
@@ -6130,6 +6400,7 @@ var treeui_catalog_default = {
6130
6400
  "calendar-plus",
6131
6401
  "calendar-range",
6132
6402
  "calendar-x",
6403
+ "campaign",
6133
6404
  "carousel",
6134
6405
  "catalog",
6135
6406
  "chart-column",
@@ -6166,7 +6437,6 @@ var treeui_catalog_default = {
6166
6437
  "companion",
6167
6438
  "compass",
6168
6439
  "connections",
6169
- "contentpilot",
6170
6440
  "copy",
6171
6441
  "cpu",
6172
6442
  "cpu-chip",
@@ -6412,8 +6682,6 @@ var treeui_catalog_default = {
6412
6682
  "tickets",
6413
6683
  "timeline",
6414
6684
  "timer",
6415
- "toggle-left",
6416
- "toggle-right",
6417
6685
  "token-input",
6418
6686
  "token-output",
6419
6687
  "trail",
@@ -6451,10 +6719,18 @@ var treeui_catalog_default = {
6451
6719
  },
6452
6720
  {
6453
6721
  name: "size",
6454
- type: "number | string",
6722
+ type: "number | TIconSize",
6455
6723
  default: "20",
6456
- description: "Pixel size for both width and height of the rendered svg.",
6457
- note: 'This is a PIXEL COUNT, not the sm | md | lg token scale used by nearly every\nother TreeUI component. `<TIcon size="md" />` does not fail loudly: it writes\nwidth="md" / height="md" onto the svg, which the browser discards, so the\nicon becomes unsized and stretches to fill its container. Pass a number\n(20, 32) or a numeric string ("20"). To size an icon from tokens, leave `size`\nalone and set width/height in CSS from the icon scale \u2014 --tree-size-icon-sm |\n-md | -lg | -xl | -2xl (1rem / 1.25rem / 1.5rem / 2rem / 3rem) \u2014 which is what\nTLanguageSelect\'s switcher variant does.\n'
6724
+ values: [
6725
+ "sm",
6726
+ "md",
6727
+ "lg",
6728
+ "xl",
6729
+ "2xl",
6730
+ "<number of pixels>"
6731
+ ],
6732
+ description: "A token name from the icon scale, or a pixel number.",
6733
+ note: '`size` used to be `number | string`, which meant `<TIcon size="sm" />`\ntype-checked, wrote width="sm" onto the svg, and left the icon unsized \u2014\nit then stretched to fill its container, measured at 300px in the report\nthat produced this change. It now takes either a NUMBER of pixels or one\nof the icon-scale token names, which map to the same values as\n--tree-size-icon-sm | -md | -lg | -xl | -2xl (16 / 20 / 24 / 32 / 48 px).\nAn unrecognised string is a compile error in TypeScript and, for\nJavaScript consumers, falls back to the default size with a console\nwarning in development rather than rendering at an arbitrary size.\nA free-form CSS length is no longer accepted: set width/height in CSS\nfrom the icon scale instead, which is what TLanguageSelect\'s switcher\nvariant does.\n'
6458
6734
  },
6459
6735
  {
6460
6736
  name: "strokeWidth",
@@ -6484,7 +6760,7 @@ var treeui_catalog_default = {
6484
6760
  behavior: [
6485
6761
  "unknown_name_renders_nothing: An unregistered name makes `resolveTreeIcon` return `undefined`, and `TIcon`'s `v-if` then renders no element at all \u2014 no empty box, no placeholder, no error. A `console.warn` names the missing icon, suggests the `registerTreeIcons` call, and lists every valid name. It warns once per NAME (not per message \u2014 the message embeds the valid-name list, which changes on every registration), so a miss inside a render loop cannot flood the console. The warning is NOT gated on `process.env.NODE_ENV`: it fires in production builds too, deliberately, because a missing icon is a misconfiguration worth hearing about once and `process` is undefined in most browser bundles. Components with a marker fallback (TNavMenu) show that instead \u2014 see nav-menu.yaml.",
6486
6762
  'absolute_stroke_width_math: Icons draw on `viewBox="0 0 24 24"` with the svg sized to `size`, so painted pixels = (stroke-width attribute) * size / 24. With `absoluteStrokeWidth` (the default) the attribute is set to `(strokeWidth * 24) / size`, which cancels the scale: at the default `strokeWidth: 2` the attribute is 2.4 at size 20, 1.5 at 32, and 1.0 at 48 \u2014 a constant 2px painted at every size. That is the entire point of the correction: a 48px icon sits beside a 20px one at the same optical weight. A `size` of 0 or a non-numeric value keeps the nominal stroke rather than producing NaN/Infinity. Setting `absoluteStrokeWidth: false` pins the attribute at `strokeWidth`, which is the hand-rolled behaviour: the painted stroke then scales WITH the icon (1.67px at 20, 2.67px at 32, 4px at 48 \u2014 double the library weight).',
6487
- "icons_are_built_lazily_and_cached: Geometry lives in a data registry; a component is created on first lookup and reused after that, so an unused icon stays as DATA instead of becoming a `defineComponent`. Note what this does not claim: the geometry for all 364 built-ins is retained by any import of the package (the registry copies `builtinTreeIconNodes` into a `Map` at module scope, so a live reference exists and nothing tree-shakes it away \u2014 `sideEffects: false` does not change that). The saving is component construction, not bundle bytes.",
6763
+ "icons_are_built_lazily_and_cached: Geometry lives in a data registry; a component is created on first lookup and reused after that, so an unused icon stays as DATA instead of becoming a `defineComponent`. Note what this does not claim: the geometry for all 370 built-ins is retained by any import of the package (the registry copies `builtinTreeIconNodes` into a `Map` at module scope, so a live reference exists and nothing tree-shakes it away \u2014 `sideEffects: false` does not change that). The saving is component construction, not bundle bytes.",
6488
6764
  "resolved_per_render: TIcon resolves its name in a computed, not once at setup, so an icon registered after the component mounted still appears.",
6489
6765
  "migration_aliases: Twelve compatibility names resolve to the same geometry as their canonical names: grid \u2192 layout-grid, share \u2192 share-nodes, chevron-updown \u2192 chevrons-up-down, refresh \u2192 refresh-cw, comment/chat \u2192 message-circle, check-square \u2192 square-check, help \u2192 circle-help, automations \u2192 zap, connections \u2192 external-link, persona \u2192 user-round, and close \u2192 x.",
6490
6766
  "company_logos_are_app_owned: Company logos are not built-ins. When an application needs one, it imports the official SVG as its own component and passes or registers that component under an application-owned name."
@@ -6534,7 +6810,89 @@ var treeui_catalog_default = {
6534
6810
  recipes: [
6535
6811
  "nav-menu-icons"
6536
6812
  ],
6537
- practices: []
6813
+ practices: [
6814
+ "branchline-icons"
6815
+ ]
6816
+ },
6817
+ {
6818
+ name: "TIconTile",
6819
+ aliases: [],
6820
+ package: "@treeui/vue",
6821
+ kind: "display",
6822
+ category: "Data Display",
6823
+ status: "available",
6824
+ purpose: "A registered icon on a toned ground, sized and coloured so the glyph reads at phone size.",
6825
+ sourceFile: "packages/vue/src/components/TIconTile.vue",
6826
+ storyFile: "apps/docs/src/stories/IconTile.stories.ts",
6827
+ storyTitle: "Components/Data Display/IconTile",
6828
+ manifestFile: "docs/ai/COMPONENTS/icon-tile.yaml",
6829
+ props: [
6830
+ {
6831
+ name: "name",
6832
+ type: "TIconName",
6833
+ default: "undefined",
6834
+ description: "Registry name. Use the default slot for a glyph that is not registered."
6835
+ },
6836
+ {
6837
+ name: "tone",
6838
+ type: "TIconTileTone",
6839
+ default: "brand",
6840
+ values: [
6841
+ "brand",
6842
+ "neutral",
6843
+ "success",
6844
+ "warning",
6845
+ "danger",
6846
+ "info"
6847
+ ],
6848
+ description: "Selects the `--tree-icon-tile-accent` / `--tree-icon-tile-soft` pair. The ink is the tone's full-strength colour on its own `*-soft` tint, which is the pairing that tint is derived for."
6849
+ },
6850
+ {
6851
+ name: "size",
6852
+ type: "TSize",
6853
+ default: "md",
6854
+ values: [
6855
+ "sm",
6856
+ "md",
6857
+ "lg"
6858
+ ],
6859
+ description: "2rem / 2.75rem / 3.5rem. The glyph steps with the tile (16 / 20 / 24px) so the ink keeps its proportion of the pill."
6860
+ },
6861
+ {
6862
+ name: "label",
6863
+ type: "string",
6864
+ default: "undefined",
6865
+ description: "Accessible name. Omit it when the tile sits beside a heading that already says the same thing \u2014 the glyph is then decoration, and a screen reader reading it twice is noise."
6866
+ }
6867
+ ],
6868
+ events: [],
6869
+ slots: [
6870
+ {
6871
+ name: "default"
6872
+ }
6873
+ ],
6874
+ a11y: [
6875
+ "semantic_element: span",
6876
+ 'role_img_only_when_named: With `label` the tile is `role="img"` with that name; without it, `aria-hidden`. There is no third state where a glyph is announced as nothing.',
6877
+ "contrast_floor: 3:1, measured. An icon is a non-text UI component under WCAG 1.4.11, so the floor is 3:1 and not the 4.5:1 a badge has to clear \u2014 a badge carries a word. `packages/tokens/src/icon-tile-contrast.test.ts` measures every tone in both themes and fails the build below the floor, the same way the badge's own floor is held."
6878
+ ],
6879
+ behavior: [],
6880
+ selection: {
6881
+ keywords: [],
6882
+ useWhen: [],
6883
+ avoidWhen: [],
6884
+ alternatives: []
6885
+ },
6886
+ setup: {
6887
+ required: [],
6888
+ notes: []
6889
+ },
6890
+ recipes: [],
6891
+ practices: [
6892
+ "accessible-by-default",
6893
+ "content-alignment",
6894
+ "token-driven"
6895
+ ]
6538
6896
  },
6539
6897
  {
6540
6898
  name: "TImage",
@@ -6657,7 +7015,7 @@ var treeui_catalog_default = {
6657
7015
  name: "modelValue",
6658
7016
  type: "T",
6659
7017
  default: "undefined",
6660
- note: "Generic over `T extends string | number`, so a `ref<number>` binds without a cast and `update:modelValue` pays back a number. `<script setup>` was widening every model to `string | number`, which under a consumer's `strictTemplates` made a numeric field a type error at the call site with no correct way to write it (TREEUX-011, group 3). When `T` is `number` the component parses the raw input value and only falls back to the raw string when it does not parse.\n"
7018
+ note: "Generic over `T extends string | number`, so a `ref<number>` binds without a cast and `update:modelValue` pays back a number. `<script setup>` was widening every model to `string | number`, and widening makes a numeric field a type error under `strictTemplates`, with no correct way to write the binding. When `T` is `number` the component parses the raw input value and only falls back to the raw string when it does not parse.\n"
6661
7019
  },
6662
7020
  {
6663
7021
  name: "size",
@@ -7369,7 +7727,9 @@ var treeui_catalog_default = {
7369
7727
  "aria_current: `page` when `current`.",
7370
7728
  "focus_visible: Focus ring on the tile."
7371
7729
  ],
7372
- behavior: [],
7730
+ behavior: [
7731
+ "default_slot_does_not_stretch_its_children: `.t-link-tile__body` is `align-items: flex-start`, not the `stretch` default. The default slot is where a consumer drops a TTag or a TBadge, and stretch made a four-character tag as wide as the description. Title and description are TText and still wrap to the available width."
7732
+ ],
7373
7733
  selection: {
7374
7734
  keywords: [
7375
7735
  "tile",
@@ -7405,6 +7765,7 @@ var treeui_catalog_default = {
7405
7765
  practices: [
7406
7766
  "interaction-feedback",
7407
7767
  "accessible-by-default",
7768
+ "content-alignment",
7408
7769
  "token-driven"
7409
7770
  ]
7410
7771
  },
@@ -7451,7 +7812,8 @@ var treeui_catalog_default = {
7451
7812
  "not_selectable: For a single-select list of options use TSelectableList instead."
7452
7813
  ],
7453
7814
  behavior: [
7454
- "responsive: On a container narrower than 30rem the row wraps: meta drops below the content while actions stay visible inline. Actions are NEVER hidden or auto-collapsed into an overflow menu \u2014 compose a TMenu inside the actions slot deliberately if an overflow is wanted (uses a CSS container query; the list sets container-type).",
7815
+ "actions_take_their_own_line_in_a_narrow_container: Inside the existing `@container (max-width: 30rem)` query, `.t-list-item__actions` becomes `flex-basis: 100%; order: 2; flex-wrap: wrap; justify-content: flex-end`. Rigid actions used to squeeze the content column down to 40\u2013110px rather than moving below it \u2014 the meta already dropped to its own line, and the actions now follow the same rule. Actions are still never hidden or auto-collapsed into an overflow menu; that stays a deliberate consumer choice.",
7816
+ "responsive: On a container narrower than 30rem the row wraps: meta drops below the content while actions stay visible inline, and the leading slot stays on the first line of the content \u2014 the wrap is for the meta only. Actions are NEVER hidden or auto-collapsed into an overflow menu \u2014 compose a TMenu inside the actions slot deliberately if an overflow is wanted (uses a CSS container query; the list sets container-type).",
7455
7817
  "no_overflow_ownership: TList does not create, own, or collapse an actions overflow."
7456
7818
  ],
7457
7819
  selection: {
@@ -7614,7 +7976,7 @@ var treeui_catalog_default = {
7614
7976
  kind: "overlay",
7615
7977
  category: "Overlay",
7616
7978
  status: "available",
7617
- purpose: "Rich action menu in a popover \u2014 slot-driven compound with groups, icons, descriptions, meta, destructive items, link items, and a radio (workspace-switch) item, with roving focus and keyboard support. Built on TPopover, so anchoring and focus restore come from there (TREEUX-015).",
7979
+ purpose: "Rich action menu in a popover \u2014 slot-driven compound with groups, icons, descriptions, meta, destructive items, link items, and a radio (workspace-switch) item, with roving focus and keyboard support. Built on TPopover, so anchoring and focus restore come from there.",
7618
7980
  sourceFile: "packages/vue/src/components/TMenu.vue",
7619
7981
  storyFile: "apps/docs/src/stories/Menu.stories.ts",
7620
7982
  storyTitle: "Components/Overlay/Menu",
@@ -7859,6 +8221,10 @@ var treeui_catalog_default = {
7859
8221
  "escape_closes_when_enabled: true"
7860
8222
  ],
7861
8223
  behavior: [
8224
+ "surface_never_inflated_by_a_descendant: The surface and its body declare `grid-template-columns: minmax(0, 1fr)`. An implicit `auto` track takes its minimum from the min-content of its items, so one unbreakable descendant \u2014 a table, a code block, a machine string \u2014 behind any wrapper widened the whole surface and laid every sibling out past the border: `TText truncate` never engaged, the TTable wrapper's `overflow-x` never scrolled, and anything aligned to the end was painted outside the surface or over the neighbouring card.",
8225
+ "only_the_body_scrolls: `.t-modal__surface` is a flex column with `overflow: hidden`, and `.t-modal__body` is the single scroller (`flex: 1 1 auto; min-block-size: 0; overflow: auto`). The whole surface used to scroll, so on a short viewport the footer's actions sat below the fold and the title scrolled away with the content. A flex column also removes the implicit `auto` track, which is the other half of the inflation above.",
8226
+ "title_and_description_wrap: `.t-modal__title` and `.t-modal__description` carry `overflow-wrap: anywhere` \u2014 `anywhere`, not `break-word`, because only `anywhere` reduces the min-content. A confirm dialog exists to name the thing being deleted, and a resource name without a break opportunity used to push confirm and cancel off the surface at any width, desktop included.",
8227
+ "a_child_overlay_must_be_teleported: Because the surface now clips, a panel rendered inside it would be cut off. TSelect's listbox and TDatePicker's panel teleport to the body for exactly this reason; a consumer composing its own overlay inside a modal should do the same with `useAnchoredLayer`.",
7862
8228
  "uses_teleport_to_body: true",
7863
8229
  "locks_body_scroll_while_open: true",
7864
8230
  "overlay_click_closes_when_enabled: true",
@@ -7903,7 +8269,8 @@ var treeui_catalog_default = {
7903
8269
  ],
7904
8270
  practices: [
7905
8271
  "accessible-by-default",
7906
- "predictable-api"
8272
+ "predictable-api",
8273
+ "content-alignment"
7907
8274
  ]
7908
8275
  },
7909
8276
  {
@@ -8249,6 +8616,7 @@ var treeui_catalog_default = {
8249
8616
  "aria_disabled: on link items when disabled"
8250
8617
  ],
8251
8618
  behavior: [
8619
+ "labels_wrap_rather_than_scroll_the_sidebar: `.t-nav-menu__list` and `.t-nav-menu__copy` declare `grid-template-columns: minmax(0, 1fr)`, and the label and description carry `overflow-wrap: anywhere`. A sidebar is the one place a horizontal scrollbar is never the right answer, and an item label without a break opportunity used to give it one.",
8252
8620
  "can_be_controlled_or_uncontrolled: true",
8253
8621
  "can_inherit_collapsed_state_from_sidebar: true",
8254
8622
  "supports_rich_item_rendering_via_slot: true"
@@ -8296,7 +8664,8 @@ var treeui_catalog_default = {
8296
8664
  "responsive-navigation"
8297
8665
  ],
8298
8666
  practices: [
8299
- "interaction-feedback"
8667
+ "interaction-feedback",
8668
+ "content-alignment"
8300
8669
  ]
8301
8670
  },
8302
8671
  {
@@ -8315,7 +8684,7 @@ var treeui_catalog_default = {
8315
8684
  {
8316
8685
  name: "modelValue",
8317
8686
  type: "number | null",
8318
- note: "`null` is REAL, not an error state: clearing the field is empty, and the component emits `null` for it. So `v-model` needs a `ref<number | null>`, and a `ref<number>` is a genuine type error under a consumer's `strictTemplates` rather than a gap in the library. This is the one place in the round where the checker was right and the call site was wrong \u2014 the others (TInput, TTabs, TToggleGroup) were the library's fault.\n"
8687
+ note: "`null` is REAL, not an error state: clearing the field is empty, and the component emits `null` for it. So `v-model` needs a `ref<number | null>`, and a `ref<number>` is a genuine type error under `strictTemplates` rather than a gap in the library \u2014 here the binding is what has to widen, not the model. The opposite of TInput/TTabs/TToggleGroup, where the library's own model type was too wide and no narrow ref could bind it.\n"
8319
8688
  },
8320
8689
  {
8321
8690
  name: "min",
@@ -8563,6 +8932,7 @@ var treeui_catalog_default = {
8563
8932
  "Actions area holds standard focusable controls (e.g. TButton) \u2014 no custom roles added."
8564
8933
  ],
8565
8934
  behavior: [
8935
+ "slot_presence_is_read_during_render: The template tests `$slots.title` / `$slots.subtitle` directly instead of a `computed` over `useSlots()`. The computed cached its first answer, so a `v-if`'d title slot that appeared later never rendered, and one that disappeared left an empty `<h1>` behind. This is the arrangement TCard already used for `$slots.header`.",
8566
8936
  "title_and_subtitle_optional: true",
8567
8937
  "heading_level_controlled_by_level: true",
8568
8938
  "actions_area_right_aligned_and_wraps: true",
@@ -9583,14 +9953,14 @@ var treeui_catalog_default = {
9583
9953
  props: [
9584
9954
  {
9585
9955
  name: "modelValue",
9586
- type: "string | number",
9587
- default: ""
9956
+ type: "T",
9957
+ default: "undefined"
9588
9958
  },
9589
9959
  {
9590
9960
  name: "options",
9591
- type: "TSelectOption[]",
9961
+ type: "TSelectOption<T>[]",
9592
9962
  default: "[]",
9593
- note: "Array of { label: string; value: string | number; disabled?: boolean }"
9963
+ note: "Array of { label: string; value: T; disabled?: boolean }"
9594
9964
  },
9595
9965
  {
9596
9966
  name: "open",
@@ -9686,7 +10056,9 @@ var treeui_catalog_default = {
9686
10056
  "outside_click_closes_dropdown: true",
9687
10057
  "focus_restores_on_close: true",
9688
10058
  "numeric_values_supported_without_string_coercion: true",
9689
- "listbox_flips_above_trigger_when_space_below_is_insufficient: true"
10059
+ "listbox_flips_above_trigger_when_space_below_is_insufficient: true",
10060
+ "option_label_wraps_instead_of_being_clipped: `.t-select__option` carries `overflow-wrap: anywhere`, so a label without a break opportunity wraps inside the listbox rather than overflowing it and being cut off.",
10061
+ "listbox_is_teleported_to_the_body: The listbox renders through `<Teleport to=\"body\">` and is placed with `position: fixed` from the trigger's viewport rect by the `useAnchoredLayer` composable, matching the trigger's width. It used to be positioned inside the root, which meant ANY ancestor with `overflow` clipped it \u2014 the TTable wrapper cut it off and a modal trapped it. Consequences for tests: the listbox is not a descendant of the component's root element, so query it from `document.body`."
9690
10062
  ],
9691
10063
  selection: {
9692
10064
  keywords: [
@@ -9724,6 +10096,7 @@ var treeui_catalog_default = {
9724
10096
  ],
9725
10097
  practices: [
9726
10098
  "interaction-feedback",
10099
+ "accessible-by-default",
9727
10100
  "predictable-api",
9728
10101
  "token-driven"
9729
10102
  ]
@@ -10197,7 +10570,9 @@ var treeui_catalog_default = {
10197
10570
  "Hit area is `--tree-size-control-lg` (48px) tall, above the 44px target floor.",
10198
10571
  "The label must say where it goes; the library does not supply one."
10199
10572
  ],
10200
- behavior: [],
10573
+ behavior: [
10574
+ "hidden_link_paints_no_shadow: `.t-skip-link:not(:focus-visible)` sets `box-shadow: none`. The box is translated off-screen while hidden, but a 14px offset with a 34px blur is not: it painted a grey smudge in the top-left of every header, at every width, all the time. The shadow belongs to the focused state, which is when the link is actually on screen."
10575
+ ],
10201
10576
  selection: {
10202
10577
  keywords: [
10203
10578
  "skip link",
@@ -10422,6 +10797,12 @@ var treeui_catalog_default = {
10422
10797
  default: "false",
10423
10798
  note: "Marker on the final point (line/area)."
10424
10799
  },
10800
+ {
10801
+ name: "fluid",
10802
+ type: "boolean",
10803
+ default: "false",
10804
+ note: "Fill the parent's width; `width` stays the viewBox. The height is unchanged on purpose \u2014 a sparkline is a band, and growing in both directions turns it into a chart that lies about its precision. Strokes use `non-scaling-stroke`, so the pen keeps its declared width.\n"
10805
+ },
10425
10806
  {
10426
10807
  name: "ariaLabel",
10427
10808
  type: "string",
@@ -10437,7 +10818,8 @@ var treeui_catalog_default = {
10437
10818
  behavior: [
10438
10819
  "no_axes_or_legend: true",
10439
10820
  "flat_series_renders_centered: true",
10440
- "scales_to_intrinsic_width_and_height: true"
10821
+ "scales_to_intrinsic_width_and_height: true",
10822
+ "fluid_fills_parent_width: true"
10441
10823
  ],
10442
10824
  selection: {
10443
10825
  keywords: [
@@ -10477,7 +10859,9 @@ var treeui_catalog_default = {
10477
10859
  recipes: [
10478
10860
  "metric-dashboard"
10479
10861
  ],
10480
- practices: []
10862
+ practices: [
10863
+ "content-alignment"
10864
+ ]
10481
10865
  },
10482
10866
  {
10483
10867
  name: "TSpinner",
@@ -10836,6 +11220,33 @@ var treeui_catalog_default = {
10836
11220
  storyTitle: "Components/Data Display/Stat",
10837
11221
  manifestFile: "docs/ai/COMPONENTS/stat.yaml",
10838
11222
  props: [
11223
+ {
11224
+ name: "variant",
11225
+ type: "TStatVariant",
11226
+ default: "card (plain inside a TStatGroup)",
11227
+ values: [
11228
+ "card",
11229
+ "plain"
11230
+ ],
11231
+ description: "Whether the tile draws its own card. `plain` keeps the padding, the type and the tones and drops the border, radius, shadow and background. Inside a `TStatGroup` the default flips to `plain`, because the group owns the surface; an explicit value always wins. A stat drawing its own card inside a TCard is a card within a card \u2014 doubled frame, summed padding, and eight of them read as eight objects rather than one row of figures."
11232
+ },
11233
+ {
11234
+ name: "metaPlacement",
11235
+ type: "'top' | 'bottom'",
11236
+ default: "top",
11237
+ description: 'Where the `meta` note sits. `top` shares the label\'s line; `bottom` puts it under the value, for the reading order of an indicator with a footnote. It is also a height fix: on the shared line a long label and a long note do not fit, the note wraps onto a line of its own ABOVE the value, and that one tile grows \u2014 the grid then stretches the whole row and every sibling shows an empty band. With `bottom` the DOM follows the visual order, so the announced order is label, value, note. Different from `emphasis="value"`, which promotes the FIGURE and takes the label down with it.'
11238
+ },
11239
+ {
11240
+ name: "size",
11241
+ type: "TSize",
11242
+ default: "md",
11243
+ values: [
11244
+ "sm",
11245
+ "md",
11246
+ "lg"
11247
+ ],
11248
+ description: "Density. `sm` lowers the value's floor from 1.5rem to 1.125rem and tightens the padding, which is what lets two stats share a row on a phone \u2014 at the default floor a formatted negative currency does not fit, so a grid of six indicators collapsed into one tall column. The value still scales to its card through the container query, not to the viewport."
11249
+ },
10839
11250
  {
10840
11251
  name: "label",
10841
11252
  type: "string",
@@ -10882,6 +11293,16 @@ var treeui_catalog_default = {
10882
11293
  name: "loading",
10883
11294
  type: "boolean",
10884
11295
  default: "false"
11296
+ },
11297
+ {
11298
+ name: "emphasis",
11299
+ type: "TStatEmphasis",
11300
+ default: "label",
11301
+ values: [
11302
+ "label",
11303
+ "value"
11304
+ ],
11305
+ note: 'Which half leads. `label` is the dashboard reading \u2014 the eye is scanning for what is measured. `value` puts the figure first, for a marketing band where the number IS the argument; it also keeps a row of figures on one baseline, because the labels above them no longer have to be the same height. Visual order only: the DOM keeps label before value, so the announcement stays "Requests served, 4.2M".\n'
10885
11306
  }
10886
11307
  ],
10887
11308
  events: [],
@@ -10908,6 +11329,7 @@ var treeui_catalog_default = {
10908
11329
  ],
10909
11330
  behavior: [
10910
11331
  "value_and_trend_are_optional: true",
11332
+ "emphasis_reorders_visually_not_in_the_dom: true",
10911
11333
  "icon_slot_adds_visual_emphasis: true",
10912
11334
  "trend_indicator_changes_with_direction: true",
10913
11335
  "loading_renders_internal_skeleton_placeholders: true"
@@ -10945,6 +11367,76 @@ var treeui_catalog_default = {
10945
11367
  "metric-dashboard"
10946
11368
  ],
10947
11369
  practices: [
11370
+ "content-alignment",
11371
+ "token-driven"
11372
+ ]
11373
+ },
11374
+ {
11375
+ name: "TStatGroup",
11376
+ aliases: [],
11377
+ package: "@treeui/vue",
11378
+ kind: "layout",
11379
+ status: "available",
11380
+ purpose: "A row of indicators sharing one surface, with a hairline between cells.",
11381
+ sourceFile: "packages/vue/src/components/TStatGroup.vue",
11382
+ manifestFile: "docs/ai/COMPONENTS/stat-group.yaml",
11383
+ props: [
11384
+ {
11385
+ name: "label",
11386
+ type: "string",
11387
+ default: "undefined",
11388
+ description: 'Accessible name for the band. Sets `aria-label` on a `role="group"`, so the row is announced as one unit rather than as a run of unrelated figures.'
11389
+ },
11390
+ {
11391
+ name: "columns",
11392
+ type: "number",
11393
+ default: "undefined",
11394
+ description: "Fixed track count. Leave unset to let `minItemWidth` decide."
11395
+ },
11396
+ {
11397
+ name: "minItemWidth",
11398
+ type: "string",
11399
+ default: "12rem",
11400
+ description: "Smallest a cell may get before a track is dropped. Same meaning as on TGrid."
11401
+ },
11402
+ {
11403
+ name: "balance",
11404
+ type: "boolean",
11405
+ default: "false",
11406
+ description: "Let the last row share itself out. Off by default here, unlike TGrid, and deliberately: a stat's value scales to its own cell (`container-type: inline-size`), so a lone cell stretched across the last row prints a number visibly larger than its siblings', and size on a dashboard reads as importance. An orphan beside empty space is the lesser mistake. The real fix for a band is a track count that divides the figures \u2014 set `columns`, or a `minItemWidth` that lands on one."
11407
+ }
11408
+ ],
11409
+ events: [],
11410
+ slots: [
11411
+ {
11412
+ name: "default"
11413
+ }
11414
+ ],
11415
+ a11y: [
11416
+ "semantic_element: div",
11417
+ "role: group",
11418
+ "naming: `label` is what makes the band a named unit. Without it the group has no accessible name and is announced as an unlabelled grouping."
11419
+ ],
11420
+ behavior: [
11421
+ 'children_render_plain_without_being_told: The group provides a context that TStat reads, so every child defaults to `variant="plain"` inside it. The alternative is the consumer repeating `variant="plain"` on all eight, which is the alignment busywork this library exists to absorb \u2014 and which goes wrong on the ninth one added later. An explicit `variant` on a child still wins.',
11422
+ "the_track_is_a_TGrid: Composed, not re-implemented, so `columns`, `minItemWidth` and `balance` behave exactly as they do everywhere else. `gap` is 0: the cells have to meet for the hairline to be a shared edge, and the breathing room comes from each cell's own padding.",
11423
+ "hairlines_survive_reflow: Every cell carries a start and a top line as pseudo-elements, and the track is pulled back one pixel so the outermost pair falls outside the clipped box. A cell that reflows onto a new row keeps its top line and becomes the first of its row, where its start line is the one being clipped \u2014 nothing hanging on the outer edge, nothing doubled between neighbours. `:nth-child` cannot express this, because which cell starts a row depends on a track count only the browser knows.",
11424
+ "pseudo_elements_not_borders: A border would land on the child's own `border-width`. The contract is that a cell in a band measures as frameless from the outside \u2014 `border-width` is actually 0 \u2014 so the lines are drawn beside the cell rather than on it."
11425
+ ],
11426
+ selection: {
11427
+ keywords: [],
11428
+ useWhen: [],
11429
+ avoidWhen: [],
11430
+ alternatives: []
11431
+ },
11432
+ setup: {
11433
+ required: [],
11434
+ notes: []
11435
+ },
11436
+ recipes: [],
11437
+ practices: [
11438
+ "accessible-by-default",
11439
+ "content-alignment",
10948
11440
  "token-driven"
10949
11441
  ]
10950
11442
  },
@@ -10957,7 +11449,7 @@ var treeui_catalog_default = {
10957
11449
  kind: "navigation",
10958
11450
  category: "Navigation",
10959
11451
  status: "available",
10960
- purpose: "Stepper component for visualizing workflow progress, with optional interactive selection.",
11452
+ purpose: "Ordered sequence \u2014 a wizard to operate, or a described flow to read.",
10961
11453
  sourceFile: "packages/vue/src/components/TSteps.vue",
10962
11454
  storyFile: "apps/docs/src/stories/Steps.stories.ts",
10963
11455
  storyTitle: "Components/Navigation/Steps",
@@ -11001,7 +11493,20 @@ var treeui_catalog_default = {
11001
11493
  {
11002
11494
  name: "interactive",
11003
11495
  type: "boolean",
11004
- default: "false"
11496
+ default: "false",
11497
+ note: "Whether a step can be picked. False \u2014 the default \u2014 renders inert boxes: no button in the accessibility tree, no disabled wash, and no step elected current unless `modelValue`/`defaultValue` or an item `status` says so. A described sequence used to render one dead control per step, at 60% opacity, with the first step ringed as if the reader were standing on it.\n"
11498
+ },
11499
+ {
11500
+ name: "columns",
11501
+ type: "number",
11502
+ default: "undefined",
11503
+ note: "Fixed steps per row, horizontal only. Use for a sequence with a shape (3 + 3)."
11504
+ },
11505
+ {
11506
+ name: "minItemWidth",
11507
+ type: "string",
11508
+ default: "14rem",
11509
+ note: "Width at which a step stops shrinking and the strip wraps. Same axis as TGrid."
11005
11510
  }
11006
11511
  ],
11007
11512
  events: [
@@ -11017,10 +11522,13 @@ var treeui_catalog_default = {
11017
11522
  a11y: [
11018
11523
  "semantic_root: ol",
11019
11524
  "current_step_uses_aria_current_step: true",
11020
- "interactive_variant_uses_buttons: true"
11525
+ "interactive_variant_uses_buttons: true",
11526
+ "static_variant_emits_no_control: true"
11021
11527
  ],
11022
11528
  behavior: [
11023
11529
  "can_be_controlled_or_uncontrolled: true",
11530
+ "static_variant_elects_no_current_step: true",
11531
+ "shared_box_class_is_t_steps_box: true",
11024
11532
  "status_can_be_explicit_or_computed: true",
11025
11533
  "alias_export_available: true"
11026
11534
  ],
@@ -11051,7 +11559,10 @@ var treeui_catalog_default = {
11051
11559
  ]
11052
11560
  },
11053
11561
  recipes: [],
11054
- practices: []
11562
+ practices: [
11563
+ "accessible-by-default",
11564
+ "content-alignment"
11565
+ ]
11055
11566
  },
11056
11567
  {
11057
11568
  name: "TSwitch",
@@ -11158,11 +11669,23 @@ var treeui_catalog_default = {
11158
11669
  storyTitle: "Components/Data Display/Table",
11159
11670
  manifestFile: "docs/ai/COMPONENTS/table.yaml",
11160
11671
  props: [
11672
+ {
11673
+ name: "stackBelow",
11674
+ type: "TBreakpoint",
11675
+ default: "undefined",
11676
+ values: [
11677
+ "sm",
11678
+ "md",
11679
+ "lg",
11680
+ "xl"
11681
+ ],
11682
+ description: "Below this CONTAINER width the grid becomes one block per row: the column marked `stack=\"title\"` is the block's heading, the rest sit under it, and each value carries its column's label because the header row is gone. Above it the table is unchanged, horizontal scroll included. Measured on the wrapper and not the viewport \u2014 a table can sit in a narrow panel on a wide screen, which is the case a media query cannot see. A breakpoint name and not a free length: a container query cannot read a custom property in its condition, so an arbitrary value would need a ResizeObserver per table."
11683
+ },
11161
11684
  {
11162
11685
  name: "columns",
11163
11686
  type: "TTableColumn[]",
11164
11687
  required: true,
11165
- description: "Array of column definitions with key, label, sortable, align, and width."
11688
+ description: "Array of column definitions with key, label, sortable, align, width, and minWidth."
11166
11689
  },
11167
11690
  {
11168
11691
  name: "rows",
@@ -11204,6 +11727,35 @@ var treeui_catalog_default = {
11204
11727
  type: "(row, index) => 'default' | 'muted'",
11205
11728
  required: false,
11206
11729
  description: "Semantic per-row state applied by data, not position, so it survives sorting. `muted` recesses a row (e.g. a resource that no longer exists) via `t-table__row--muted`, written at compound specificity so it wins over the zebra/hover rules."
11730
+ },
11731
+ {
11732
+ name: "rowHref",
11733
+ type: "(row: Record<string, unknown>, index: number) => string | undefined",
11734
+ default: "undefined",
11735
+ description: 'The row NAVIGATES: a real `href` per row, rendered as an `<a>` in the first cell and stretched over the whole row via `::after`, so ctrl/middle-click, "open in new tab", and the status-bar URL all work and the accessible role is `link`. Mutually exclusive with `rowTo` and `rowActivatable` \u2014 dev builds warn. The affordance follows the RESOLVED target: a row whose function returns `undefined` renders no link and gets no pointer.'
11736
+ },
11737
+ {
11738
+ name: "rowTo",
11739
+ type: "(row: Record<string, unknown>, index: number) => string | undefined",
11740
+ default: "undefined",
11741
+ description: "Same as `rowHref`, resolved through the app's RouterLink instead of a plain `<a>`."
11742
+ },
11743
+ {
11744
+ name: "rowLabel",
11745
+ type: "(row: Record<string, unknown>, index: number) => string",
11746
+ default: "undefined",
11747
+ description: "Accessible name for the row's activator, per row \u2014 used by BOTH `rowHref`/`rowTo` and `rowActivatable`. Required with the former and strongly advised with the latter: without it the name is every cell concatenated, repeated for every row."
11748
+ },
11749
+ {
11750
+ name: "rowActivatable",
11751
+ type: "boolean",
11752
+ default: "false",
11753
+ description: 'The row ACTIVATES something that is not a route \u2014 a modal, a selection. Renders a real `<button>` in the first cell, stretched over the row the same way `rowHref` does, rather than `role="button"` on the `<tr>`, which would make every cell presentational and destroy the table\'s grid semantics. Mutually exclusive with `rowHref`/`rowTo` \u2014 dev builds warn.'
11754
+ },
11755
+ {
11756
+ name: "expandedRow",
11757
+ type: "string | number | null",
11758
+ description: "Key of the row whose detail panel is open, matched against the identity `rowKey` resolves."
11207
11759
  }
11208
11760
  ],
11209
11761
  events: [
@@ -11211,6 +11763,11 @@ var treeui_catalog_default = {
11211
11763
  name: "sort",
11212
11764
  description: "Emitted when a sortable column header is clicked.",
11213
11765
  payload: "TTableSortState"
11766
+ },
11767
+ {
11768
+ name: "row-activate",
11769
+ description: "Emitted when an activatable row is activated without navigating \u2014 see `rowActivatable`.",
11770
+ payload: "[row: Record<string, unknown>, index: number]"
11214
11771
  }
11215
11772
  ],
11216
11773
  slots: [
@@ -11225,6 +11782,10 @@ var treeui_catalog_default = {
11225
11782
  {
11226
11783
  name: "empty",
11227
11784
  description: "Content shown when rows array is empty."
11785
+ },
11786
+ {
11787
+ name: "detail",
11788
+ description: "Detail panel for the expanded row, rendered as a `<tr>` adjacent to its own row. Receives `{ row, index }`."
11228
11789
  }
11229
11790
  ],
11230
11791
  a11y: [
@@ -11235,7 +11796,11 @@ var treeui_catalog_default = {
11235
11796
  "keyboard: Sortable headers are focusable and activated with Enter or Space.",
11236
11797
  "scope: col on all th elements."
11237
11798
  ],
11238
- behavior: [],
11799
+ behavior: [
11800
+ 'roles_are_declared_rather_than_implied: `role` is on the table, both rowgroups, every row, every column header and every cell. Each is the element\'s own implicit role, so nothing changes while `display: table` holds \u2014 they exist to survive stacked mode, where `display: block` drops the implicit table semantics in several screen readers and the block would be announced as a run of unrelated text. Measured in the accessibility tree: a stacked cell reads as "Saldo real R$ 1.284,30", carrying its column.',
11801
+ "containment_is_scoped_to_tables_that_use_it: `container-type: inline-size` applies `contain: layout inline-size style`, so it is set only on a wrapper carrying a `stack-below` modifier rather than on every table in the library.",
11802
+ "a_row_link_covers_the_whole_block: Outside stacked mode every cell after the first is raised above the stretched-link overlay, because those are neighbouring columns and one may hold a button. Stacked, those same cells are plain text under the title, and raising them punched a hole in the link. Inside the container query only cells that actually contain something focusable are raised \u2014 and never the first, which holds the overlay: positioning it makes it the overlay's containing block, and `inset: 0` then covers that cell alone."
11803
+ ],
11239
11804
  selection: {
11240
11805
  keywords: [
11241
11806
  "table",
@@ -11270,7 +11835,9 @@ var treeui_catalog_default = {
11270
11835
  ],
11271
11836
  practices: [
11272
11837
  "interaction-feedback",
11273
- "state-clarity"
11838
+ "accessible-by-default",
11839
+ "state-clarity",
11840
+ "content-alignment"
11274
11841
  ]
11275
11842
  },
11276
11843
  {
@@ -11290,7 +11857,7 @@ var treeui_catalog_default = {
11290
11857
  name: "modelValue",
11291
11858
  type: "T",
11292
11859
  default: "undefined",
11293
- note: "The value of the currently active tab; use with v-model. Generic over `T extends string`, so a `ref<'overview' | 'logs'>` keeps its literal union instead of being widened to `string` \u2014 under a consumer's `strictTemplates` that widening made the binding an error with no correct way to write it (TREEUX-011, group 3).\n"
11860
+ note: "The value of the currently active tab; use with v-model. Generic over `T extends string`, so a `ref<'overview' | 'logs'>` keeps its literal union instead of being widened to `string` \u2014 widening makes the binding an error under `strictTemplates`, with no correct way to write it.\n"
11294
11861
  },
11295
11862
  {
11296
11863
  name: "defaultValue",
@@ -11340,6 +11907,7 @@ var treeui_catalog_default = {
11340
11907
  "aria_controls_aria_labelledby_pairing: true"
11341
11908
  ],
11342
11909
  behavior: [
11910
+ "overflowing_tabs_scroll_within_their_own_strip: `.t-tabs__list` is `overflow-x: auto` with `overscroll-behavior-x: contain`. Tabs that did not fit pushed the PAGE sideways, which moves every other element on screen to solve a problem belonging to one component. TTabList scrolls the active tab into view whenever the active value changes, so a tab reached by keyboard or restored from a model value is never left off-strip.",
11343
11911
  "arrow_left_right_moves_focus: true",
11344
11912
  "home_focuses_first_tab: true",
11345
11913
  "end_focuses_last_tab: true",
@@ -11379,7 +11947,9 @@ var treeui_catalog_default = {
11379
11947
  "router-backed-tabs"
11380
11948
  ],
11381
11949
  practices: [
11382
- "interaction-feedback"
11950
+ "interaction-feedback",
11951
+ "accessible-by-default",
11952
+ "content-alignment"
11383
11953
  ]
11384
11954
  },
11385
11955
  {
@@ -11717,6 +12287,17 @@ var treeui_catalog_default = {
11717
12287
  ],
11718
12288
  note: "Break axis for a string with no break opportunity \u2014 an id, ARN, API key or hash is one long word, so it overflows its box. `truncate` is the wrong answer when the string IS what the reader came to copy: an ellipsis makes an id useless. `anywhere` breaks at any character (and sets `min-inline-size: 0`, without which a flex or grid child still refuses to shrink below its longest word and never breaks); `break-word` only breaks a word that would otherwise overflow. Ignored when `truncate` is set.\n"
11719
12289
  },
12290
+ {
12291
+ name: "align",
12292
+ type: "TTextAlign",
12293
+ default: "undefined",
12294
+ values: [
12295
+ "start",
12296
+ "center",
12297
+ "end"
12298
+ ],
12299
+ note: "How the LINES sit inside the box, which is the other half of `TStack align` \u2014 that one centres the box, and a centred box only looks centred while it is narrow: at full width the paragraph inside reads left-aligned. Renders block, because `text-align` does nothing on an inline box that shrink-wraps its content. Logical values, so `start`/`end` follow the writing direction. Combined with `measure`, `center` also centres the capped block itself.\n"
12300
+ },
11720
12301
  {
11721
12302
  name: "balance",
11722
12303
  type: "boolean",
@@ -11757,6 +12338,7 @@ var treeui_catalog_default = {
11757
12338
  "Choose `as` to convey the correct semantics (e.g. headings via h1\u2013h6); TText adds only visual styling."
11758
12339
  ],
11759
12340
  behavior: [
12341
+ 'the_wrap_axis_outranks_preserve_whitespace: `.t-text.t-text--wrap-anywhere` is qualified with `.t-text` so it matches the (0,2,0) specificity of `.t-text.is-pre-wrap`, and wins by order. Without it `preserveWhitespace` silently downgraded `wrap="anywhere"` to `break-word`, which is the one thing `anywhere` exists to do differently: only `anywhere` reduces the min-content.',
11760
12342
  "semantic_element_controlled_by_as: true",
11761
12343
  "token_driven_size_tone_weight: true",
11762
12344
  "truncate_takes_precedence_over_preserve_whitespace: true"
@@ -11778,6 +12360,7 @@ var treeui_catalog_default = {
11778
12360
  practices: [
11779
12361
  "accessible-by-default",
11780
12362
  "predictable-api",
12363
+ "content-alignment",
11781
12364
  "token-driven"
11782
12365
  ]
11783
12366
  },
@@ -11794,6 +12377,12 @@ var treeui_catalog_default = {
11794
12377
  storyTitle: "Components/Data Entry/Textarea",
11795
12378
  manifestFile: "docs/ai/COMPONENTS/textarea.yaml",
11796
12379
  props: [
12380
+ {
12381
+ name: "spellcheck",
12382
+ type: "boolean",
12383
+ default: "undefined",
12384
+ description: 'A real `<textarea>` attribute that the declared surface was missing. It matters most with `family="mono"`: a JSON or config editor wants the spell-checker to stop underlining keys, ARNs and identifiers.'
12385
+ },
11797
12386
  {
11798
12387
  name: "modelValue",
11799
12388
  type: "string",
@@ -11867,7 +12456,7 @@ var treeui_catalog_default = {
11867
12456
  "sans",
11868
12457
  "mono"
11869
12458
  ],
11870
- note: "Font family of the editable content, same vocabulary as TText. `mono` is for machine text \u2014 JSON, a policy document, a payload \u2014 which otherwise leaves a `.mono` class behind in the consumer's stylesheet. Applied to the `<textarea>`, not the label wrapper, which also holds the loading spinner.\n"
12459
+ note: "Font family of the editable content, same vocabulary as TText. `mono` is for machine text \u2014 JSON, a policy document, a payload \u2014 which otherwise needs a one-off `.mono` font-family rule in product CSS. Applied to the `<textarea>`, not the label wrapper, which also holds the loading spinner.\n"
11871
12460
  }
11872
12461
  ],
11873
12462
  events: [
@@ -11967,6 +12556,7 @@ var treeui_catalog_default = {
11967
12556
  "timestamp_uses_time_element_when_provided: true"
11968
12557
  ],
11969
12558
  behavior: [
12559
+ "content_stays_inside_the_container: `.t-timeline` and `.t-timeline__content` declare `grid-template-columns: minmax(0, 1fr)`, and the title and description carry `overflow-wrap: anywhere`. Together they stop an unbreakable string from setting the column's min-content and carrying the whole entry across the container.",
11970
12560
  "items_render_in_order_given: true",
11971
12561
  "line_hidden_on_last_item: true",
11972
12562
  "marker_tone_defaults_to_neutral: true"
@@ -11982,7 +12572,9 @@ var treeui_catalog_default = {
11982
12572
  notes: []
11983
12573
  },
11984
12574
  recipes: [],
11985
- practices: []
12575
+ practices: [
12576
+ "content-alignment"
12577
+ ]
11986
12578
  },
11987
12579
  {
11988
12580
  name: "TToast",
@@ -11997,6 +12589,12 @@ var treeui_catalog_default = {
11997
12589
  storyTitle: "Components/Feedback/Toast",
11998
12590
  manifestFile: "docs/ai/COMPONENTS/toast.yaml",
11999
12591
  props: [
12592
+ {
12593
+ name: "closeLabel",
12594
+ type: "string",
12595
+ default: "Dismiss notification",
12596
+ description: "Accessible name for the dismiss button, on `TToastProvider` as the instance default and on a single toast's options to override it. It was hard-coded English, which left the toast's only control unreadable to a screen reader in any other language."
12597
+ },
12000
12598
  {
12001
12599
  name: "toast",
12002
12600
  type: "ToastItem",
@@ -12056,6 +12654,7 @@ var treeui_catalog_default = {
12056
12654
  "confirm-destructive-action"
12057
12655
  ],
12058
12656
  practices: [
12657
+ "accessible-by-default",
12059
12658
  "token-driven"
12060
12659
  ]
12061
12660
  },
@@ -12076,7 +12675,7 @@ var treeui_catalog_default = {
12076
12675
  name: "modelValue",
12077
12676
  type: "TToggleGroupModel<T, M> \u2014 `T` in single mode, `T[]` in multiple",
12078
12677
  default: "undefined",
12079
- note: "Generic over the option type AND the selection mode, so the model is the type the call site actually has: `<TToggleGroup v-model=\"view\">` with a `ref<'grid' | 'list'>` binds, and `selectionMode=\"multiple\"` binds a `('grid' | 'list')[]`. It was `string | string[]` in and `string | string[] | undefined` out, which no literal-union ref could bind under a consumer's `strictTemplates` \u2014 the same defect as TTabs/TInput (TREEUX-011, group 3), found by the gate once it ran on the same `vue-tsc` major consumers use. `undefined` was never actually emitted.\n"
12678
+ note: "Generic over the option type AND the selection mode, so the model is the type the binding actually has: `<TToggleGroup v-model=\"view\">` with a `ref<'grid' | 'list'>` binds, and `selectionMode=\"multiple\"` binds a `('grid' | 'list')[]`. It was `string | string[]` in and `string | string[] | undefined` out, which no literal-union ref could bind under `strictTemplates` \u2014 the same defect as TTabs/TInput, and invisible to any build that does not typecheck templates against the published types. `undefined` was never actually emitted.\n"
12080
12679
  },
12081
12680
  {
12082
12681
  name: "options",
@@ -12137,6 +12736,7 @@ var treeui_catalog_default = {
12137
12736
  "activation_keys_select_option: true"
12138
12737
  ],
12139
12738
  behavior: [
12739
+ 'selected_item_repaints_its_descendants_secondary_text: `.t-toggle-group__item.is-selected` rebinds `--tree-color-text-muted` to `--tree-color-brand-contrast`. Only the inherited colour used to change, so a `TText tone="muted"` in the `#option` slot kept reading the muted token and measured 1.18:1 (light) and 1.09:1 (dark) over the brand fill, where `xs` text needs 4.5:1. Rebinding the token is what keeps a slot the library does not control legible; `brand-contrast` is the only value that clears AA in both themes (5.19:1 / 6.21:1) \u2014 a `color-mix` at 85% still measured 4.18:1.',
12140
12740
  "single_and_multiple_modes: true",
12141
12741
  "variant_changes_visual_style_only: true",
12142
12742
  "visible_option_set_expected: true"
@@ -12177,7 +12777,9 @@ var treeui_catalog_default = {
12177
12777
  ]
12178
12778
  },
12179
12779
  recipes: [],
12180
- practices: []
12780
+ practices: [
12781
+ "accessible-by-default"
12782
+ ]
12181
12783
  },
12182
12784
  {
12183
12785
  name: "TTooltip",
@@ -13326,25 +13928,25 @@ var treeui_catalog_default = {
13326
13928
  {
13327
13929
  cssVar: "--tree-color-status-success",
13328
13930
  path: "color.status.success",
13329
- value: "#1a7f37",
13931
+ value: "#197d36",
13330
13932
  category: "color",
13331
13933
  derived: false,
13332
13934
  description: "Themed semantic color, resolved per [data-tree-theme].",
13333
13935
  themeValues: {
13334
- light: "#1a7f37",
13335
- dark: "#59ac5c"
13936
+ light: "#197d36",
13937
+ dark: "#5bae5e"
13336
13938
  }
13337
13939
  },
13338
13940
  {
13339
13941
  cssVar: "--tree-color-status-warning",
13340
13942
  path: "color.status.warning",
13341
- value: "#956400",
13943
+ value: "#946300",
13342
13944
  category: "color",
13343
13945
  derived: false,
13344
13946
  description: "Themed semantic color, resolved per [data-tree-theme].",
13345
13947
  themeValues: {
13346
- light: "#956400",
13347
- dark: "#c69026"
13948
+ light: "#946300",
13949
+ dark: "#c99227"
13348
13950
  }
13349
13951
  },
13350
13952
  {
@@ -13650,25 +14252,25 @@ var treeui_catalog_default = {
13650
14252
  {
13651
14253
  cssVar: "--tree-color-status-success-hover",
13652
14254
  path: "color.status.success-hover",
13653
- value: "#15682d",
14255
+ value: "#15672c",
13654
14256
  category: "color",
13655
14257
  derived: true,
13656
14258
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13657
14259
  themeValues: {
13658
- light: "#15682d",
13659
- dark: "#77bb79"
14260
+ light: "#15672c",
14261
+ dark: "#79bd7b"
13660
14262
  }
13661
14263
  },
13662
14264
  {
13663
14265
  cssVar: "--tree-color-status-success-press",
13664
14266
  path: "color.status.success-press",
13665
- value: "#125625",
14267
+ value: "#115525",
13666
14268
  category: "color",
13667
14269
  derived: true,
13668
14270
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13669
14271
  themeValues: {
13670
- light: "#125625",
13671
- dark: "#8ec790"
14272
+ light: "#115525",
14273
+ dark: "#8fc892"
13672
14274
  }
13673
14275
  },
13674
14276
  {
@@ -13680,31 +14282,31 @@ var treeui_catalog_default = {
13680
14282
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13681
14283
  themeValues: {
13682
14284
  light: "#dfede3",
13683
- dark: "#2a3a34"
14285
+ dark: "#2a3a35"
13684
14286
  }
13685
14287
  },
13686
14288
  {
13687
14289
  cssVar: "--tree-color-status-success-soft-hover",
13688
14290
  path: "color.status.success-soft-hover",
13689
- value: "#c3decb",
14291
+ value: "#c3ddcb",
13690
14292
  category: "color",
13691
14293
  derived: true,
13692
14294
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13693
14295
  themeValues: {
13694
- light: "#c3decb",
13695
- dark: "#314a3a"
14296
+ light: "#c3ddcb",
14297
+ dark: "#314a3b"
13696
14298
  }
13697
14299
  },
13698
14300
  {
13699
14301
  cssVar: "--tree-color-status-success-border",
13700
14302
  path: "color.status.success-border",
13701
- value: "#419559",
14303
+ value: "#419359",
13702
14304
  category: "color",
13703
14305
  derived: true,
13704
14306
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13705
14307
  themeValues: {
13706
- light: "#419559",
13707
- dark: "#4d9052"
14308
+ light: "#419359",
14309
+ dark: "#4f9154"
13708
14310
  }
13709
14311
  },
13710
14312
  {
@@ -13722,49 +14324,49 @@ var treeui_catalog_default = {
13722
14324
  {
13723
14325
  cssVar: "--tree-color-status-success-on-soft",
13724
14326
  path: "color.status.success-on-soft",
13725
- value: "#197934",
14327
+ value: "#187733",
13726
14328
  category: "color",
13727
14329
  derived: true,
13728
14330
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13729
14331
  themeValues: {
13730
- light: "#197934",
13731
- dark: "#61b064"
14332
+ light: "#187733",
14333
+ dark: "#63b266"
13732
14334
  }
13733
14335
  },
13734
14336
  {
13735
14337
  cssVar: "--tree-color-status-success-on-soft-hover",
13736
14338
  path: "color.status.success-on-soft-hover",
13737
- value: "#15672d",
14339
+ value: "#156b2e",
13738
14340
  category: "color",
13739
14341
  derived: true,
13740
14342
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13741
14343
  themeValues: {
13742
- light: "#15672d",
13743
- dark: "#85c287"
14344
+ light: "#156b2e",
14345
+ dark: "#80c082"
13744
14346
  }
13745
14347
  },
13746
14348
  {
13747
14349
  cssVar: "--tree-color-status-warning-hover",
13748
14350
  path: "color.status.warning-hover",
13749
- value: "#7a5200",
14351
+ value: "#795100",
13750
14352
  category: "color",
13751
14353
  derived: true,
13752
14354
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13753
14355
  themeValues: {
13754
- light: "#7a5200",
13755
- dark: "#d0a44d"
14356
+ light: "#795100",
14357
+ dark: "#d3a64e"
13756
14358
  }
13757
14359
  },
13758
14360
  {
13759
14361
  cssVar: "--tree-color-status-warning-press",
13760
14362
  path: "color.status.warning-press",
13761
- value: "#654400",
14363
+ value: "#654300",
13762
14364
  category: "color",
13763
14365
  derived: true,
13764
14366
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13765
14367
  themeValues: {
13766
- light: "#654400",
13767
- dark: "#d8b46b"
14368
+ light: "#654300",
14369
+ dark: "#dab56c"
13768
14370
  }
13769
14371
  },
13770
14372
  {
@@ -13794,13 +14396,13 @@ var treeui_catalog_default = {
13794
14396
  {
13795
14397
  cssVar: "--tree-color-status-warning-border",
13796
14398
  path: "color.status.warning-border",
13797
- value: "#a77f2c",
14399
+ value: "#a67e2c",
13798
14400
  category: "color",
13799
14401
  derived: true,
13800
14402
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13801
14403
  themeValues: {
13802
- light: "#a77f2c",
13803
- dark: "#a37a28"
14404
+ light: "#a67e2c",
14405
+ dark: "#a57b29"
13804
14406
  }
13805
14407
  },
13806
14408
  {
@@ -13818,25 +14420,25 @@ var treeui_catalog_default = {
13818
14420
  {
13819
14421
  cssVar: "--tree-color-status-warning-on-soft",
13820
14422
  path: "color.status.warning-on-soft",
13821
- value: "#8e5f00",
14423
+ value: "#8d5e00",
13822
14424
  category: "color",
13823
14425
  derived: true,
13824
14426
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13825
14427
  themeValues: {
13826
- light: "#8e5f00",
13827
- dark: "#c99631"
14428
+ light: "#8d5e00",
14429
+ dark: "#cc9732"
13828
14430
  }
13829
14431
  },
13830
14432
  {
13831
14433
  cssVar: "--tree-color-status-warning-on-soft-hover",
13832
14434
  path: "color.status.warning-on-soft-hover",
13833
- value: "#805600",
14435
+ value: "#7f5500",
13834
14436
  category: "color",
13835
14437
  derived: true,
13836
14438
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13837
14439
  themeValues: {
13838
- light: "#805600",
13839
- dark: "#d5ad5f"
14440
+ light: "#7f5500",
14441
+ dark: "#d5ab58"
13840
14442
  }
13841
14443
  },
13842
14444
  {
@@ -14547,16 +15149,23 @@ var treePrimitives = {
14547
15149
  600: "#633fa6",
14548
15150
  800: "#332e4a"
14549
15151
  },
15152
+ /**
15153
+ * The status hues carry the narrowest margin in the palette, because the band
15154
+ * they have to clear is `bg.subtle` — the secondary surface a status line
15155
+ * usually lands on (inside a card, under a field), not the primary one they
15156
+ * are easy to calibrate against. Each of these three was a step away from the
15157
+ * floor and is now a step above it; `status.test.ts` holds the headroom, and
15158
+ * the contract validator holds the floor.
15159
+ */
14550
15160
  green: {
14551
- 400: "#59ac5c",
14552
- 500: "#1a7f37"
15161
+ 400: "#5bae5e",
15162
+ 500: "#197d36"
14553
15163
  },
14554
15164
  amber: {
14555
- 400: "#c69026",
14556
- 500: "#956400"
15165
+ 400: "#c99227",
15166
+ 500: "#946300"
14557
15167
  },
14558
15168
  red: {
14559
- /** Landed on 4.50:1 against the dark subtle band; nudged for headroom. */
14560
15169
  400: "#f5766e",
14561
15170
  500: "#d1242f"
14562
15171
  },