@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.
@@ -1,8 +1,8 @@
1
1
  // src/generated/treeui.catalog.json
2
2
  var treeui_catalog_default = {
3
- generatedAt: "0.30.0",
3
+ generatedAt: "0.34.1",
4
4
  packageName: "@treeui/vue",
5
- packageVersion: "0.30.0",
5
+ packageVersion: "0.34.1",
6
6
  decisionGuides: [
7
7
  {
8
8
  id: "single-choice",
@@ -125,7 +125,7 @@ var treeui_catalog_default = {
125
125
  ],
126
126
  notes: [
127
127
  "Editing, not display. For read-only key/value display use TDescriptionList.",
128
- "A write-only/sensitive mode (value never read back) is a closed contract on the queue, not yet shipped."
128
+ "A write-only/sensitive mode (value never read back) is a designed contract that is not yet shipped."
129
129
  ]
130
130
  },
131
131
  {
@@ -423,8 +423,8 @@ var treeui_catalog_default = {
423
423
  "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.",
424
424
  '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.',
425
425
  '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.',
426
- "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.",
427
- "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."
426
+ "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.",
427
+ "Company and service logos are brand assets, not TreeUI icons \u2014 source them from your own brand assets rather than the icon registry."
428
428
  ]
429
429
  },
430
430
  {
@@ -462,7 +462,7 @@ var treeui_catalog_default = {
462
462
  "`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.",
463
463
  "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.",
464
464
  "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.",
465
- "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.",
465
+ "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.",
466
466
  "Keep the icon vocabulary consistent: built-ins use TreeUI's descriptive kebab-case names, so register custom ones with the same convention.",
467
467
  "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.",
468
468
  "Set `to` for the route target on each item \u2014 `TNavMenuItem` has no `href` and no `id`; identity is `value`.",
@@ -770,28 +770,41 @@ var treeui_catalog_default = {
770
770
  "Tab/Shift+Tab order, Escape to close, Enter/Space to activate, arrow keys where the pattern expects them.",
771
771
  "Overlays trap focus while open and restore it on close.",
772
772
  "Clickable targets stay at or above 44\xD744px.",
773
- "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."
773
+ "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.",
774
+ "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.",
775
+ "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."
774
776
  ],
775
777
  components: [
776
- "TModal",
777
- "TDrawer",
778
- "TConfirmDialog",
779
- "TDropdown",
780
- "TTooltip",
781
778
  "TAccordion",
782
- "TFormField",
783
- "TTreeView",
784
- "TToastProvider",
779
+ "TAppShell",
785
780
  "TButton",
781
+ "TCanvasSurface",
782
+ "TCheckbox",
786
783
  "TCodeBlock",
784
+ "TConfirmDialog",
785
+ "TDatePicker",
786
+ "TDrawer",
787
+ "TDropdown",
788
+ "TFileUpload",
789
+ "TFormField",
790
+ "TIconTile",
791
+ "TImage",
787
792
  "TLinkTile",
788
793
  "TMenu",
794
+ "TModal",
789
795
  "TPopover",
790
- "TImage",
796
+ "TSelect",
791
797
  "TSkipLink",
792
- "TAppShell",
793
- "TCheckbox",
794
- "TText"
798
+ "TStatGroup",
799
+ "TSteps",
800
+ "TTable",
801
+ "TTabs",
802
+ "TText",
803
+ "TToast",
804
+ "TToastProvider",
805
+ "TToggleGroup",
806
+ "TTooltip",
807
+ "TTreeView"
795
808
  ]
796
809
  },
797
810
  {
@@ -851,26 +864,45 @@ var treeui_catalog_default = {
851
864
  "Spacing comes from the --tree-space-* scale, applied by layout components.",
852
865
  "Compose TPage, TStack, TGrid, and TSplit before writing a custom layout div.",
853
866
  "Forms align through TFormField + TStack + TGrid \u2014 labels, hints, and errors line up by contract.",
854
- "Reading widths and page gutters come from TContainer/TPage, not local max-widths."
867
+ "Reading widths and page gutters come from TContainer/TPage, not local max-widths.",
868
+ "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."
855
869
  ],
856
870
  components: [
857
- "TStack",
858
- "TGrid",
859
- "TSplit",
860
- "TContainer",
861
- "TSpacer",
862
- "TPage",
863
- "TPageHeader",
864
- "TFormField",
871
+ "TAppShell",
872
+ "TBadge",
865
873
  "TBrandLockup",
866
- "TList",
874
+ "TCanvasSurface",
875
+ "TCard",
876
+ "TCodeBlock",
877
+ "TConfirmDialog",
878
+ "TContainer",
867
879
  "TDescriptionList",
868
- "TSection",
880
+ "TEmptyState",
881
+ "TFormField",
882
+ "TGrid",
869
883
  "THero",
884
+ "THide",
885
+ "TIconTile",
886
+ "TLinkTile",
887
+ "TList",
888
+ "TModal",
889
+ "TNavMenu",
890
+ "TPage",
891
+ "TPageHeader",
870
892
  "TPageSurface",
893
+ "TSection",
871
894
  "TShow",
872
- "THide",
873
- "TEmptyState"
895
+ "TSpacer",
896
+ "TSparkline",
897
+ "TSplit",
898
+ "TStack",
899
+ "TStat",
900
+ "TStatGroup",
901
+ "TSteps",
902
+ "TTable",
903
+ "TTabs",
904
+ "TText",
905
+ "TTimeline"
874
906
  ]
875
907
  },
876
908
  {
@@ -897,6 +929,7 @@ var treeui_catalog_default = {
897
929
  "TColorSwatch",
898
930
  "TDescriptionList",
899
931
  "THero",
932
+ "TIconTile",
900
933
  "TImage",
901
934
  "TInput",
902
935
  "TKeyValueEditor",
@@ -910,6 +943,7 @@ var treeui_catalog_default = {
910
943
  "TSection",
911
944
  "TSelect",
912
945
  "TStat",
946
+ "TStatGroup",
913
947
  "TSwitch",
914
948
  "TTag",
915
949
  "TTagInput",
@@ -917,6 +951,24 @@ var treeui_catalog_default = {
917
951
  "TTextarea",
918
952
  "TToast"
919
953
  ]
954
+ },
955
+ {
956
+ id: "branchline-icons",
957
+ title: "Icons drawn to a contract",
958
+ 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.",
959
+ rules: [
960
+ "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.",
961
+ "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.",
962
+ "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.",
963
+ "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.",
964
+ "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.",
965
+ "A family shares one base, drawn once and reused literally; a variant changes exactly one thing, in one place, by one rule.",
966
+ "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.",
967
+ "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."
968
+ ],
969
+ components: [
970
+ "TIcon"
971
+ ]
920
972
  }
921
973
  ],
922
974
  setup: {
@@ -931,7 +983,7 @@ var treeui_catalog_default = {
931
983
  },
932
984
  vueSetup: {
933
985
  pluginInstall: {
934
- import: "import TreeUIPlugin from '@treeui/vue'",
986
+ import: "import { TreeUIPlugin } from '@treeui/vue'",
935
987
  usage: "app.use(TreeUIPlugin)"
936
988
  },
937
989
  individualImports: {
@@ -1554,7 +1606,7 @@ var treeui_catalog_default = {
1554
1606
  name: "showCollapseButton",
1555
1607
  type: "boolean",
1556
1608
  default: "true",
1557
- 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."
1609
+ 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."
1558
1610
  },
1559
1611
  {
1560
1612
  name: "collapseLabel",
@@ -1638,7 +1690,7 @@ var treeui_catalog_default = {
1638
1690
  },
1639
1691
  {
1640
1692
  name: "header-end",
1641
- 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",
1693
+ 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",
1642
1694
  note: "AppShellSlotProps"
1643
1695
  },
1644
1696
  {
@@ -1692,6 +1744,7 @@ var treeui_catalog_default = {
1692
1744
  "menu_and_drawer_close_buttons_are_44px: true"
1693
1745
  ],
1694
1746
  behavior: [
1747
+ "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.",
1695
1748
  "fills_viewport_full_width_and_height: true",
1696
1749
  "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",
1697
1750
  "header_spans_full_width_above_sidebar_and_content: true",
@@ -1779,7 +1832,8 @@ var treeui_catalog_default = {
1779
1832
  "brand-lockup"
1780
1833
  ],
1781
1834
  practices: [
1782
- "accessible-by-default"
1835
+ "accessible-by-default",
1836
+ "content-alignment"
1783
1837
  ]
1784
1838
  },
1785
1839
  {
@@ -1990,6 +2044,18 @@ var treeui_catalog_default = {
1990
2044
  "lg"
1991
2045
  ],
1992
2046
  description: "Changes min-height and font-size only; inline padding is constant across sizes."
2047
+ },
2048
+ {
2049
+ name: "truncate",
2050
+ type: "boolean",
2051
+ default: "false",
2052
+ 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."
2053
+ },
2054
+ {
2055
+ name: "label",
2056
+ type: "string",
2057
+ default: "undefined",
2058
+ description: "The badge text. Only needed with `truncate`, which puts it in `title`; otherwise use the default slot."
1993
2059
  }
1994
2060
  ],
1995
2061
  events: [],
@@ -2012,8 +2078,10 @@ var treeui_catalog_default = {
2012
2078
  "tone_remaps_variant_color_variables: true",
2013
2079
  "tone_overrides_variant_danger_palette: true",
2014
2080
  "tone_neutral_has_no_stylesheet_rule: true",
2081
+ '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.',
2015
2082
  "icon_wrapper_rendered_only_when_slot_filled: true",
2016
- "icon_renders_before_default_slot: true"
2083
+ "icon_renders_before_default_slot: true",
2084
+ "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."
2017
2085
  ],
2018
2086
  selection: {
2019
2087
  keywords: [],
@@ -2031,6 +2099,7 @@ var treeui_catalog_default = {
2031
2099
  practices: [
2032
2100
  "predictable-api",
2033
2101
  "state-clarity",
2102
+ "content-alignment",
2034
2103
  "token-driven"
2035
2104
  ]
2036
2105
  },
@@ -2221,7 +2290,7 @@ var treeui_catalog_default = {
2221
2290
  name: "href",
2222
2291
  type: "string",
2223
2292
  default: "undefined",
2224
- 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'
2293
+ 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'
2225
2294
  },
2226
2295
  {
2227
2296
  name: "target",
@@ -2636,6 +2705,116 @@ var treeui_catalog_default = {
2636
2705
  recipes: [],
2637
2706
  practices: []
2638
2707
  },
2708
+ {
2709
+ name: "TCanvasSurface",
2710
+ aliases: [],
2711
+ package: "@treeui/vue",
2712
+ kind: "data-display",
2713
+ category: "Data Display",
2714
+ status: "available",
2715
+ purpose: "A sized, repaintable, hit-testable canvas for a data-driven drawing \u2014 a diagram, a graph, a custom plot.",
2716
+ sourceFile: "packages/vue/src/components/TCanvasSurface.vue",
2717
+ storyFile: "apps/docs/src/stories/CanvasSurface.stories.ts",
2718
+ storyTitle: "Components/Data Display/Canvas Surface",
2719
+ manifestFile: "docs/ai/COMPONENTS/canvas-surface.yaml",
2720
+ props: [
2721
+ {
2722
+ name: "draw",
2723
+ type: "(frame: CanvasSurfaceFrame) => void",
2724
+ required: true,
2725
+ 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"
2726
+ },
2727
+ {
2728
+ name: "height",
2729
+ type: "number | string",
2730
+ default: "320",
2731
+ note: "Number is px, string is any CSS length. A diagram has no intrinsic height, so it is declared."
2732
+ },
2733
+ {
2734
+ name: "cursor",
2735
+ type: "string",
2736
+ default: "undefined",
2737
+ 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"
2738
+ },
2739
+ {
2740
+ name: "maxPixelRatio",
2741
+ type: "number",
2742
+ default: "2"
2743
+ },
2744
+ {
2745
+ name: "ariaLabel",
2746
+ type: "string",
2747
+ default: "undefined",
2748
+ note: "Sets role=img. A bitmap says nothing on its own."
2749
+ },
2750
+ {
2751
+ name: "redrawKey",
2752
+ type: "unknown",
2753
+ default: "undefined",
2754
+ note: "Repaint whenever this changes \u2014 pass the data, a version counter, or the theme. Deep-watched."
2755
+ }
2756
+ ],
2757
+ events: [],
2758
+ slots: [
2759
+ {
2760
+ name: "default",
2761
+ 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"
2762
+ }
2763
+ ],
2764
+ a11y: [
2765
+ "Give it an ariaLabel; a canvas is opaque to assistive technology.",
2766
+ "Put the same information in the default slot as markup \u2014 it is the accessible form, not a fallback.",
2767
+ "Anything clickable inside the drawing needs a real control elsewhere, or a keyboard user cannot reach it."
2768
+ ],
2769
+ behavior: [
2770
+ "repaints_on_demand_not_on_a_clock: true",
2771
+ "backing_store_follows_the_element: true",
2772
+ "pointer_events_reach_the_canvas: true",
2773
+ "ssr_safe: Nothing touches the DOM outside onMounted."
2774
+ ],
2775
+ selection: {
2776
+ keywords: [
2777
+ "canvas",
2778
+ "diagram",
2779
+ "graph",
2780
+ "node and edge",
2781
+ "custom drawing",
2782
+ "plot",
2783
+ "topology",
2784
+ "map",
2785
+ "hit testing"
2786
+ ],
2787
+ useWhen: [
2788
+ "The drawing is data-driven and no chart primitive expresses it \u2014 nodes and edges, a topology, a custom plot.",
2789
+ "Parts of the drawing are click or hover targets.",
2790
+ "It must repaint when the data, the locale or the theme changes."
2791
+ ],
2792
+ avoidWhen: [
2793
+ "A line, area or bar chart says it \u2014 use TChart, which brings axes, legend, tooltip and an accessible table.",
2794
+ "The drawing is ornament \u2014 use useDecorativeCanvas, which gates the loop on visibility and reduced motion.",
2795
+ "The shape is an inline trend \u2014 use TSparkline."
2796
+ ],
2797
+ alternatives: [
2798
+ {
2799
+ component: "TChart",
2800
+ when: "The data fits axes and categories."
2801
+ },
2802
+ {
2803
+ component: "useDecorativeCanvas",
2804
+ when: "It is decoration, not information."
2805
+ }
2806
+ ]
2807
+ },
2808
+ setup: {
2809
+ required: [],
2810
+ notes: []
2811
+ },
2812
+ recipes: [],
2813
+ practices: [
2814
+ "accessible-by-default",
2815
+ "content-alignment"
2816
+ ]
2817
+ },
2639
2818
  {
2640
2819
  name: "TCard",
2641
2820
  aliases: [],
@@ -2709,6 +2888,9 @@ var treeui_catalog_default = {
2709
2888
  "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."
2710
2889
  ],
2711
2890
  behavior: [
2891
+ "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.",
2892
+ "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`.",
2893
+ "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.",
2712
2894
  "body_wrapper_class: t-card__body",
2713
2895
  "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.",
2714
2896
  "footer_render_condition: The `footer.t-card__footer` element renders only when the `footer` slot is supplied.",
@@ -2751,6 +2933,7 @@ var treeui_catalog_default = {
2751
2933
  practices: [
2752
2934
  "interaction-feedback",
2753
2935
  "predictable-api",
2936
+ "content-alignment",
2754
2937
  "token-driven"
2755
2938
  ]
2756
2939
  },
@@ -2767,6 +2950,18 @@ var treeui_catalog_default = {
2767
2950
  storyTitle: "Components/Data Display/Chart",
2768
2951
  manifestFile: "docs/ai/COMPONENTS/chart.yaml",
2769
2952
  props: [
2953
+ {
2954
+ name: "axisValueFormat",
2955
+ type: "(value: number) => string",
2956
+ default: "undefined",
2957
+ 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."
2958
+ },
2959
+ {
2960
+ name: "yAxisWidth",
2961
+ type: "number",
2962
+ default: "undefined",
2963
+ 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."
2964
+ },
2770
2965
  {
2771
2966
  name: "type",
2772
2967
  type: "TChartType",
@@ -2911,6 +3106,7 @@ var treeui_catalog_default = {
2911
3106
  "point-click has no keyboard equivalent \u2014 do not make it the only way to reach a behavior."
2912
3107
  ],
2913
3108
  behavior: [
3109
+ "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.",
2914
3110
  "colors_auto_assign_from_chart_palette: --tree-color-chart-1..8, cycling by series index; per-series color overrides",
2915
3111
  "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.",
2916
3112
  "y_domain_uses_nice_ticks: every type runs through niceScale, which rounds the min down and the max up to round numbers",
@@ -2937,7 +3133,7 @@ var treeui_catalog_default = {
2937
3133
  ],
2938
3134
  avoidWhen: [
2939
3135
  "A single value tells the story \u2014 use TStat.",
2940
- "You only need an inline trend with no axes \u2014 use TSparkline.",
3136
+ "You only need an inline trend with no axes \u2014 use TSparkline (`fluid` fills the card width).",
2941
3137
  "The data is a part-of-whole breakdown \u2014 use TDonutChart."
2942
3138
  ],
2943
3139
  alternatives: [
@@ -3109,6 +3305,17 @@ var treeui_catalog_default = {
3109
3305
  storyTitle: "Components/Data Display/CodeBlock",
3110
3306
  manifestFile: "docs/ai/COMPONENTS/code-block.yaml",
3111
3307
  props: [
3308
+ {
3309
+ name: "size",
3310
+ type: "TSize",
3311
+ default: "md",
3312
+ values: [
3313
+ "sm",
3314
+ "md",
3315
+ "lg"
3316
+ ],
3317
+ 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."
3318
+ },
3112
3319
  {
3113
3320
  name: "code",
3114
3321
  type: "string",
@@ -3157,7 +3364,10 @@ var treeui_catalog_default = {
3157
3364
  '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.',
3158
3365
  "copy_button_state: The copy button's aria-label reflects the copied state briefly."
3159
3366
  ],
3160
- behavior: [],
3367
+ behavior: [
3368
+ "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.",
3369
+ '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"`.'
3370
+ ],
3161
3371
  selection: {
3162
3372
  keywords: [
3163
3373
  "code",
@@ -3195,6 +3405,7 @@ var treeui_catalog_default = {
3195
3405
  recipes: [],
3196
3406
  practices: [
3197
3407
  "accessible-by-default",
3408
+ "content-alignment",
3198
3409
  "token-driven"
3199
3410
  ]
3200
3411
  },
@@ -3716,6 +3927,7 @@ var treeui_catalog_default = {
3716
3927
  "icon_slot_marked_aria_hidden: true"
3717
3928
  ],
3718
3929
  behavior: [
3930
+ "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.",
3719
3931
  "built_on_modal_contract: true",
3720
3932
  "default_confirm_button_variant: danger",
3721
3933
  "default_cancel_button_variant: ghost",
@@ -3761,7 +3973,8 @@ var treeui_catalog_default = {
3761
3973
  "confirm-destructive-action"
3762
3974
  ],
3763
3975
  practices: [
3764
- "accessible-by-default"
3976
+ "accessible-by-default",
3977
+ "content-alignment"
3765
3978
  ]
3766
3979
  },
3767
3980
  {
@@ -4141,6 +4354,8 @@ var treeui_catalog_default = {
4141
4354
  "focus_is_not_trapped_and_tab_leaves_the_open_calendar: true"
4142
4355
  ],
4143
4356
  behavior: [
4357
+ '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.',
4358
+ '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.',
4144
4359
  "value_format: YYYY-MM-DD",
4145
4360
  "controlled_and_uncontrolled_open: true",
4146
4361
  "open_state_emits_are_deduped: true",
@@ -4189,7 +4404,9 @@ var treeui_catalog_default = {
4189
4404
  ]
4190
4405
  },
4191
4406
  recipes: [],
4192
- practices: []
4407
+ practices: [
4408
+ "accessible-by-default"
4409
+ ]
4193
4410
  },
4194
4411
  {
4195
4412
  name: "TDateTimePicker",
@@ -4630,6 +4847,7 @@ var treeui_catalog_default = {
4630
4847
  "Provide a meaningful ariaLabel describing what the chart shows."
4631
4848
  ],
4632
4849
  behavior: [
4850
+ "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.",
4633
4851
  "colors_auto_assign_from_chart_palette: --tree-color-chart-1..8, cycling by visible-segment index; per-segment color overrides",
4634
4852
  "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",
4635
4853
  "hovering_a_legend_row_drives_the_same_highlight: legend items carry the same pointerenter/pointerleave handlers as the segments",
@@ -5203,6 +5421,48 @@ var treeui_catalog_default = {
5203
5421
  storyTitle: "Components/Data Entry/FileUpload",
5204
5422
  manifestFile: "docs/ai/COMPONENTS/file-upload.yaml",
5205
5423
  props: [
5424
+ {
5425
+ name: "uploadingLabel",
5426
+ type: "string",
5427
+ default: "Uploading files",
5428
+ description: "Accessible name for the spinner shown while `loading`."
5429
+ },
5430
+ {
5431
+ name: "uploadedAnnouncement",
5432
+ type: "(name: string) => string",
5433
+ default: "(n) => `${n} uploaded.`",
5434
+ 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."
5435
+ },
5436
+ {
5437
+ name: "failedAnnouncement",
5438
+ type: "(name: string) => string",
5439
+ default: "(n) => `${n} failed.`",
5440
+ description: "The same, for a failure. The error text is appended after it."
5441
+ },
5442
+ {
5443
+ name: "showRejections",
5444
+ type: "boolean",
5445
+ default: "true",
5446
+ 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."
5447
+ },
5448
+ {
5449
+ name: "clearLabel",
5450
+ type: "string",
5451
+ default: "Clear all",
5452
+ description: "Label for the button that drops every selected file. It was hard-coded English with no way out."
5453
+ },
5454
+ {
5455
+ name: "removeLabel",
5456
+ type: "string",
5457
+ default: "Remove",
5458
+ description: "Label for a row's remove button."
5459
+ },
5460
+ {
5461
+ name: "removeAriaLabel",
5462
+ type: "(file: File) => string",
5463
+ default: "(f) => `Remove ${f.name}`",
5464
+ 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."
5465
+ },
5206
5466
  {
5207
5467
  name: "modelValue",
5208
5468
  type: "File[]",
@@ -5411,6 +5671,7 @@ var treeui_catalog_default = {
5411
5671
  "remove_and_retry_meet_the_44px_target: true"
5412
5672
  ],
5413
5673
  behavior: [
5674
+ "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.",
5414
5675
  "accepts_files_from_input_drop_and_paste: true",
5415
5676
  "multiple_false_replaces_existing_selection: true",
5416
5677
  "accept_maxFiles_and_maxFileSize_apply_to_all_entry_points: true",
@@ -5462,7 +5723,9 @@ var treeui_catalog_default = {
5462
5723
  recipes: [
5463
5724
  "page-drop-target"
5464
5725
  ],
5465
- practices: []
5726
+ practices: [
5727
+ "accessible-by-default"
5728
+ ]
5466
5729
  },
5467
5730
  {
5468
5731
  name: "TFlag",
@@ -5775,6 +6038,12 @@ var treeui_catalog_default = {
5775
6038
  name: "dense",
5776
6039
  type: "boolean",
5777
6040
  default: "false"
6041
+ },
6042
+ {
6043
+ name: "balance",
6044
+ type: "boolean",
6045
+ default: "false",
6046
+ 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'
5778
6047
  }
5779
6048
  ],
5780
6049
  events: [],
@@ -5788,6 +6057,7 @@ var treeui_catalog_default = {
5788
6057
  ],
5789
6058
  behavior: [
5790
6059
  "semantic_element_controlled_by_as: true",
6060
+ "balance_shares_the_last_row_instead_of_leaving_holes: true",
5791
6061
  "fixed_columns_supported: true",
5792
6062
  "auto_fit_columns_supported: true",
5793
6063
  "dense_mode_uses_grid_auto_flow_dense: true"
@@ -6080,6 +6350,7 @@ var treeui_catalog_default = {
6080
6350
  "calendar-plus",
6081
6351
  "calendar-range",
6082
6352
  "calendar-x",
6353
+ "campaign",
6083
6354
  "carousel",
6084
6355
  "catalog",
6085
6356
  "chart-column",
@@ -6116,7 +6387,6 @@ var treeui_catalog_default = {
6116
6387
  "companion",
6117
6388
  "compass",
6118
6389
  "connections",
6119
- "contentpilot",
6120
6390
  "copy",
6121
6391
  "cpu",
6122
6392
  "cpu-chip",
@@ -6362,8 +6632,6 @@ var treeui_catalog_default = {
6362
6632
  "tickets",
6363
6633
  "timeline",
6364
6634
  "timer",
6365
- "toggle-left",
6366
- "toggle-right",
6367
6635
  "token-input",
6368
6636
  "token-output",
6369
6637
  "trail",
@@ -6401,10 +6669,18 @@ var treeui_catalog_default = {
6401
6669
  },
6402
6670
  {
6403
6671
  name: "size",
6404
- type: "number | string",
6672
+ type: "number | TIconSize",
6405
6673
  default: "20",
6406
- description: "Pixel size for both width and height of the rendered svg.",
6407
- 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'
6674
+ values: [
6675
+ "sm",
6676
+ "md",
6677
+ "lg",
6678
+ "xl",
6679
+ "2xl",
6680
+ "<number of pixels>"
6681
+ ],
6682
+ description: "A token name from the icon scale, or a pixel number.",
6683
+ 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'
6408
6684
  },
6409
6685
  {
6410
6686
  name: "strokeWidth",
@@ -6434,7 +6710,7 @@ var treeui_catalog_default = {
6434
6710
  behavior: [
6435
6711
  "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.",
6436
6712
  '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).',
6437
- "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.",
6713
+ "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.",
6438
6714
  "resolved_per_render: TIcon resolves its name in a computed, not once at setup, so an icon registered after the component mounted still appears.",
6439
6715
  "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.",
6440
6716
  "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."
@@ -6484,7 +6760,89 @@ var treeui_catalog_default = {
6484
6760
  recipes: [
6485
6761
  "nav-menu-icons"
6486
6762
  ],
6487
- practices: []
6763
+ practices: [
6764
+ "branchline-icons"
6765
+ ]
6766
+ },
6767
+ {
6768
+ name: "TIconTile",
6769
+ aliases: [],
6770
+ package: "@treeui/vue",
6771
+ kind: "display",
6772
+ category: "Data Display",
6773
+ status: "available",
6774
+ purpose: "A registered icon on a toned ground, sized and coloured so the glyph reads at phone size.",
6775
+ sourceFile: "packages/vue/src/components/TIconTile.vue",
6776
+ storyFile: "apps/docs/src/stories/IconTile.stories.ts",
6777
+ storyTitle: "Components/Data Display/IconTile",
6778
+ manifestFile: "docs/ai/COMPONENTS/icon-tile.yaml",
6779
+ props: [
6780
+ {
6781
+ name: "name",
6782
+ type: "TIconName",
6783
+ default: "undefined",
6784
+ description: "Registry name. Use the default slot for a glyph that is not registered."
6785
+ },
6786
+ {
6787
+ name: "tone",
6788
+ type: "TIconTileTone",
6789
+ default: "brand",
6790
+ values: [
6791
+ "brand",
6792
+ "neutral",
6793
+ "success",
6794
+ "warning",
6795
+ "danger",
6796
+ "info"
6797
+ ],
6798
+ 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."
6799
+ },
6800
+ {
6801
+ name: "size",
6802
+ type: "TSize",
6803
+ default: "md",
6804
+ values: [
6805
+ "sm",
6806
+ "md",
6807
+ "lg"
6808
+ ],
6809
+ description: "2rem / 2.75rem / 3.5rem. The glyph steps with the tile (16 / 20 / 24px) so the ink keeps its proportion of the pill."
6810
+ },
6811
+ {
6812
+ name: "label",
6813
+ type: "string",
6814
+ default: "undefined",
6815
+ 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."
6816
+ }
6817
+ ],
6818
+ events: [],
6819
+ slots: [
6820
+ {
6821
+ name: "default"
6822
+ }
6823
+ ],
6824
+ a11y: [
6825
+ "semantic_element: span",
6826
+ '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.',
6827
+ "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."
6828
+ ],
6829
+ behavior: [],
6830
+ selection: {
6831
+ keywords: [],
6832
+ useWhen: [],
6833
+ avoidWhen: [],
6834
+ alternatives: []
6835
+ },
6836
+ setup: {
6837
+ required: [],
6838
+ notes: []
6839
+ },
6840
+ recipes: [],
6841
+ practices: [
6842
+ "accessible-by-default",
6843
+ "content-alignment",
6844
+ "token-driven"
6845
+ ]
6488
6846
  },
6489
6847
  {
6490
6848
  name: "TImage",
@@ -6607,7 +6965,7 @@ var treeui_catalog_default = {
6607
6965
  name: "modelValue",
6608
6966
  type: "T",
6609
6967
  default: "undefined",
6610
- 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"
6968
+ 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"
6611
6969
  },
6612
6970
  {
6613
6971
  name: "size",
@@ -7319,7 +7677,9 @@ var treeui_catalog_default = {
7319
7677
  "aria_current: `page` when `current`.",
7320
7678
  "focus_visible: Focus ring on the tile."
7321
7679
  ],
7322
- behavior: [],
7680
+ behavior: [
7681
+ "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."
7682
+ ],
7323
7683
  selection: {
7324
7684
  keywords: [
7325
7685
  "tile",
@@ -7355,6 +7715,7 @@ var treeui_catalog_default = {
7355
7715
  practices: [
7356
7716
  "interaction-feedback",
7357
7717
  "accessible-by-default",
7718
+ "content-alignment",
7358
7719
  "token-driven"
7359
7720
  ]
7360
7721
  },
@@ -7401,7 +7762,8 @@ var treeui_catalog_default = {
7401
7762
  "not_selectable: For a single-select list of options use TSelectableList instead."
7402
7763
  ],
7403
7764
  behavior: [
7404
- "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).",
7765
+ "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.",
7766
+ "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).",
7405
7767
  "no_overflow_ownership: TList does not create, own, or collapse an actions overflow."
7406
7768
  ],
7407
7769
  selection: {
@@ -7564,7 +7926,7 @@ var treeui_catalog_default = {
7564
7926
  kind: "overlay",
7565
7927
  category: "Overlay",
7566
7928
  status: "available",
7567
- 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).",
7929
+ 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.",
7568
7930
  sourceFile: "packages/vue/src/components/TMenu.vue",
7569
7931
  storyFile: "apps/docs/src/stories/Menu.stories.ts",
7570
7932
  storyTitle: "Components/Overlay/Menu",
@@ -7809,6 +8171,10 @@ var treeui_catalog_default = {
7809
8171
  "escape_closes_when_enabled: true"
7810
8172
  ],
7811
8173
  behavior: [
8174
+ "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.",
8175
+ "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.",
8176
+ "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.",
8177
+ "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`.",
7812
8178
  "uses_teleport_to_body: true",
7813
8179
  "locks_body_scroll_while_open: true",
7814
8180
  "overlay_click_closes_when_enabled: true",
@@ -7853,7 +8219,8 @@ var treeui_catalog_default = {
7853
8219
  ],
7854
8220
  practices: [
7855
8221
  "accessible-by-default",
7856
- "predictable-api"
8222
+ "predictable-api",
8223
+ "content-alignment"
7857
8224
  ]
7858
8225
  },
7859
8226
  {
@@ -8199,6 +8566,7 @@ var treeui_catalog_default = {
8199
8566
  "aria_disabled: on link items when disabled"
8200
8567
  ],
8201
8568
  behavior: [
8569
+ "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.",
8202
8570
  "can_be_controlled_or_uncontrolled: true",
8203
8571
  "can_inherit_collapsed_state_from_sidebar: true",
8204
8572
  "supports_rich_item_rendering_via_slot: true"
@@ -8246,7 +8614,8 @@ var treeui_catalog_default = {
8246
8614
  "responsive-navigation"
8247
8615
  ],
8248
8616
  practices: [
8249
- "interaction-feedback"
8617
+ "interaction-feedback",
8618
+ "content-alignment"
8250
8619
  ]
8251
8620
  },
8252
8621
  {
@@ -8265,7 +8634,7 @@ var treeui_catalog_default = {
8265
8634
  {
8266
8635
  name: "modelValue",
8267
8636
  type: "number | null",
8268
- 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"
8637
+ 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"
8269
8638
  },
8270
8639
  {
8271
8640
  name: "min",
@@ -8513,6 +8882,7 @@ var treeui_catalog_default = {
8513
8882
  "Actions area holds standard focusable controls (e.g. TButton) \u2014 no custom roles added."
8514
8883
  ],
8515
8884
  behavior: [
8885
+ "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`.",
8516
8886
  "title_and_subtitle_optional: true",
8517
8887
  "heading_level_controlled_by_level: true",
8518
8888
  "actions_area_right_aligned_and_wraps: true",
@@ -9533,14 +9903,14 @@ var treeui_catalog_default = {
9533
9903
  props: [
9534
9904
  {
9535
9905
  name: "modelValue",
9536
- type: "string | number",
9537
- default: ""
9906
+ type: "T",
9907
+ default: "undefined"
9538
9908
  },
9539
9909
  {
9540
9910
  name: "options",
9541
- type: "TSelectOption[]",
9911
+ type: "TSelectOption<T>[]",
9542
9912
  default: "[]",
9543
- note: "Array of { label: string; value: string | number; disabled?: boolean }"
9913
+ note: "Array of { label: string; value: T; disabled?: boolean }"
9544
9914
  },
9545
9915
  {
9546
9916
  name: "open",
@@ -9636,7 +10006,9 @@ var treeui_catalog_default = {
9636
10006
  "outside_click_closes_dropdown: true",
9637
10007
  "focus_restores_on_close: true",
9638
10008
  "numeric_values_supported_without_string_coercion: true",
9639
- "listbox_flips_above_trigger_when_space_below_is_insufficient: true"
10009
+ "listbox_flips_above_trigger_when_space_below_is_insufficient: true",
10010
+ "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.",
10011
+ "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`."
9640
10012
  ],
9641
10013
  selection: {
9642
10014
  keywords: [
@@ -9674,6 +10046,7 @@ var treeui_catalog_default = {
9674
10046
  ],
9675
10047
  practices: [
9676
10048
  "interaction-feedback",
10049
+ "accessible-by-default",
9677
10050
  "predictable-api",
9678
10051
  "token-driven"
9679
10052
  ]
@@ -10147,7 +10520,9 @@ var treeui_catalog_default = {
10147
10520
  "Hit area is `--tree-size-control-lg` (48px) tall, above the 44px target floor.",
10148
10521
  "The label must say where it goes; the library does not supply one."
10149
10522
  ],
10150
- behavior: [],
10523
+ behavior: [
10524
+ "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."
10525
+ ],
10151
10526
  selection: {
10152
10527
  keywords: [
10153
10528
  "skip link",
@@ -10372,6 +10747,12 @@ var treeui_catalog_default = {
10372
10747
  default: "false",
10373
10748
  note: "Marker on the final point (line/area)."
10374
10749
  },
10750
+ {
10751
+ name: "fluid",
10752
+ type: "boolean",
10753
+ default: "false",
10754
+ 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"
10755
+ },
10375
10756
  {
10376
10757
  name: "ariaLabel",
10377
10758
  type: "string",
@@ -10387,7 +10768,8 @@ var treeui_catalog_default = {
10387
10768
  behavior: [
10388
10769
  "no_axes_or_legend: true",
10389
10770
  "flat_series_renders_centered: true",
10390
- "scales_to_intrinsic_width_and_height: true"
10771
+ "scales_to_intrinsic_width_and_height: true",
10772
+ "fluid_fills_parent_width: true"
10391
10773
  ],
10392
10774
  selection: {
10393
10775
  keywords: [
@@ -10427,7 +10809,9 @@ var treeui_catalog_default = {
10427
10809
  recipes: [
10428
10810
  "metric-dashboard"
10429
10811
  ],
10430
- practices: []
10812
+ practices: [
10813
+ "content-alignment"
10814
+ ]
10431
10815
  },
10432
10816
  {
10433
10817
  name: "TSpinner",
@@ -10786,6 +11170,33 @@ var treeui_catalog_default = {
10786
11170
  storyTitle: "Components/Data Display/Stat",
10787
11171
  manifestFile: "docs/ai/COMPONENTS/stat.yaml",
10788
11172
  props: [
11173
+ {
11174
+ name: "variant",
11175
+ type: "TStatVariant",
11176
+ default: "card (plain inside a TStatGroup)",
11177
+ values: [
11178
+ "card",
11179
+ "plain"
11180
+ ],
11181
+ 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."
11182
+ },
11183
+ {
11184
+ name: "metaPlacement",
11185
+ type: "'top' | 'bottom'",
11186
+ default: "top",
11187
+ 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.'
11188
+ },
11189
+ {
11190
+ name: "size",
11191
+ type: "TSize",
11192
+ default: "md",
11193
+ values: [
11194
+ "sm",
11195
+ "md",
11196
+ "lg"
11197
+ ],
11198
+ 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."
11199
+ },
10789
11200
  {
10790
11201
  name: "label",
10791
11202
  type: "string",
@@ -10832,6 +11243,16 @@ var treeui_catalog_default = {
10832
11243
  name: "loading",
10833
11244
  type: "boolean",
10834
11245
  default: "false"
11246
+ },
11247
+ {
11248
+ name: "emphasis",
11249
+ type: "TStatEmphasis",
11250
+ default: "label",
11251
+ values: [
11252
+ "label",
11253
+ "value"
11254
+ ],
11255
+ 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'
10835
11256
  }
10836
11257
  ],
10837
11258
  events: [],
@@ -10858,6 +11279,7 @@ var treeui_catalog_default = {
10858
11279
  ],
10859
11280
  behavior: [
10860
11281
  "value_and_trend_are_optional: true",
11282
+ "emphasis_reorders_visually_not_in_the_dom: true",
10861
11283
  "icon_slot_adds_visual_emphasis: true",
10862
11284
  "trend_indicator_changes_with_direction: true",
10863
11285
  "loading_renders_internal_skeleton_placeholders: true"
@@ -10895,6 +11317,76 @@ var treeui_catalog_default = {
10895
11317
  "metric-dashboard"
10896
11318
  ],
10897
11319
  practices: [
11320
+ "content-alignment",
11321
+ "token-driven"
11322
+ ]
11323
+ },
11324
+ {
11325
+ name: "TStatGroup",
11326
+ aliases: [],
11327
+ package: "@treeui/vue",
11328
+ kind: "layout",
11329
+ status: "available",
11330
+ purpose: "A row of indicators sharing one surface, with a hairline between cells.",
11331
+ sourceFile: "packages/vue/src/components/TStatGroup.vue",
11332
+ manifestFile: "docs/ai/COMPONENTS/stat-group.yaml",
11333
+ props: [
11334
+ {
11335
+ name: "label",
11336
+ type: "string",
11337
+ default: "undefined",
11338
+ 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.'
11339
+ },
11340
+ {
11341
+ name: "columns",
11342
+ type: "number",
11343
+ default: "undefined",
11344
+ description: "Fixed track count. Leave unset to let `minItemWidth` decide."
11345
+ },
11346
+ {
11347
+ name: "minItemWidth",
11348
+ type: "string",
11349
+ default: "12rem",
11350
+ description: "Smallest a cell may get before a track is dropped. Same meaning as on TGrid."
11351
+ },
11352
+ {
11353
+ name: "balance",
11354
+ type: "boolean",
11355
+ default: "false",
11356
+ 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."
11357
+ }
11358
+ ],
11359
+ events: [],
11360
+ slots: [
11361
+ {
11362
+ name: "default"
11363
+ }
11364
+ ],
11365
+ a11y: [
11366
+ "semantic_element: div",
11367
+ "role: group",
11368
+ "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."
11369
+ ],
11370
+ behavior: [
11371
+ '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.',
11372
+ "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.",
11373
+ "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.",
11374
+ "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."
11375
+ ],
11376
+ selection: {
11377
+ keywords: [],
11378
+ useWhen: [],
11379
+ avoidWhen: [],
11380
+ alternatives: []
11381
+ },
11382
+ setup: {
11383
+ required: [],
11384
+ notes: []
11385
+ },
11386
+ recipes: [],
11387
+ practices: [
11388
+ "accessible-by-default",
11389
+ "content-alignment",
10898
11390
  "token-driven"
10899
11391
  ]
10900
11392
  },
@@ -10907,7 +11399,7 @@ var treeui_catalog_default = {
10907
11399
  kind: "navigation",
10908
11400
  category: "Navigation",
10909
11401
  status: "available",
10910
- purpose: "Stepper component for visualizing workflow progress, with optional interactive selection.",
11402
+ purpose: "Ordered sequence \u2014 a wizard to operate, or a described flow to read.",
10911
11403
  sourceFile: "packages/vue/src/components/TSteps.vue",
10912
11404
  storyFile: "apps/docs/src/stories/Steps.stories.ts",
10913
11405
  storyTitle: "Components/Navigation/Steps",
@@ -10951,7 +11443,20 @@ var treeui_catalog_default = {
10951
11443
  {
10952
11444
  name: "interactive",
10953
11445
  type: "boolean",
10954
- default: "false"
11446
+ default: "false",
11447
+ 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"
11448
+ },
11449
+ {
11450
+ name: "columns",
11451
+ type: "number",
11452
+ default: "undefined",
11453
+ note: "Fixed steps per row, horizontal only. Use for a sequence with a shape (3 + 3)."
11454
+ },
11455
+ {
11456
+ name: "minItemWidth",
11457
+ type: "string",
11458
+ default: "14rem",
11459
+ note: "Width at which a step stops shrinking and the strip wraps. Same axis as TGrid."
10955
11460
  }
10956
11461
  ],
10957
11462
  events: [
@@ -10967,10 +11472,13 @@ var treeui_catalog_default = {
10967
11472
  a11y: [
10968
11473
  "semantic_root: ol",
10969
11474
  "current_step_uses_aria_current_step: true",
10970
- "interactive_variant_uses_buttons: true"
11475
+ "interactive_variant_uses_buttons: true",
11476
+ "static_variant_emits_no_control: true"
10971
11477
  ],
10972
11478
  behavior: [
10973
11479
  "can_be_controlled_or_uncontrolled: true",
11480
+ "static_variant_elects_no_current_step: true",
11481
+ "shared_box_class_is_t_steps_box: true",
10974
11482
  "status_can_be_explicit_or_computed: true",
10975
11483
  "alias_export_available: true"
10976
11484
  ],
@@ -11001,7 +11509,10 @@ var treeui_catalog_default = {
11001
11509
  ]
11002
11510
  },
11003
11511
  recipes: [],
11004
- practices: []
11512
+ practices: [
11513
+ "accessible-by-default",
11514
+ "content-alignment"
11515
+ ]
11005
11516
  },
11006
11517
  {
11007
11518
  name: "TSwitch",
@@ -11108,11 +11619,23 @@ var treeui_catalog_default = {
11108
11619
  storyTitle: "Components/Data Display/Table",
11109
11620
  manifestFile: "docs/ai/COMPONENTS/table.yaml",
11110
11621
  props: [
11622
+ {
11623
+ name: "stackBelow",
11624
+ type: "TBreakpoint",
11625
+ default: "undefined",
11626
+ values: [
11627
+ "sm",
11628
+ "md",
11629
+ "lg",
11630
+ "xl"
11631
+ ],
11632
+ 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."
11633
+ },
11111
11634
  {
11112
11635
  name: "columns",
11113
11636
  type: "TTableColumn[]",
11114
11637
  required: true,
11115
- description: "Array of column definitions with key, label, sortable, align, and width."
11638
+ description: "Array of column definitions with key, label, sortable, align, width, and minWidth."
11116
11639
  },
11117
11640
  {
11118
11641
  name: "rows",
@@ -11154,6 +11677,35 @@ var treeui_catalog_default = {
11154
11677
  type: "(row, index) => 'default' | 'muted'",
11155
11678
  required: false,
11156
11679
  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."
11680
+ },
11681
+ {
11682
+ name: "rowHref",
11683
+ type: "(row: Record<string, unknown>, index: number) => string | undefined",
11684
+ default: "undefined",
11685
+ 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.'
11686
+ },
11687
+ {
11688
+ name: "rowTo",
11689
+ type: "(row: Record<string, unknown>, index: number) => string | undefined",
11690
+ default: "undefined",
11691
+ description: "Same as `rowHref`, resolved through the app's RouterLink instead of a plain `<a>`."
11692
+ },
11693
+ {
11694
+ name: "rowLabel",
11695
+ type: "(row: Record<string, unknown>, index: number) => string",
11696
+ default: "undefined",
11697
+ 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."
11698
+ },
11699
+ {
11700
+ name: "rowActivatable",
11701
+ type: "boolean",
11702
+ default: "false",
11703
+ 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.'
11704
+ },
11705
+ {
11706
+ name: "expandedRow",
11707
+ type: "string | number | null",
11708
+ description: "Key of the row whose detail panel is open, matched against the identity `rowKey` resolves."
11157
11709
  }
11158
11710
  ],
11159
11711
  events: [
@@ -11161,6 +11713,11 @@ var treeui_catalog_default = {
11161
11713
  name: "sort",
11162
11714
  description: "Emitted when a sortable column header is clicked.",
11163
11715
  payload: "TTableSortState"
11716
+ },
11717
+ {
11718
+ name: "row-activate",
11719
+ description: "Emitted when an activatable row is activated without navigating \u2014 see `rowActivatable`.",
11720
+ payload: "[row: Record<string, unknown>, index: number]"
11164
11721
  }
11165
11722
  ],
11166
11723
  slots: [
@@ -11175,6 +11732,10 @@ var treeui_catalog_default = {
11175
11732
  {
11176
11733
  name: "empty",
11177
11734
  description: "Content shown when rows array is empty."
11735
+ },
11736
+ {
11737
+ name: "detail",
11738
+ description: "Detail panel for the expanded row, rendered as a `<tr>` adjacent to its own row. Receives `{ row, index }`."
11178
11739
  }
11179
11740
  ],
11180
11741
  a11y: [
@@ -11185,7 +11746,11 @@ var treeui_catalog_default = {
11185
11746
  "keyboard: Sortable headers are focusable and activated with Enter or Space.",
11186
11747
  "scope: col on all th elements."
11187
11748
  ],
11188
- behavior: [],
11749
+ behavior: [
11750
+ '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.',
11751
+ "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.",
11752
+ "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."
11753
+ ],
11189
11754
  selection: {
11190
11755
  keywords: [
11191
11756
  "table",
@@ -11220,7 +11785,9 @@ var treeui_catalog_default = {
11220
11785
  ],
11221
11786
  practices: [
11222
11787
  "interaction-feedback",
11223
- "state-clarity"
11788
+ "accessible-by-default",
11789
+ "state-clarity",
11790
+ "content-alignment"
11224
11791
  ]
11225
11792
  },
11226
11793
  {
@@ -11240,7 +11807,7 @@ var treeui_catalog_default = {
11240
11807
  name: "modelValue",
11241
11808
  type: "T",
11242
11809
  default: "undefined",
11243
- 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"
11810
+ 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"
11244
11811
  },
11245
11812
  {
11246
11813
  name: "defaultValue",
@@ -11290,6 +11857,7 @@ var treeui_catalog_default = {
11290
11857
  "aria_controls_aria_labelledby_pairing: true"
11291
11858
  ],
11292
11859
  behavior: [
11860
+ "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.",
11293
11861
  "arrow_left_right_moves_focus: true",
11294
11862
  "home_focuses_first_tab: true",
11295
11863
  "end_focuses_last_tab: true",
@@ -11329,7 +11897,9 @@ var treeui_catalog_default = {
11329
11897
  "router-backed-tabs"
11330
11898
  ],
11331
11899
  practices: [
11332
- "interaction-feedback"
11900
+ "interaction-feedback",
11901
+ "accessible-by-default",
11902
+ "content-alignment"
11333
11903
  ]
11334
11904
  },
11335
11905
  {
@@ -11667,6 +12237,17 @@ var treeui_catalog_default = {
11667
12237
  ],
11668
12238
  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"
11669
12239
  },
12240
+ {
12241
+ name: "align",
12242
+ type: "TTextAlign",
12243
+ default: "undefined",
12244
+ values: [
12245
+ "start",
12246
+ "center",
12247
+ "end"
12248
+ ],
12249
+ 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"
12250
+ },
11670
12251
  {
11671
12252
  name: "balance",
11672
12253
  type: "boolean",
@@ -11707,6 +12288,7 @@ var treeui_catalog_default = {
11707
12288
  "Choose `as` to convey the correct semantics (e.g. headings via h1\u2013h6); TText adds only visual styling."
11708
12289
  ],
11709
12290
  behavior: [
12291
+ '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.',
11710
12292
  "semantic_element_controlled_by_as: true",
11711
12293
  "token_driven_size_tone_weight: true",
11712
12294
  "truncate_takes_precedence_over_preserve_whitespace: true"
@@ -11728,6 +12310,7 @@ var treeui_catalog_default = {
11728
12310
  practices: [
11729
12311
  "accessible-by-default",
11730
12312
  "predictable-api",
12313
+ "content-alignment",
11731
12314
  "token-driven"
11732
12315
  ]
11733
12316
  },
@@ -11744,6 +12327,12 @@ var treeui_catalog_default = {
11744
12327
  storyTitle: "Components/Data Entry/Textarea",
11745
12328
  manifestFile: "docs/ai/COMPONENTS/textarea.yaml",
11746
12329
  props: [
12330
+ {
12331
+ name: "spellcheck",
12332
+ type: "boolean",
12333
+ default: "undefined",
12334
+ 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.'
12335
+ },
11747
12336
  {
11748
12337
  name: "modelValue",
11749
12338
  type: "string",
@@ -11817,7 +12406,7 @@ var treeui_catalog_default = {
11817
12406
  "sans",
11818
12407
  "mono"
11819
12408
  ],
11820
- 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"
12409
+ 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"
11821
12410
  }
11822
12411
  ],
11823
12412
  events: [
@@ -11917,6 +12506,7 @@ var treeui_catalog_default = {
11917
12506
  "timestamp_uses_time_element_when_provided: true"
11918
12507
  ],
11919
12508
  behavior: [
12509
+ "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.",
11920
12510
  "items_render_in_order_given: true",
11921
12511
  "line_hidden_on_last_item: true",
11922
12512
  "marker_tone_defaults_to_neutral: true"
@@ -11932,7 +12522,9 @@ var treeui_catalog_default = {
11932
12522
  notes: []
11933
12523
  },
11934
12524
  recipes: [],
11935
- practices: []
12525
+ practices: [
12526
+ "content-alignment"
12527
+ ]
11936
12528
  },
11937
12529
  {
11938
12530
  name: "TToast",
@@ -11947,6 +12539,12 @@ var treeui_catalog_default = {
11947
12539
  storyTitle: "Components/Feedback/Toast",
11948
12540
  manifestFile: "docs/ai/COMPONENTS/toast.yaml",
11949
12541
  props: [
12542
+ {
12543
+ name: "closeLabel",
12544
+ type: "string",
12545
+ default: "Dismiss notification",
12546
+ 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."
12547
+ },
11950
12548
  {
11951
12549
  name: "toast",
11952
12550
  type: "ToastItem",
@@ -12006,6 +12604,7 @@ var treeui_catalog_default = {
12006
12604
  "confirm-destructive-action"
12007
12605
  ],
12008
12606
  practices: [
12607
+ "accessible-by-default",
12009
12608
  "token-driven"
12010
12609
  ]
12011
12610
  },
@@ -12026,7 +12625,7 @@ var treeui_catalog_default = {
12026
12625
  name: "modelValue",
12027
12626
  type: "TToggleGroupModel<T, M> \u2014 `T` in single mode, `T[]` in multiple",
12028
12627
  default: "undefined",
12029
- 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"
12628
+ 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"
12030
12629
  },
12031
12630
  {
12032
12631
  name: "options",
@@ -12087,6 +12686,7 @@ var treeui_catalog_default = {
12087
12686
  "activation_keys_select_option: true"
12088
12687
  ],
12089
12688
  behavior: [
12689
+ '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.',
12090
12690
  "single_and_multiple_modes: true",
12091
12691
  "variant_changes_visual_style_only: true",
12092
12692
  "visible_option_set_expected: true"
@@ -12127,7 +12727,9 @@ var treeui_catalog_default = {
12127
12727
  ]
12128
12728
  },
12129
12729
  recipes: [],
12130
- practices: []
12730
+ practices: [
12731
+ "accessible-by-default"
12732
+ ]
12131
12733
  },
12132
12734
  {
12133
12735
  name: "TTooltip",
@@ -13276,25 +13878,25 @@ var treeui_catalog_default = {
13276
13878
  {
13277
13879
  cssVar: "--tree-color-status-success",
13278
13880
  path: "color.status.success",
13279
- value: "#1a7f37",
13881
+ value: "#197d36",
13280
13882
  category: "color",
13281
13883
  derived: false,
13282
13884
  description: "Themed semantic color, resolved per [data-tree-theme].",
13283
13885
  themeValues: {
13284
- light: "#1a7f37",
13285
- dark: "#59ac5c"
13886
+ light: "#197d36",
13887
+ dark: "#5bae5e"
13286
13888
  }
13287
13889
  },
13288
13890
  {
13289
13891
  cssVar: "--tree-color-status-warning",
13290
13892
  path: "color.status.warning",
13291
- value: "#956400",
13893
+ value: "#946300",
13292
13894
  category: "color",
13293
13895
  derived: false,
13294
13896
  description: "Themed semantic color, resolved per [data-tree-theme].",
13295
13897
  themeValues: {
13296
- light: "#956400",
13297
- dark: "#c69026"
13898
+ light: "#946300",
13899
+ dark: "#c99227"
13298
13900
  }
13299
13901
  },
13300
13902
  {
@@ -13600,25 +14202,25 @@ var treeui_catalog_default = {
13600
14202
  {
13601
14203
  cssVar: "--tree-color-status-success-hover",
13602
14204
  path: "color.status.success-hover",
13603
- value: "#15682d",
14205
+ value: "#15672c",
13604
14206
  category: "color",
13605
14207
  derived: true,
13606
14208
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13607
14209
  themeValues: {
13608
- light: "#15682d",
13609
- dark: "#77bb79"
14210
+ light: "#15672c",
14211
+ dark: "#79bd7b"
13610
14212
  }
13611
14213
  },
13612
14214
  {
13613
14215
  cssVar: "--tree-color-status-success-press",
13614
14216
  path: "color.status.success-press",
13615
- value: "#125625",
14217
+ value: "#115525",
13616
14218
  category: "color",
13617
14219
  derived: true,
13618
14220
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13619
14221
  themeValues: {
13620
- light: "#125625",
13621
- dark: "#8ec790"
14222
+ light: "#115525",
14223
+ dark: "#8fc892"
13622
14224
  }
13623
14225
  },
13624
14226
  {
@@ -13630,31 +14232,31 @@ var treeui_catalog_default = {
13630
14232
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13631
14233
  themeValues: {
13632
14234
  light: "#dfede3",
13633
- dark: "#2a3a34"
14235
+ dark: "#2a3a35"
13634
14236
  }
13635
14237
  },
13636
14238
  {
13637
14239
  cssVar: "--tree-color-status-success-soft-hover",
13638
14240
  path: "color.status.success-soft-hover",
13639
- value: "#c3decb",
14241
+ value: "#c3ddcb",
13640
14242
  category: "color",
13641
14243
  derived: true,
13642
14244
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13643
14245
  themeValues: {
13644
- light: "#c3decb",
13645
- dark: "#314a3a"
14246
+ light: "#c3ddcb",
14247
+ dark: "#314a3b"
13646
14248
  }
13647
14249
  },
13648
14250
  {
13649
14251
  cssVar: "--tree-color-status-success-border",
13650
14252
  path: "color.status.success-border",
13651
- value: "#419559",
14253
+ value: "#419359",
13652
14254
  category: "color",
13653
14255
  derived: true,
13654
14256
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13655
14257
  themeValues: {
13656
- light: "#419559",
13657
- dark: "#4d9052"
14258
+ light: "#419359",
14259
+ dark: "#4f9154"
13658
14260
  }
13659
14261
  },
13660
14262
  {
@@ -13672,49 +14274,49 @@ var treeui_catalog_default = {
13672
14274
  {
13673
14275
  cssVar: "--tree-color-status-success-on-soft",
13674
14276
  path: "color.status.success-on-soft",
13675
- value: "#197934",
14277
+ value: "#187733",
13676
14278
  category: "color",
13677
14279
  derived: true,
13678
14280
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13679
14281
  themeValues: {
13680
- light: "#197934",
13681
- dark: "#61b064"
14282
+ light: "#187733",
14283
+ dark: "#63b266"
13682
14284
  }
13683
14285
  },
13684
14286
  {
13685
14287
  cssVar: "--tree-color-status-success-on-soft-hover",
13686
14288
  path: "color.status.success-on-soft-hover",
13687
- value: "#15672d",
14289
+ value: "#156b2e",
13688
14290
  category: "color",
13689
14291
  derived: true,
13690
14292
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13691
14293
  themeValues: {
13692
- light: "#15672d",
13693
- dark: "#85c287"
14294
+ light: "#156b2e",
14295
+ dark: "#80c082"
13694
14296
  }
13695
14297
  },
13696
14298
  {
13697
14299
  cssVar: "--tree-color-status-warning-hover",
13698
14300
  path: "color.status.warning-hover",
13699
- value: "#7a5200",
14301
+ value: "#795100",
13700
14302
  category: "color",
13701
14303
  derived: true,
13702
14304
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13703
14305
  themeValues: {
13704
- light: "#7a5200",
13705
- dark: "#d0a44d"
14306
+ light: "#795100",
14307
+ dark: "#d3a64e"
13706
14308
  }
13707
14309
  },
13708
14310
  {
13709
14311
  cssVar: "--tree-color-status-warning-press",
13710
14312
  path: "color.status.warning-press",
13711
- value: "#654400",
14313
+ value: "#654300",
13712
14314
  category: "color",
13713
14315
  derived: true,
13714
14316
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13715
14317
  themeValues: {
13716
- light: "#654400",
13717
- dark: "#d8b46b"
14318
+ light: "#654300",
14319
+ dark: "#dab56c"
13718
14320
  }
13719
14321
  },
13720
14322
  {
@@ -13744,13 +14346,13 @@ var treeui_catalog_default = {
13744
14346
  {
13745
14347
  cssVar: "--tree-color-status-warning-border",
13746
14348
  path: "color.status.warning-border",
13747
- value: "#a77f2c",
14349
+ value: "#a67e2c",
13748
14350
  category: "color",
13749
14351
  derived: true,
13750
14352
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13751
14353
  themeValues: {
13752
- light: "#a77f2c",
13753
- dark: "#a37a28"
14354
+ light: "#a67e2c",
14355
+ dark: "#a57b29"
13754
14356
  }
13755
14357
  },
13756
14358
  {
@@ -13768,25 +14370,25 @@ var treeui_catalog_default = {
13768
14370
  {
13769
14371
  cssVar: "--tree-color-status-warning-on-soft",
13770
14372
  path: "color.status.warning-on-soft",
13771
- value: "#8e5f00",
14373
+ value: "#8d5e00",
13772
14374
  category: "color",
13773
14375
  derived: true,
13774
14376
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13775
14377
  themeValues: {
13776
- light: "#8e5f00",
13777
- dark: "#c99631"
14378
+ light: "#8d5e00",
14379
+ dark: "#cc9732"
13778
14380
  }
13779
14381
  },
13780
14382
  {
13781
14383
  cssVar: "--tree-color-status-warning-on-soft-hover",
13782
14384
  path: "color.status.warning-on-soft-hover",
13783
- value: "#805600",
14385
+ value: "#7f5500",
13784
14386
  category: "color",
13785
14387
  derived: true,
13786
14388
  description: "Themed semantic color, resolved per [data-tree-theme]. Derived by the library from the semantic layer \u2014 read it, do not override it.",
13787
14389
  themeValues: {
13788
- light: "#805600",
13789
- dark: "#d5ad5f"
14390
+ light: "#7f5500",
14391
+ dark: "#d5ab58"
13790
14392
  }
13791
14393
  },
13792
14394
  {