@astryxdesign/core 0.7.1-canary.3235ec9 → 0.7.1-canary.366aafa

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.
@@ -27,6 +27,7 @@ export interface DividerProps extends BaseProps<HTMLDivElement> {
27
27
  orientation?: 'horizontal' | 'vertical';
28
28
  /**
29
29
  * Optional label to display centered on the divider.
30
+ * Numeric zero is preserved; null, booleans, and empty strings render no label.
30
31
  * Rendered with small, secondary text styling.
31
32
  */
32
33
  label?: ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"Divider.d.ts","sourceRoot":"","sources":["../../src/Divider/Divider.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAQ,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAC5C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAW5C,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,SAAS,CAAC;AAE/C;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,iBAAiB,CAAC;AAErD,MAAM,WAAW,YAAa,SAAQ,SAAS,CAAC,cAAc,CAAC;IAC7D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;OAGG;IACH,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IAExC;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAElB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;IAEzB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAiED;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,EACtB,WAA0B,EAC1B,KAAK,EACL,OAAkB,EAClB,WAAmB,EACnB,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,YAAY,EAAE,SAAS,EACvB,iBAAiB,EAAE,cAAc,EACjC,GAAG,KAAK,EACT,EAAE,YAAY,+BA0Dd;yBAtEe,OAAO"}
1
+ {"version":3,"file":"Divider.d.ts","sourceRoot":"","sources":["../../src/Divider/Divider.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAQ,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAC5C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAW5C,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,SAAS,CAAC;AAE/C;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,iBAAiB,CAAC;AAErD,MAAM,WAAW,YAAa,SAAQ,SAAS,CAAC,cAAc,CAAC;IAC7D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;OAGG;IACH,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IAExC;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAElB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;IAEzB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAiED;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,EACtB,WAA0B,EAC1B,KAAK,EACL,OAAkB,EAClB,WAAmB,EACnB,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,YAAY,EAAE,SAAS,EACvB,iBAAiB,EAAE,cAAc,EACjC,GAAG,KAAK,EACT,EAAE,YAAY,+BA2Dd;yBAvEe,OAAO"}
@@ -18,7 +18,7 @@ import { useId } from 'react';
18
18
  import * as stylex from '@stylexjs/stylex';
19
19
  import "../theme/tokens.stylex.js";
20
20
  import { colorVars, spacingVars, typeScaleVars, borderVars } from "../theme/tokens.stylex.js";
21
- import { mergeProps } from "../utils/index.js";
21
+ import { isRenderable, mergeProps } from "../utils/index.js";
22
22
  import { themeProps } from "../utils/themeProps.js";
23
23
 
24
24
  /**
@@ -102,13 +102,14 @@ export function Divider({
102
102
  ...props
103
103
  }) {
104
104
  const isHorizontal = orientation === 'horizontal';
105
+ const hasLabel = isRenderable(label);
105
106
  const labelId = useId();
106
107
 
107
108
  // A separator does not derive its accessible name from its content, so the
108
109
  // rendered label must be referenced explicitly via aria-labelledby to be
109
110
  // exposed as the separator's name (WCAG 1.3.1). An explicit aria-label or
110
111
  // aria-labelledby from the consumer takes precedence.
111
- const resolvedLabelledBy = ariaLabelledBy ?? (label && ariaLabel == null ? labelId : undefined);
112
+ const resolvedLabelledBy = ariaLabelledBy ?? (hasLabel && ariaLabel == null ? labelId : undefined);
112
113
  return /*#__PURE__*/_jsxs("div", {
113
114
  ref: ref,
114
115
  ...props,
@@ -122,7 +123,7 @@ export function Divider({
122
123
  }), stylex.props(isHorizontal ? baseStyles.horizontal : baseStyles.vertical, isFullBleed && (isHorizontal ? fullBleedStyles.horizontal : fullBleedStyles.vertical), xstyle), className, style),
123
124
  children: [/*#__PURE__*/_jsx("div", {
124
125
  ...stylex.props(isHorizontal ? lineStyles.horizontalLine : lineStyles.verticalLine, lineStyles[variant])
125
- }), label && /*#__PURE__*/_jsx("div", {
126
+ }), hasLabel && /*#__PURE__*/_jsx("div", {
126
127
  id: labelId,
127
128
  ...{
128
129
  0: {
@@ -133,7 +134,7 @@ export function Divider({
133
134
  }
134
135
  }[!!!isHorizontal << 0],
135
136
  children: label
136
- }), label && /*#__PURE__*/_jsx("div", {
137
+ }), hasLabel && /*#__PURE__*/_jsx("div", {
137
138
  ...stylex.props(isHorizontal ? lineStyles.horizontalLine : lineStyles.verticalLine, lineStyles[variant])
138
139
  })]
139
140
  });
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file DropdownMenuCheckboxItem.tsx
3
- * @input React, stylex, Item + CheckboxInput + DropdownMenu context + tokens from core
3
+ * @input React, stylex, Item + Indicator registry + DropdownMenu context + tokens from core
4
4
  * @output DropdownMenuCheckboxItem — a standalone checkable menu item.
5
5
  * @position Sub-component; used inside DropdownMenu.
6
6
  *
@@ -47,9 +47,8 @@ export interface DropdownMenuCheckboxItemProps extends Omit<BaseProps, 'onChange
47
47
  */
48
48
  onChange?: (checked: boolean) => void;
49
49
  /**
50
- * Whether the item is disabled. Disabled items stay focusable (via
51
- * `aria-disabled`) so they remain discoverable by keyboard and assistive
52
- * technology, but activation is blocked.
50
+ * Whether the item is disabled. Disabled items expose `aria-disabled`, block
51
+ * activation, and are currently skipped by DropdownMenu arrow-key navigation.
53
52
  * @default false
54
53
  */
55
54
  isDisabled?: boolean;
@@ -73,6 +72,9 @@ export interface DropdownMenuCheckboxItemProps extends Omit<BaseProps, 'onChange
73
72
  *
74
73
  * Must be used inside a DropdownMenu. Toggles an independent boolean; for a
75
74
  * one-of-N choice use DropdownMenuRadioGroup + DropdownMenuRadioItem instead.
75
+ * Consumer click and pointer-move handlers compose after the row's own
76
+ * activation and hover-focus behavior; disabled activation stops before the
77
+ * consumer click handler.
76
78
  *
77
79
  * @example
78
80
  * ```
@@ -86,7 +88,7 @@ export interface DropdownMenuCheckboxItemProps extends Omit<BaseProps, 'onChange
86
88
  * </DropdownMenu>
87
89
  * ```
88
90
  */
89
- export declare function DropdownMenuCheckboxItem({ label, description, icon, value, onChange, isDisabled, hasCloseOnSelect, endContent, xstyle, className, style, ...rest }: DropdownMenuCheckboxItemProps): import("react").JSX.Element;
91
+ export declare function DropdownMenuCheckboxItem({ label, description, icon, value, onChange, isDisabled, hasCloseOnSelect, endContent, xstyle, className, style, onClick, onPointerMove, ...rest }: DropdownMenuCheckboxItemProps): import("react").JSX.Element;
90
92
  export declare namespace DropdownMenuCheckboxItem {
91
93
  var displayName: string;
92
94
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DropdownMenuCheckboxItem.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenuCheckboxItem.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAErE,OAAO,EAAiB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;AAOtD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA4C5C,MAAM,WAAW,6BAA8B,SAAQ,IAAI,CACzD,SAAS,EACT,UAAU,GAAG,MAAM,GAAG,cAAc,GAAG,UAAU,CAClD;IACC;;OAEG;IACH,KAAK,EAAE,SAAS,CAAC;IACjB;;OAEG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B;;OAEG;IACH,KAAK,EAAE,OAAO,CAAC;IACf;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sFAAsF;IACtF,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC7B;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,KAAK,EACL,WAAW,EACX,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,UAAkB,EAClB,gBAAwB,EACxB,UAAU,EACV,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,IAAI,EACR,EAAE,6BAA6B,+BA0D/B;yBAvEe,wBAAwB"}
1
+ {"version":3,"file":"DropdownMenuCheckboxItem.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenuCheckboxItem.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,EAAiB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;AAOtD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA4C5C,MAAM,WAAW,6BAA8B,SAAQ,IAAI,CACzD,SAAS,EACT,UAAU,GAAG,MAAM,GAAG,cAAc,GAAG,UAAU,CAClD;IACC;;OAEG;IACH,KAAK,EAAE,SAAS,CAAC;IACjB;;OAEG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B;;OAEG;IACH,KAAK,EAAE,OAAO,CAAC;IACf;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sFAAsF;IACtF,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC7B;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,KAAK,EACL,WAAW,EACX,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,UAAkB,EAClB,gBAAwB,EACxB,UAAU,EACV,MAAM,EACN,SAAS,EACT,KAAK,EACL,OAAO,EACP,aAAa,EACb,GAAG,IAAI,EACR,EAAE,6BAA6B,+BA+D/B;yBA9Ee,wBAAwB"}
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file DropdownMenuCheckboxItem.tsx
7
- * @input React, stylex, Item + CheckboxInput + DropdownMenu context + tokens from core
7
+ * @input React, stylex, Item + Indicator registry + DropdownMenu context + tokens from core
8
8
  * @output DropdownMenuCheckboxItem — a standalone checkable menu item.
9
9
  * @position Sub-component; used inside DropdownMenu.
10
10
  *
@@ -34,7 +34,7 @@ import { useDropdownMenuContext } from "./DropdownMenuContext.js";
34
34
  import { focusMenuItemOnHover } from "./menuItemHover.js";
35
35
  import "../theme/tokens.stylex.js";
36
36
  import { colorVars, spacingVars } from "../theme/tokens.stylex.js";
37
- import { mergeProps, themeProps } from "../utils/index.js";
37
+ import { composeEventHandlers, mergeProps, themeProps } from "../utils/index.js";
38
38
  import { jsx as _jsx } from "react/jsx-runtime";
39
39
  const styles = {
40
40
  root: {
@@ -67,6 +67,9 @@ const styles = {
67
67
  *
68
68
  * Must be used inside a DropdownMenu. Toggles an independent boolean; for a
69
69
  * one-of-N choice use DropdownMenuRadioGroup + DropdownMenuRadioItem instead.
70
+ * Consumer click and pointer-move handlers compose after the row's own
71
+ * activation and hover-focus behavior; disabled activation stops before the
72
+ * consumer click handler.
70
73
  *
71
74
  * @example
72
75
  * ```
@@ -92,28 +95,32 @@ export function DropdownMenuCheckboxItem({
92
95
  xstyle,
93
96
  className,
94
97
  style,
98
+ onClick,
99
+ onPointerMove,
95
100
  ...rest
96
101
  }) {
97
102
  const ctx = useDropdownMenuContext();
98
103
  const menuSize = ctx?.menuSize ?? 'md';
99
104
  const controlSize = menuSize === 'sm' ? 'sm' : 'md';
100
105
  const CheckboxControl = useIndicator('checkbox');
101
- const handleClick = useCallback(() => {
106
+ const handleClick = useCallback(event => {
102
107
  if (isDisabled) {
108
+ event.preventDefault();
103
109
  return;
104
110
  }
105
111
  onChange?.(!value);
106
112
  if (hasCloseOnSelect) {
107
113
  ctx?.closeMenu();
108
114
  }
109
- }, [isDisabled, onChange, value, hasCloseOnSelect, ctx]);
115
+ onClick?.(event);
116
+ }, [isDisabled, onChange, value, hasCloseOnSelect, ctx, onClick]);
110
117
  const handlePointerMove = useCallback(e => focusMenuItemOnHover(e, isDisabled), [isDisabled]);
111
118
  return /*#__PURE__*/_jsx(Item, {
112
119
  ...rest,
113
120
  role: "menuitemcheckbox",
114
121
  "aria-checked": value,
115
122
  tabIndex: isDisabled ? undefined : -1,
116
- onPointerMove: handlePointerMove,
123
+ onPointerMove: composeEventHandlers(handlePointerMove, onPointerMove),
117
124
  marker:
118
125
  /*#__PURE__*/
119
126
  // No wrapper, and no menu-specific theme target: the shared
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/core",
3
- "version": "0.7.1-canary.3235ec9",
3
+ "version": "0.7.1-canary.366aafa",
4
4
  "displayName": "Astryx Core",
5
5
  "description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
6
6
  "author": "Meta Open Source",
@@ -787,8 +787,8 @@
787
787
  "react-dom": ">=19.0.0"
788
788
  },
789
789
  "devDependencies": {
790
- "@astryxdesign/a11y-spec": "0.7.1-canary.3235ec9",
791
- "@astryxdesign/cli": "0.7.1-canary.3235ec9",
790
+ "@astryxdesign/a11y-spec": "0.7.1-canary.366aafa",
791
+ "@astryxdesign/cli": "0.7.1-canary.366aafa",
792
792
  "@babel/cli": "^7.29.7",
793
793
  "@babel/core": "^7.29.7",
794
794
  "@babel/preset-react": "^7.29.7",
@@ -1556,6 +1556,10 @@ describe('ChatComposerInput', () => {
1556
1556
  await waitFor(() => {
1557
1557
  expect(editable).toHaveAttribute('aria-expanded', 'false');
1558
1558
  });
1559
+ // The hidden menu's results commit outside act, so the popover's Escape
1560
+ // registration is released by a passive effect that can still be pending
1561
+ // when the attribute changes. Flush it before pressing Escape.
1562
+ await act(async () => {});
1559
1563
 
1560
1564
  // Nothing cancels the key: the closed menu holds no Escape handling,
1561
1565
  // so a surrounding dialog or drawer still receives it.
@@ -80,7 +80,7 @@ Consumer migration instructions belong in consumer docs and release notes.
80
80
  - Sender identity, avatar, bubble, transcript ordering, or scrolling.
81
81
  - Divider rule paint, label placement, separator role, or separator naming, which are delegated to `component:Divider`.
82
82
  - Long Divider-label overflow and forced-colors rule paint, which remain shared `component:Divider` advisories rather than ChatSystemMessage-owned defects.
83
- - Divider's falsey-label rendering. Empty string and empty Fragment content leave the separator without a visible label or accessible name; numeric zero renders stray `00` text with no accessible name. This is a shared `spec:AST-002/FR15` advisory routed to `component:Divider`.
83
+ - Divider's skipped-label rendering. Empty strings and empty Fragments leave the separator without a visible label or accessible name; numeric zero renders one visible `0` and names the separator `0`. These partitions remain owned by `component:Divider`.
84
84
  - The semantic or directional meaning of caller-provided content or icon artwork.
85
85
  - Announcement timing or spoken output; claims at that layer require `spec:AST-009` evidence.
86
86
 
@@ -99,14 +99,14 @@ observable public surface without deciding the unresolved divider/icon contract.
99
99
 
100
100
  ## Behavioral and layout contract
101
101
 
102
- | ID | Candidate invariant | Basis | Draft review state |
103
- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
104
- | FR1 | The default branch MUST render the required content in a centered, noninteractive status row, allow long content to wrap within the available inline space, and preserve optional icon geometry. | Current source, released docs, tests, and examples | Verified current behavior |
105
- | FR2 | For truthy content, the divider branch MUST render the content through Divider as a labelled horizontal separator inside the component's status root. Falsey content follows the shared Divider advisory recorded in AV1. | Current source, released docs, Divider implementation, tests, and examples | Verified current behavior; shared Divider advisory |
106
- | FR3 | The divider branch currently ignores `icon`; this draft MUST NOT convert that observation into a stable restriction, a requirement to render the icon, or a warning policy. | `architecture:public-component-api/INV3`, `architecture:public-component-api/INV5`, and `spec:AST-002/FR15` | Human API decision required; see OQ1 |
107
- | FR4 | Supported root DOM, data, ARIA, className, style, xstyle, and ref inputs MUST reach or compose on the root while component-owned `role="status"` and reflected `variant` remain intact. | Current source plus `architecture:public-component-api/INV5-INV8` | Verified current behavior and existing focused evidence |
108
- | FR5 | The component MUST use semantic color, spacing, and typography tokens and logical layout; the `variant` state remains reflected on the single `chat-system-message` target. | Current source, `architecture:theme-tokens`, and theming architecture | Verified current behavior |
109
- | FR6 | The component remains render-only: props determine output without Effects, state mirrors, observers, listeners, timers, or owned async resources. | Current source and `architecture:react-component-runtime/INV1-INV8` | Verified current behavior |
102
+ | ID | Candidate invariant | Basis | Draft review state |
103
+ | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
104
+ | FR1 | The default branch MUST render the required content in a centered, noninteractive status row, allow long content to wrap within the available inline space, and preserve optional icon geometry. | Current source, released docs, tests, and examples | Verified current behavior |
105
+ | FR2 | The divider branch MUST render renderable content through Divider as a labelled horizontal separator inside the component's status root. Skipped content follows Divider's delegated label contract. | Current source, released docs, Divider implementation, tests, and examples | Verified current behavior; delegated Divider partitions |
106
+ | FR3 | The divider branch currently ignores `icon`; this draft MUST NOT convert that observation into a stable restriction, a requirement to render the icon, or a warning policy. | `architecture:public-component-api/INV3`, `architecture:public-component-api/INV5`, and `spec:AST-002/FR15` | Human API decision required; see OQ1 |
107
+ | FR4 | Supported root DOM, data, ARIA, className, style, xstyle, and ref inputs MUST reach or compose on the root while component-owned `role="status"` and reflected `variant` remain intact. | Current source plus `architecture:public-component-api/INV5-INV8` | Verified current behavior and existing focused evidence |
108
+ | FR5 | The component MUST use semantic color, spacing, and typography tokens and logical layout; the `variant` state remains reflected on the single `chat-system-message` target. | Current source, `architecture:theme-tokens`, and theming architecture | Verified current behavior |
109
+ | FR6 | The component remains render-only: props determine output without Effects, state mirrors, observers, listeners, timers, or owned async resources. | Current source and `architecture:react-component-runtime/INV1-INV8` | Verified current behavior |
110
110
 
111
111
  ### Allowed variation
112
112
 
@@ -116,9 +116,8 @@ observable public surface without deciding the unresolved divider/icon contract.
116
116
  content unchanged: empty string and empty Fragment content remain empty, while
117
117
  numeric zero renders one `0`. In the divider variant, the status root and separator
118
118
  remain; empty string and empty Fragment content produce no visible label or
119
- accessible name, while numeric zero paints stray `00` text with no accessible name.
120
- The divider's falsey-label behavior is a shared `spec:AST-002/FR15` advisory routed
121
- to `component:Divider`.
119
+ accessible name, while numeric zero renders one visible `0` and names the
120
+ separator `0`. Divider owns those label partitions.
122
121
  - **AV2 - Caller styling.** Supported root styling inputs may extend the component
123
122
  without replacing owned status semantics or variant reflection.
124
123
  - **AV3 - Delegated Divider paint.** Divider owns its rule and label paint and may
@@ -126,15 +125,15 @@ observable public surface without deciding the unresolved divider/icon contract.
126
125
 
127
126
  ### Representative states
128
127
 
129
- | State | Required invariant | Allowed variation |
130
- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
131
- | Default | Centered status content is present. | Content, theme, and root styling. |
132
- | Default with icon | Optional icon content and visible content render together. | Caller-provided artwork and content. |
133
- | Default long content | Content wraps without crossing either inline edge or creating page overflow. | Theme and localized content. |
134
- | Divider | A labelled horizontal separator presents the supplied content. | Label content, theme, and root styling. |
135
- | Divider with icon | Current output is measured without deciding the ignored-input contract. | No behavior change until OQ1 is resolved. |
136
- | Empty ReactNode | The default variant keeps `role="status"` and renders content unchanged (empty or one `0`). In the divider variant, the status root and separator remain; empty content has no label/name, while numeric zero paints stray `00` without a name. | Empty string, numeric zero, empty Fragment. |
137
- | Narrow/coarse pointer | The noninteractive row remains visible without horizontal page overflow. | Theme and supported content. |
128
+ | State | Required invariant | Allowed variation |
129
+ | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
130
+ | Default | Centered status content is present. | Content, theme, and root styling. |
131
+ | Default with icon | Optional icon content and visible content render together. | Caller-provided artwork and content. |
132
+ | Default long content | Content wraps without crossing either inline edge or creating page overflow. | Theme and localized content. |
133
+ | Divider | A labelled horizontal separator presents the supplied content. | Label content, theme, and root styling. |
134
+ | Divider with icon | Current output is measured without deciding the ignored-input contract. | No behavior change until OQ1 is resolved. |
135
+ | Empty ReactNode | The default variant keeps `role="status"` and renders content unchanged (empty or one `0`). In the divider variant, the status root and separator remain; empty content has no label/name, while numeric zero renders and names one `0`. | Empty string, numeric zero, empty Fragment. |
136
+ | Narrow/coarse pointer | The noninteractive row remains visible without horizontal page overflow. | Theme and supported content. |
138
137
 
139
138
  ### Transformation and precedence order
140
139
 
@@ -156,10 +155,10 @@ observable public surface without deciding the unresolved divider/icon contract.
156
155
  - **AR1 - Status exposure.** The root currently exposes `role="status"` for both
157
156
  variants. Browser evidence may prove role exposure; spoken announcement timing is
158
157
  outside this draft and requires `spec:AST-009` evidence.
159
- - **AR2 - Divider relationship.** For truthy content, the divider branch delegates a
158
+ - **AR2 - Divider relationship.** Renderable content in the divider branch delegates a
160
159
  horizontal `separator` whose labelled target contains the visible label; focused DOM
161
- tests verify the computed accessible name. Falsey content follows the shared Divider
162
- advisory in AV1.
160
+ tests verify the computed accessible name. Skipped content follows Divider's
161
+ delegated label contract.
163
162
  - **AR3 - Caller content.** Visible content remains the self-contained message.
164
163
  Caller-provided icon content does not replace that text.
165
164
  - **AR4 - Noninteractive surface.** The component introduces no focus target,
@@ -180,8 +179,8 @@ icon artwork, live-region timing, or divider/icon behavior.
180
179
  ## Family and system relationships
181
180
 
182
181
  - `component:Divider` owns the delegated separator structure, role, name,
183
- paint, long-label overflow, and falsey-label behavior. The numeric-zero `00`
184
- output with no accessible name is a shared `spec:AST-002/FR15` advisory.
182
+ paint, long-label overflow, and label partitions. Numeric zero renders as one
183
+ visible label and names the separator `0`; skipped content remains unlabelled.
185
184
  - `architecture:public-component-api` owns released exports, BaseProps reachability,
186
185
  styling composition, and the ref contract.
187
186
  - `architecture:component-theming-surface` owns the target/anatomy mapping and
@@ -197,17 +196,17 @@ icon artwork, live-region timing, or divider/icon behavior.
197
196
 
198
197
  ## Verification map
199
198
 
200
- | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
201
- | ------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
202
- | FR1, AR1, AR3 | `ChatSystemMessage.test.tsx`, scoped axe, and exact-head Chromium receipts | Default, default with icon, long content, nested live log, neutral light/dark | Missing content, status role, icon content, wrapping, or browser-visible subject fails focused or browser evidence. | `audit:ChatSystemMessage/accessibility` |
203
- | FR2, AR2 | `ChatSystemMessage.test.tsx`, Divider tests, scoped axe, and exact-head Chromium receipts | Truthy divider labels plus empty string, numeric zero, and empty Fragment observations | Lost separator role, truthy label association, orientation, content, or paint fails focused or browser evidence; falsey anomalies stay routed to Divider. | `audit:ChatSystemMessage/behavior` |
204
- | FR3 | Closed audit inventory and exact-head observation of the divider/icon partition | Divider with supplied icon | The audit must expose, not silently omit, the unresolved branch; implementation remains unchanged until owner decision. | `audit:ChatSystemMessage/public-api` |
205
- | FR4 | Mutation-sensitive root-passthrough tests, source inspection, type checks, and target reflection | Default and divider roots | Removing ref, className, style, xstyle, ARIA forwarding, or owned role precedence fails focused tests. | `audit:ChatSystemMessage/public-api` |
206
- | FR5 | `themingTargets.test.ts`, source inspection, generated build, and exact-head computed paint | Both variants and color modes | Target/state metadata drift, raw styling, physical layout, or unresolved mode paint fails checks or browser evidence. | `audit:ChatSystemMessage/theming` |
207
- | FR6 | Source inspection, strict lint, and component test suite | Every render | Adding hidden state or an owned external resource triggers runtime review and corresponding evidence. | `audit:ChatSystemMessage/code-health` |
208
- | RTL relation | Source-hashed verified-N/A record | Both variants and caller-owned icon content | Directional component-owned content, physical positioning, ordering, scroll/drag, overlay, or horizontal keyboard behavior invalidates the record. | `audit:ChatSystemMessage/i18n-rtl` |
209
- | Browser matrix | `ChatSystemMessage.a11y.chromium.spec.ts` exact-head 10-sensor receipts | Default, icon, wrapped long content, divider, unresolved divider/icon, nested live log, 320px/coarse pointer, light/dark, D7 pairs | Stale build, wrong story, incorrect mode, missing semantics, default overflow, failed contrast, blank crop, or page error fails closed. | `audit:ChatSystemMessage/visual` |
210
- | Documentation and surface | Consumer docs, published declarations, block metadata, explicit story ownership, exports, and `check:knowledge` | Props, `./Chat` subpath, four blocks, draft record | Missing or stale public docs, export, owner route, target map, or knowledge shape fails validation or review. | `audit:ChatSystemMessage/docs` |
199
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
200
+ | ------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
201
+ | FR1, AR1, AR3 | `ChatSystemMessage.test.tsx`, scoped axe, and exact-head Chromium receipts | Default, default with icon, long content, nested live log, neutral light/dark | Missing content, status role, icon content, wrapping, or browser-visible subject fails focused or browser evidence. | `audit:ChatSystemMessage/accessibility` |
202
+ | FR2, AR2 | `ChatSystemMessage.test.tsx`, Divider tests, scoped axe, and exact-head Chromium receipts | Labelled divider content plus empty string, numeric zero, and empty Fragment observations | Lost separator role, label association, orientation, content, or paint fails focused or browser evidence; skipped partitions stay delegated to Divider. | `audit:ChatSystemMessage/behavior` |
203
+ | FR3 | Closed audit inventory and exact-head observation of the divider/icon partition | Divider with supplied icon | The audit must expose, not silently omit, the unresolved branch; implementation remains unchanged until owner decision. | `audit:ChatSystemMessage/public-api` |
204
+ | FR4 | Mutation-sensitive root-passthrough tests, source inspection, type checks, and target reflection | Default and divider roots | Removing ref, className, style, xstyle, ARIA forwarding, or owned role precedence fails focused tests. | `audit:ChatSystemMessage/public-api` |
205
+ | FR5 | `themingTargets.test.ts`, source inspection, generated build, and exact-head computed paint | Both variants and color modes | Target/state metadata drift, raw styling, physical layout, or unresolved mode paint fails checks or browser evidence. | `audit:ChatSystemMessage/theming` |
206
+ | FR6 | Source inspection, strict lint, and component test suite | Every render | Adding hidden state or an owned external resource triggers runtime review and corresponding evidence. | `audit:ChatSystemMessage/code-health` |
207
+ | RTL relation | Source-hashed verified-N/A record | Both variants and caller-owned icon content | Directional component-owned content, physical positioning, ordering, scroll/drag, overlay, or horizontal keyboard behavior invalidates the record. | `audit:ChatSystemMessage/i18n-rtl` |
208
+ | Browser matrix | `ChatSystemMessage.a11y.chromium.spec.ts` exact-head 10-sensor receipts | Default, icon, wrapped long content, divider, unresolved divider/icon, nested live log, 320px/coarse pointer, light/dark, D7 pairs | Stale build, wrong story, incorrect mode, missing semantics, default overflow, failed contrast, blank crop, or page error fails closed. | `audit:ChatSystemMessage/visual` |
209
+ | Documentation and surface | Consumer docs, published declarations, block metadata, explicit story ownership, exports, and `check:knowledge` | Props, `./Chat` subpath, four blocks, draft record | Missing or stale public docs, export, owner route, target map, or knowledge shape fails validation or review. | `audit:ChatSystemMessage/docs` |
211
210
 
212
211
  ## Decision log
213
212
 
@@ -152,7 +152,7 @@ describe('ChatSystemMessage', () => {
152
152
  expect(root).toContainElement(screen.getByRole('separator'));
153
153
  });
154
154
 
155
- it('records Divider numeric-zero output for the shared FR15 advisory', () => {
155
+ it('keeps the numeric-zero label visible and names the shared divider', () => {
156
156
  render(
157
157
  <ChatSystemMessage variant="divider" data-testid="divider-zero">
158
158
  {0}
@@ -160,8 +160,8 @@ describe('ChatSystemMessage', () => {
160
160
  );
161
161
  const root = screen.getByTestId('divider-zero');
162
162
  const separator = screen.getByRole('separator');
163
- expect(root).toHaveTextContent('00');
164
- expect(separator).not.toHaveAccessibleName();
165
- expect(separator).not.toHaveAttribute('aria-labelledby');
163
+ expect(root).toHaveTextContent(/^0$/);
164
+ expect(separator).toHaveAccessibleName('0');
165
+ expect(separator).toHaveAttribute('aria-labelledby');
166
166
  });
167
167
  });
@@ -40,7 +40,8 @@ export const docs = {
40
40
  {
41
41
  name: 'label',
42
42
  type: 'ReactNode',
43
- description: 'Optional label centered on the divider.',
43
+ description:
44
+ 'Optional label centered on the divider. Numeric zero is preserved; null, booleans, and empty strings render no label.',
44
45
  },
45
46
  {
46
47
  name: 'variant',
@@ -92,7 +93,8 @@ export const docsZh = {
92
93
  {
93
94
  name: 'label',
94
95
  type: 'ReactNode',
95
- description: '居中显示在分隔线上的可选标签。',
96
+ description:
97
+ '居中显示在分隔线上的可选标签。数字 0 会保留;null、布尔值和空字符串不渲染标签。',
96
98
  },
97
99
  {
98
100
  name: 'variant',
@@ -143,7 +145,7 @@ export const docsDense = {
143
145
  },
144
146
  propDescriptions: {
145
147
  orientation: 'divider orientation',
146
- label: 'optional centered label on divider',
148
+ label: 'optional centered label; preserves 0; null/boolean/"" omit it',
147
149
  variant: 'visual weight of divider line',
148
150
  isFullBleed: 'extend to container edges w/ negative margins',
149
151
  xstyle: 'StyleX styles for layout; must be stylex.create() value',