@astryxdesign/core 0.6.2-canary.8112fcc → 0.6.2-canary.8c8f9a2

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,7 +1,8 @@
1
1
  /**
2
2
  * @file InputClearButton.tsx
3
- * @input Uses React, Button, Icon
4
- * @output Exports the public InputClearButton and an internal popup-aware variant.
3
+ * @input Uses React, Button, and Icon
4
+ * @output Exports the public InputClearButton and an internal popup-aware variant,
5
+ * both with a contextual tooltip matching their required label.
5
6
  * @position Shared primitive. Every input that renders a clear affordance —
6
7
  * TextInput, NumberInput, TimeInput, DateInput, DateTimeInput,
7
8
  * DateRangeInput, Selector, MultiSelector, Typeahead, Tokenizer, FileInput —
@@ -12,6 +13,7 @@
12
13
  import type { ReactNode } from 'react';
13
14
  import * as stylex from '@stylexjs/stylex';
14
15
  export interface InputClearButtonProps {
16
+ /** Contextual accessible name for the button, such as "Clear Search". */
15
17
  label: string;
16
18
  onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
17
19
  xstyle?: stylex.StyleXStyles;
@@ -1 +1 @@
1
- {"version":3,"file":"InputClearButton.d.ts","sourceRoot":"","sources":["../../src/Field/InputClearButton.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AACrC,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAkD3C,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC,YAAY,CAAC;IAC7B;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,6BAA8B,SAAQ,qBAAqB;IACnE,aAAa,EAAE,KAAK,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC;IACtD,cAAc,EAAE,KAAK,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;CACtD;AAgDD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,SAAS,CAExE;yBAFe,gBAAgB;;;AAIhC,4EAA4E;AAC5E,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,6BAA6B,GACnC,SAAS,CAEX"}
1
+ {"version":3,"file":"InputClearButton.d.ts","sourceRoot":"","sources":["../../src/Field/InputClearButton.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AACrC,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAkD3C,MAAM,WAAW,qBAAqB;IACpC,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC,YAAY,CAAC;IAC7B;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,6BAA8B,SAAQ,qBAAqB;IACnE,aAAa,EAAE,KAAK,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC;IACtD,cAAc,EAAE,KAAK,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;CACtD;AAiDD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,SAAS,CAExE;yBAFe,gBAAgB;;;AAIhC,4EAA4E;AAC5E,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,6BAA6B,GACnC,SAAS,CAEX"}
@@ -4,8 +4,9 @@
4
4
 
5
5
  /**
6
6
  * @file InputClearButton.tsx
7
- * @input Uses React, Button, Icon
8
- * @output Exports the public InputClearButton and an internal popup-aware variant.
7
+ * @input Uses React, Button, and Icon
8
+ * @output Exports the public InputClearButton and an internal popup-aware variant,
9
+ * both with a contextual tooltip matching their required label.
9
10
  * @position Shared primitive. Every input that renders a clear affordance —
10
11
  * TextInput, NumberInput, TimeInput, DateInput, DateTimeInput,
11
12
  * DateRangeInput, Selector, MultiSelector, Typeahead, Tokenizer, FileInput —
@@ -49,6 +50,7 @@ function renderInputClearButton({
49
50
  variant: "ghost",
50
51
  size: "sm",
51
52
  label: label,
53
+ tooltip: label,
52
54
  className: buttonTargetClassName,
53
55
  icon: /*#__PURE__*/_jsx(Icon, {
54
56
  icon: "close",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/core",
3
- "version": "0.6.2-canary.8112fcc",
3
+ "version": "0.6.2-canary.8c8f9a2",
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",
@@ -687,8 +687,8 @@
687
687
  "react-dom": ">=19.0.0"
688
688
  },
689
689
  "devDependencies": {
690
- "@astryxdesign/a11y-spec": "0.6.2-canary.8112fcc",
691
- "@astryxdesign/cli": "0.6.2-canary.8112fcc",
690
+ "@astryxdesign/a11y-spec": "0.6.2-canary.8c8f9a2",
691
+ "@astryxdesign/cli": "0.6.2-canary.8c8f9a2",
692
692
  "@babel/cli": "^7.29.7",
693
693
  "@babel/core": "^7.29.7",
694
694
  "@babel/preset-react": "^7.29.7",
@@ -3,11 +3,11 @@ schema_version: 3
3
3
  template_version: 5
4
4
  kind: component
5
5
  id: component:ChatComposerDrawer
6
- authority: draft
6
+ authority: current
7
7
  archive_reason: null
8
8
  superseded_by: null
9
- approved_by: null
10
- approved_at: null
9
+ approved_by: cixzhang
10
+ approved_at: 2026-09-21
11
11
  owners: [cixzhang]
12
12
  review_triggers: [public-api, behavior, layout, theming, accessibility]
13
13
  verified_by:
@@ -24,24 +24,25 @@ architecture:
24
24
  architecture:component-theming-surface,
25
25
  architecture:interaction-modality,
26
26
  architecture:knowledge-contracts,
27
+ architecture:public-component-api,
27
28
  ]
28
- contributing: []
29
- system_specs: [spec:AST-002, spec:AST-029]
29
+ contributing: [contributing:api-conventions]
30
+ system_specs: [spec:AST-002/DEC-1, spec:AST-002/DEC-2]
30
31
  ---
31
32
 
32
33
  # ChatComposerDrawer component contract
33
34
 
34
35
  ## Contract at a glance
35
36
 
36
- | Area | Contract |
37
- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
38
- | Public contract | No API or default change. ChatComposerDrawer remains a required-content panel whose optional `count` enables a controlled or uncontrolled disclosure. |
39
- | Behavior | The disclosure reports expanded state and its controlled region, preserves mounted content for animation, and makes collapsed descendants unavailable to keyboard and assistive-technology navigation. |
40
- | End-user impact | Keyboard and screen-reader users do not enter visually hidden drawer actions, and keyboard focus remains visible on the disclosure control. |
41
- | Builder impact | None. Existing props, defaults, exports, child composition, and callback values remain unchanged. |
42
- | Compatibility/readiness | Patch-compatible accessibility correction to released behavior. This observational record remains draft; collapsed-summary representation is not decided here. |
43
- | Review checks | Reject hidden focusable descendants, an invisible disclosure focus state, broken `aria-controls`, changed callback values, or any claim that the collapsed-summary representation is settled. |
44
- | Governing rules | `architecture:interaction-modality/INV1–INV4, INV9`; `architecture:knowledge-contracts/INV11, INV16`; `spec:AST-002/FR14, FR19`; WCAG 2.4.3 and 2.4.7. |
37
+ | Area | Contract |
38
+ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
39
+ | Public contract | Add one optional `collapsedSummary?: ReactNode` slot, named from the canonical **Collapsed summary** anatomy part, for caller-composed visual content. |
40
+ | Behavior | `count` continues to enable the disclosure. Omitting `collapsedSummary` keeps the neutral Badge count plus resolved label; providing it replaces that complete visual summary without changing collapse behavior or naming. |
41
+ | End-user impact | People retain the established Badge hierarchy by default. Products that need a quieter or differently composed collapsed row may provide one localized visual summary without weakening disclosure semantics. |
42
+ | Builder impact | Existing callsites do not change. A builder choosing the slot owns its visual content and localization, while `label` continues to identify the controlled content for the component-owned accessible name. |
43
+ | Compatibility/readiness | Additive and default-preserving. This record is current authority; the slot and its mapped implementation, consumer docs, tests, and rendered evidence have not shipped yet. |
44
+ | Review checks | Reject a plain-text default, a second summary API, custom content that changes naming or collapse state, reachable interactive descendants inside the visual summary, or any regression to the no-slot and no-count paths. |
45
+ | Governing rules | `component:ChatComposerDrawer/FR1–FR10, AR1–AR4, DEC-1–DEC-2`; `architecture:public-component-api/INV3–INV4, INV9, INV13`; `spec:AST-002/DEC-1, DEC-2`. |
45
46
 
46
47
  This table is a review projection; the body below is authoritative.
47
48
 
@@ -51,113 +52,216 @@ ChatComposerDrawer composes supplementary content above ChatComposer input. When
51
52
  `count` is present, builders may let people collapse that content while keeping a
52
53
  compact summary available.
53
54
 
55
+ Two otherwise identical drawers may need different visual hierarchy in the
56
+ collapsed state. The product builder knows whether the established count Badge or
57
+ a quieter, product-specific summary fits that context, and ChatComposerDrawer
58
+ cannot derive that distinction from count, content, layout, locale, or collapse
59
+ state. The component therefore exposes one visual composition seam while retaining
60
+ its existing default and owning all disclosure behavior.
61
+
54
62
  ## Compatibility and migration
55
63
 
56
- - Released default preserved: yes
57
- - Compatibility class: patch-compatible behavior correction; no public type or default changes
58
- - Controlled/uncontrolled behavior: unchanged
59
- - Migration decision: none
64
+ - Released default preserved: yes. Omitting `collapsedSummary` keeps the neutral
65
+ count Badge followed by the resolved label.
66
+ - Compatibility class: additive optional composition slot with no default change.
67
+ - Controlled/uncontrolled behavior: unchanged.
68
+ - Migration decision: none. Existing callsites require no edits.
69
+
70
+ Consumer migration instructions belong in consumer docs and release notes.
60
71
 
61
72
  ## Ownership boundary
62
73
 
63
74
  **Owns**
64
75
 
65
- - The drawer surface, optional disclosure state, disclosure association, and availability of collapsed descendants.
76
+ - The drawer surface, optional disclosure state, disclosure association, and
77
+ availability of collapsed descendants.
66
78
  - Controlled and uncontrolled collapse requests.
79
+ - The Badge-preserving default summary and the presentation-only boundary for one
80
+ caller-composed collapsed summary.
81
+ - The disclosure's localized accessible name, independently of visual summary
82
+ content.
67
83
 
68
84
  **Does not own / non-goals**
69
85
 
70
- - The meaning or behavior of arbitrary caller children.
86
+ - The meaning, styling, or localization of caller-provided drawer children or custom
87
+ summary content.
88
+ - Interactive behavior inside the custom summary; the summary is visual-only and
89
+ never creates another focus or activation target.
71
90
  - ChatComposer input, submission, status, and action behavior.
72
- - The final visual representation of the collapsed count-and-label summary; this draft records only shipped observation.
91
+ - A presentation enum, Badge toggle, render callback, summary-specific theme target,
92
+ or a second accessible-name prop.
73
93
 
74
94
  ## Public concepts
75
95
 
76
- | Concept | Closed values or states | Meaning | Availability by state | Default | Owner | Stability | Invalid-value behavior |
77
- | ---------------- | -------------------------------- | ------------------------------------------------------------------------ | ----------------------------------- | ------------ | ------------------------------ | --------- | ---------------------------------------------------- |
78
- | drawer content | any React node | Supplementary content composed above the chat input. | always | none | `component:ChatComposerDrawer` | stable | Missing content is rejected by the required type. |
79
- | collapse support | unavailable, expanded, collapsed | `count` enables a disclosure and supplies its summary count. | unavailable when `count` is omitted | unavailable | `component:ChatComposerDrawer` | stable | Collapse props have no visible effect without count. |
80
- | collapse control | uncontrolled, controlled | Internal state or `isCollapsed` determines the current disclosure state. | when collapse support is available | uncontrolled | `component:ChatComposerDrawer` | stable | Controlled state wins over the initial default. |
81
- | collapse request | next state | Activation asks for the opposite effective state. | when collapse support is available | none | `component:ChatComposerDrawer` | stable | No request is emitted without a disclosure. |
96
+ | Concept | Closed values or states | Meaning | Availability by state | Default | Owner | Stability | Invalid-value behavior |
97
+ | ----------------- | -------------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------- | ------------------------ | ------------------------------ | ------------------- | ------------------------------------------------------------------ |
98
+ | drawer content | any React node | Supplementary content composed above the chat input. | always | none | `component:ChatComposerDrawer` | stable | Missing content is rejected by the required type. |
99
+ | collapse support | unavailable, expanded, collapsed | Presence of `count`, including zero, enables one disclosure and supplies its summary count. | unavailable when `count` is omitted | unavailable | `component:ChatComposerDrawer` | stable | Collapse inputs have no visible effect without `count`. |
100
+ | collapse control | uncontrolled, controlled | Internal state or `isCollapsed` determines the current disclosure state. | when collapse support is available | uncontrolled | `component:ChatComposerDrawer` | stable | Controlled state wins over the initial default. |
101
+ | collapsed summary | default, caller-composed | Visual content shown for the collapsed disclosure. | when collapse support is available | neutral Badge plus label | caller choice; component seam | accepted, unshipped | Omission uses the default; without `count`, no summary is exposed. |
102
+ | collapse request | next state | Activation asks for the opposite effective collapsed state. | when collapse support is available | none | `component:ChatComposerDrawer` | stable | No request is emitted without a disclosure. |
103
+
104
+ `collapsedSummary` is the camelCase public API projection of the canonical
105
+ **Collapsed summary** anatomy part. It is the complete custom visual summary, not
106
+ an addition before or after the default. The component does not parse, clone,
107
+ decorate, or supply data to the node. Builders already own the values they pass as
108
+ `count` and `label`; when a custom summary forms a sentence or formats a number,
109
+ its callsite owns the complete localized message.
82
110
 
83
111
  ## Behavioral and layout contract
84
112
 
85
- | ID | Candidate invariant | Basis | Draft review state |
86
- | --- | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------ |
87
- | FR1 | When `count` is present, the component MUST expose one named disclosure control with `aria-expanded` and a resolvable `aria-controls` relationship. | Shipped API and implementation; WAI-ARIA disclosure semantics. | verify |
88
- | FR2 | Pointer activation, Enter, and Space MUST request the opposite effective collapsed state exactly once. | Shipped implementation and public callback contract. | verify |
89
- | FR3 | In controlled mode, `isCollapsed` MUST remain authoritative; in uncontrolled mode, activation MUST update internal state before the next render. | Shipped implementation and consumer docs. | verify |
90
- | FR4 | Collapsed content MAY remain mounted for the grid transition, but its descendants MUST be absent from keyboard and assistive-technology navigation. | WCAG 2.4.3 and 2.4.7; objective restoration of the shipped visually hidden state. | settled |
91
- | FR5 | Keyboard focus on the disclosure MUST remain visibly perceivable through the shared focus-indicator mechanism. | `architecture:interaction-modality/INV1–INV4`; WCAG 2.4.7. | settled |
92
- | FR6 | Omitting `count` MUST render content directly and MUST NOT create a disclosure control. | Shipped implementation and consumer docs. | verify |
93
- | FR7 | The root MUST continue forwarding its ref, BaseProps attributes, and styling seams without moving them to an internal child. | Released public surface and `architecture:knowledge-contracts/INV11`. | verify |
113
+ | ID | Invariant | Basis |
114
+ | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
115
+ | FR1 | When `count` is present, including when it is `0`, the component MUST expose one named disclosure control with `aria-expanded` and a resolvable `aria-controls` relationship. | released API and implementation; WAI-ARIA disclosure semantics |
116
+ | FR2 | Pointer activation, Enter, and Space MUST request the opposite effective collapsed state exactly once. | released implementation and public callback contract |
117
+ | FR3 | In controlled mode, `isCollapsed` MUST remain authoritative; in uncontrolled mode, activation MUST update internal state before the next render. | released implementation and consumer docs |
118
+ | FR4 | Collapsed drawer content MAY remain mounted for the grid transition, but its descendants MUST be absent from keyboard and assistive-technology navigation. | WAI-ARIA disclosure semantics; WCAG 2.4.3 and 2.4.7 |
119
+ | FR5 | Keyboard focus on the disclosure MUST remain visibly perceivable through the shared focus-indicator mechanism. | `architecture:interaction-modality/INV1–INV4` |
120
+ | FR6 | Omitting `count` MUST render drawer content directly, MUST NOT create a disclosure control, and MUST NOT expose `collapsedSummary`. Existing collapse inputs continue to have no visible effect in this state. | released no-count behavior and compatibility |
121
+ | FR7 | The root MUST continue forwarding its ref, BaseProps attributes, and styling seams without moving them to an internal child. | released public surface and `architecture:public-component-api/INV5–INV6, INV8–INV9` |
122
+ | FR8 | `collapsedSummary` MUST remain the camelCase public name of the canonical **Collapsed summary** anatomy part. When omitted, the collapsed disclosure MUST preserve the existing neutral Badge containing `count`, followed by the resolved `label`; when provided, the node MUST replace that complete anatomy part exactly once. | approved anatomy-aligned caller-composition boundary and released default |
123
+ | FR9 | The custom summary MUST affect visual content only. It MUST NOT change whether collapse is available, effective collapse state, toggle activation, callback values, the controlled region, or the component-owned accessible name. | constrained slot contract and `spec:AST-002/DEC-2` |
124
+ | FR10 | The summary subtree MUST remain presentation-only: the disclosure is the sole focus and activation target, summary descendants MUST be absent from the tab order and accessibility tree, and custom content MUST NOT add another action. | WAI-ARIA disclosure semantics and component-owned accessibility boundary |
94
125
 
95
126
  ### Allowed variation
96
127
 
97
- - **AV1 — Child composition.** Builders choose the child content and its own semantics.
98
- - **AV2 — Collapsed summary paint.** The current count-and-label presentation is observation, not an approved durable representation.
128
+ - **AV1 — Drawer child composition.** Builders choose the drawer content and its own
129
+ semantics.
130
+ - **AV2 — Custom summary composition.** Builders may compose any visual content that
131
+ fits the presentation-only boundary, including localized text or existing Astryx
132
+ display components.
133
+ - **AV3 — Summary lifecycle.** The component may keep the default or custom summary
134
+ mounted while visually expanded to preserve animation. Callers MUST NOT depend on
135
+ mount or unmount timing.
136
+ - **AV4 — Custom summary styling.** Caller-composed content retains the styling and
137
+ theme seams of the components it uses. ChatComposerDrawer adds no summary-specific
138
+ target.
99
139
 
100
140
  ### Representative states
101
141
 
102
- | State | Required invariant | Allowed variation |
103
- | ----------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------- |
104
- | count omitted | Content is visible and no disclosure is rendered. | Child content and root styling. |
105
- | uncontrolled expanded | Disclosure is expanded and descendants are available. | Child content and count value. |
106
- | uncontrolled collapsed | Disclosure is collapsed and mounted descendants are inert. | Current summary presentation. |
107
- | controlled expanded/collapsed | Rendered state follows `isCollapsed`; activation reports the requested next state. | Parent timing and whether it accepts the request. |
142
+ | State | Required invariant | Allowed variation |
143
+ | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
144
+ | `count` omitted | Content is visible; no disclosure or summary is exposed. | Child content and root styling. |
145
+ | default summary, collapsed | One disclosure shows the neutral count Badge plus resolved label. | Count value and localized label. |
146
+ | custom summary, collapsed | One disclosure shows the caller node instead of both default summary parts. | Presentation-only custom content and styling. |
147
+ | custom summary, expanded | The disclosure retains its state and name; the custom summary may stay mounted but is visually and semantically hidden. | Internal crossfade and mount timing. |
148
+ | controlled expanded or collapsed | Rendered state follows `isCollapsed`; activation reports the requested next state. | Parent timing and whether it accepts the request. |
108
149
 
109
150
  ### Transformation and precedence order
110
151
 
111
- - **ORD1 — Effective state.** `isCollapsed` when defined → otherwise internal state initialized from `defaultIsCollapsed` → disclosure output only when `count` is present.
152
+ - **ORD1 — Collapse availability.** `count` present → disclosure enabled; `count`
153
+ omitted → no disclosure or collapsed summary.
154
+ - **ORD2 — Effective state.** `isCollapsed` when defined → otherwise internal state
155
+ initialized from `defaultIsCollapsed`.
156
+ - **ORD3 — Visual summary.** Caller-provided `collapsedSummary` → otherwise the
157
+ neutral Badge for `count` followed by the resolved `label`.
158
+ - **ORD4 — Accessible name.** Resolve `label` from the prop or locale catalog → use
159
+ the catalog's expand or collapse action message for the effective state. Visual
160
+ summary content never enters or overrides this pipeline.
112
161
 
113
162
  ### Performance and resources
114
163
 
115
- - **PR1 — Local state only.** The component owns no Effect, observer, listener, timer, or external resource.
164
+ - **PR1 — Local state only.** The summary seam MUST NOT require an Effect, observer,
165
+ listener, timer, or external resource.
116
166
 
117
167
  ## Accessibility contract
118
168
 
119
- - **AR1 — Disclosure relationship.** The toggle exposes a name, expanded state, and a resolvable controlled-content ID.
120
- - **AR2 — Hidden content is unavailable.** A visually collapsed mounted subtree is inert until expansion.
121
- - **AR3 — Focus remains visible.** Keyboard focus on the toggle uses the shared focus indicator.
169
+ - **AR1 — Disclosure relationship.** The toggle exposes one localized name, expanded
170
+ state, and a resolvable controlled-content ID.
171
+ - **AR2 — Naming stays component-owned.** `label` identifies the controlled drawer
172
+ content in the expand/collapse catalog message. Default or custom visual summary
173
+ text MUST NOT replace, duplicate, or concatenate into that accessible name.
174
+ - **AR3 — Hidden content is unavailable.** Visually collapsed drawer descendants are
175
+ unavailable until expansion.
176
+ - **AR4 — Custom summary remains presentation-only.** Custom content cannot add a
177
+ second focus stop, activation target, role, or announcement inside the disclosure.
122
178
 
123
179
  ## Design relationships
124
180
 
125
- | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
126
- | ----------------- | ------------------ | ------------------------ | -------------- | ------------------ |
127
- | root surface | unsettled | human-selected | supporting | FR7 |
128
- | disclosure toggle | unsettled | human-selected | supporting | FR1, FR5 |
129
- | collapsed summary | unsettled | human-selected | supporting | AV2 |
130
- | content area | unsettled | human-selected | supporting | FR4, FR6 |
181
+ No current design specification governs this component-local representation.
182
+
183
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
184
+ | ----------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------ | -------------- | ------------------ |
185
+ | root surface | Existing released surface remains unchanged by this decision. | released behavior | supporting | FR7 |
186
+ | disclosure toggle | One operable, named control owns collapse interaction. | component-owned | supporting | FR1–FR5, AR1 |
187
+ | collapsed summary | Badge hierarchy remains the default; callers may choose one visual composition slot. | component default or caller-selected composition | supporting | FR8–FR10, AR2–AR4 |
188
+ | content area | Collapsed descendants remain unavailable to keyboard and assistive technology. | component-owned | supporting | FR4, FR6, AR3 |
131
189
 
132
- The existing `chat-composer-drawer` target remains on the painted root. This
133
- observational draft does not add targets or decide a separate theming boundary for
134
- the disclosure, summary, or content area.
190
+ The existing `chat-composer-drawer` target remains on the painted root. The
191
+ Badge-preserving default delegates count paint to Badge. Caller-composed summary
192
+ content retains the theme ownership of the components it supplies. This decision
193
+ adds no ChatComposerDrawer summary target and does not make arbitrary slot internals
194
+ part of ChatComposerDrawer's theming API.
135
195
 
136
196
  ## Family and system relationships
137
197
 
138
- - `architecture:interaction-modality` owns shared focus-visibility mechanics; this component owns the disclosure as its semantic focus owner.
139
- - `architecture:component-theming-surface` owns target and anatomy mapping rules; this component exposes only the existing root target.
140
- - `spec:AST-029` owns the observational audit-backfill boundary.
198
+ - `architecture:public-component-api` owns reachability, naming grammar, and
199
+ compatibility of the additive slot; this component owns its local meaning and
200
+ default.
201
+ - `architecture:interaction-modality` owns shared focus-visibility mechanics; this
202
+ component owns the disclosure as its semantic focus owner.
203
+ - `architecture:component-theming-surface` owns target and anatomy mapping rules; the
204
+ custom summary does not create a new target or transfer ownership of caller
205
+ content.
206
+ - `architecture:knowledge-contracts` limits this current authority to the explicit
207
+ claims in this record.
208
+ - `spec:AST-002/DEC-1` admits the slot because otherwise-identical drawers may need
209
+ a caller-owned visual hierarchy the component cannot derive. `DEC-2` requires the
210
+ seam to preserve a dependable, accessibility-safe outcome.
141
211
 
142
212
  ## Verification map
143
213
 
144
- | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
145
- | ------------ | --------------------------------------------------------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | ---------------------------------------- |
146
- | FR1–FR3, FR6 | `ChatComposerDrawer.test.tsx` | no count; controlled; uncontrolled; Enter; Space | Relationship breaks, state authority changes, or callback reports the wrong next state. | `audit:ChatComposerDrawer/behavior` |
147
- | FR4, AR2 | focused inert-state test plus exact-head accessibility CI | expanded → collapsed → expanded with focusable child | A visually hidden child remains reachable or expansion fails to restore availability. | `audit:ChatComposerDrawer/accessibility` |
148
- | FR5, AR3 | `KeyboardFocus` Storybook play and exact-head visual/a11y gates | keyboard focus in representative light and dark themes | Focus has no shared visible indicator or pointer interaction changes the keyboard-only outcome. | `audit:ChatComposerDrawer/accessibility` |
149
- | FR7 | focused DOM/type tests and package checks | ref, BaseProps, xstyle, className, style | Root ownership or a styling seam moves or disappears. | `audit:ChatComposerDrawer/api` |
214
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
215
+ | ------------------------ | ----------------------------------------------------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
216
+ | FR1–FR3, FR6, ORD1–2 | focused ChatComposerDrawer state and keyboard tests | no count; zero count; controlled; uncontrolled; pointer; Enter; Space | Presence/truthiness changes collapse availability, state authority changes, or callback reports the wrong next state. | `audit:ChatComposerDrawer/behavior` |
217
+ | FR8–FR9, ORD3 | focused default/custom summary tests and consumer-doc projection checks | default; custom node; expanded; collapsed; no count | The default Badge disappears, default and custom content render together, custom content changes behavior, or no-count exposes it. | `audit:ChatComposerDrawer/api` |
218
+ | FR4, FR10, AR2–AR4 | focused inert-state and custom-descendant tests plus exact-head a11y CI | default text; custom text; custom focusable descendant; collapsed/expanded | Hidden drawer content or custom summary becomes focusable, announced, or a second action; visual text replaces the catalog name. | `audit:ChatComposerDrawer/accessibility` |
219
+ | FR5, AR1 | `KeyboardFocus` Storybook play and exact-head visual/a11y gates | keyboard focus in representative light and dark themes | Focus has no shared visible indicator or the disclosure relationship/name breaks. | `audit:ChatComposerDrawer/accessibility` |
220
+ | FR7 | focused DOM/type tests and package checks | ref, BaseProps, xstyle, className, style | Root ownership or a styling seam moves or disappears. | `audit:ChatComposerDrawer/api` |
221
+ | AV4 and theming boundary | source/runtime theme inspection of default and custom summaries | default Badge; caller-composed Astryx content | A new summary target appears, Badge loses its target, or caller-owned content is retargeted as ChatComposerDrawer anatomy. | `audit:ChatComposerDrawer/theming` |
150
222
 
151
223
  ## Decision log
152
224
 
153
- No component-local policy decision is created by this observational draft.
225
+ ### DEC-1 — One visual slot preserves the Badge default
226
+
227
+ **Reference:** `component:ChatComposerDrawer/DEC-1`
228
+ **Decider:** `cixzhang`, `2026-09-21`
229
+
230
+ ChatComposerDrawer admits one optional `collapsedSummary` node for the complete
231
+ visual summary. Product builders own whether a particular collapsed row needs the
232
+ established Badge hierarchy or a quieter localized composition, and the component
233
+ cannot derive that visual hierarchy. Omitting the slot preserves the existing
234
+ neutral Badge plus label, so no current builder receives a new choice or migration.
235
+
236
+ Rejected: replacing the default with a `{count} {label}` sentence, adding a Badge
237
+ boolean or finite presentation enum, adding a render callback for values already
238
+ owned by the caller, or exposing parallel summary seams. Those shapes either break
239
+ the released default, encode presentation machinery, invite locale-unsafe string
240
+ concatenation, or add more API than the caller-owned distinction requires.
241
+
242
+ ### DEC-2 — Visual composition does not own disclosure semantics
243
+
244
+ **Reference:** `component:ChatComposerDrawer/DEC-2`
245
+ **Decider:** `cixzhang`, `2026-09-21`
246
+
247
+ The custom summary is presentation-only. The existing `label` and locale catalog
248
+ continue to name the expand/collapse action; `count` continues to enable the
249
+ disclosure; and the component keeps summary descendants out of focus, activation,
250
+ and assistive-technology navigation. The seam cannot replace these obligations.
251
+
252
+ Rejected: deriving the accessible name from arbitrary summary content or letting a
253
+ custom summary introduce another action inside the disclosure. Both make correct
254
+ semantics depend on caller markup the component cannot verify.
154
255
 
155
256
  ## Open questions
156
257
 
157
- - **OQ1 — Collapsed-summary representation.** Should the existing count badge remain, or should the summary use another representation? (`human-design`)
258
+ None for this contract slice.
158
259
 
159
260
  ## Content boundary
160
261
 
161
- This file does not duplicate consumer prop tables, examples, current audit scores,
162
- or visual design rationale. It records observed behavior, objective remediation,
163
- and the unresolved representation boundary without deciding it.
262
+ This file owns ChatComposerDrawer's collapsed-summary composition boundary,
263
+ Badge-preserving default, collapse gating and state behavior, accessible naming,
264
+ compatibility, and required evidence. It does not duplicate the complete consumer
265
+ prop reference, implementation steps, current audit score, or unrelated component
266
+ gaps. Current authority is claim-scoped to the explicit requirements above and does
267
+ not certify every ChatComposerDrawer behavior.
@@ -8,8 +8,8 @@
8
8
  * @output Unit tests for the shared clear-button primitive
9
9
  * @position Testing; validates InputClearButton.tsx — the single home for the
10
10
  * clearable input family's clear (✕) affordance and its theme target. Covers
11
- * the accessible name, the click callback, the decorative registry glyph,
12
- * both theme targets, the touch hit area, and xstyle forwarding.
11
+ * the accessible name, contextual hover tooltip, click callback, decorative
12
+ * registry glyph, both theme targets, touch hit area, and xstyle forwarding.
13
13
  *
14
14
  * SYNC: When InputClearButton.tsx changes, update tests to match new behavior.
15
15
  */
@@ -17,7 +17,7 @@
17
17
  import {readFileSync} from 'node:fs';
18
18
  import path from 'node:path';
19
19
  import {describe, it, expect, vi, afterEach} from 'vitest';
20
- import {render, screen, fireEvent} from '@testing-library/react';
20
+ import {render, screen, fireEvent, waitFor} from '@testing-library/react';
21
21
  import * as stylex from '@stylexjs/stylex';
22
22
  import {
23
23
  InputClearButton,
@@ -95,6 +95,20 @@ describe('InputClearButton public props', () => {
95
95
  });
96
96
 
97
97
  describe('InputClearButton', () => {
98
+ it('uses the contextual label for the tooltip and accessible name', async () => {
99
+ const showPopover = vi.spyOn(HTMLElement.prototype, 'showPopover');
100
+ render(<InputClearButton label="Clear Search" onClick={() => {}} />);
101
+
102
+ const button = screen.getByRole('button', {name: 'Clear Search'});
103
+ const tooltip = screen.getByRole('tooltip', {hidden: true});
104
+ expect(tooltip).toHaveTextContent(/^Clear Search$/);
105
+ expect(button.getAttribute('aria-describedby')).toBe(tooltip.id);
106
+
107
+ fireEvent.mouseEnter(button);
108
+ await waitFor(() => expect(showPopover).toHaveBeenCalled());
109
+ showPopover.mockRestore();
110
+ });
111
+
98
112
  it('renders a real button with the given accessible label', () => {
99
113
  render(<InputClearButton label="Clear" onClick={() => {}} />);
100
114
  const button = screen.getByRole('button', {name: 'Clear'});
@@ -4,8 +4,9 @@
4
4
 
5
5
  /**
6
6
  * @file InputClearButton.tsx
7
- * @input Uses React, Button, Icon
8
- * @output Exports the public InputClearButton and an internal popup-aware variant.
7
+ * @input Uses React, Button, and Icon
8
+ * @output Exports the public InputClearButton and an internal popup-aware variant,
9
+ * both with a contextual tooltip matching their required label.
9
10
  * @position Shared primitive. Every input that renders a clear affordance —
10
11
  * TextInput, NumberInput, TimeInput, DateInput, DateTimeInput,
11
12
  * DateRangeInput, Selector, MultiSelector, Typeahead, Tokenizer, FileInput —
@@ -66,6 +67,7 @@ const styles = stylex.create({
66
67
  });
67
68
 
68
69
  export interface InputClearButtonProps {
70
+ /** Contextual accessible name for the button, such as "Clear Search". */
69
71
  label: string;
70
72
  onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
71
73
  xstyle?: stylex.StyleXStyles;
@@ -104,6 +106,7 @@ function renderInputClearButton({
104
106
  variant="ghost"
105
107
  size="sm"
106
108
  label={label}
109
+ tooltip={label}
107
110
  className={buttonTargetClassName}
108
111
  icon={
109
112
  <Icon