@astryxdesign/core 0.7.6-canary.eecd9c1 → 0.7.6-canary.f73623d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +28 -19
- package/dist/Outline/Outline.d.ts.map +1 -1
- package/dist/Outline/Outline.js +27 -0
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +10 -3
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +5 -2
- package/dist/Table/Table.d.ts.map +1 -1
- package/dist/Table/Table.js +36 -10
- package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +46 -58
- package/dist/ToggleButton/ToggleButtonGroup.d.ts +7 -1
- package/dist/ToggleButton/ToggleButtonGroup.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButtonGroup.js +16 -2
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +37 -5
- package/dist/Token/TokenLink.d.ts.map +1 -1
- package/dist/Token/TokenLink.js +14 -2
- package/dist/i18n/generated-locales/af-ZA.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/af-ZA.generated.js +0 -5
- package/dist/i18n/generated-locales/ar-SA.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/ar-SA.generated.js +0 -5
- package/dist/i18n/generated-locales/ca-ES.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/ca-ES.generated.js +0 -5
- package/dist/i18n/generated-locales/cs-CZ.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/cs-CZ.generated.js +0 -5
- package/dist/i18n/generated-locales/da-DK.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/da-DK.generated.js +0 -5
- package/dist/i18n/generated-locales/de-DE.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/de-DE.generated.js +0 -5
- package/dist/i18n/generated-locales/el-GR.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/el-GR.generated.js +0 -5
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +1 -5
- package/dist/i18n/generated-locales/es-ES.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/es-ES.generated.js +0 -5
- package/dist/i18n/generated-locales/fi-FI.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/fi-FI.generated.js +0 -5
- package/dist/i18n/generated-locales/fr-FR.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/fr-FR.generated.js +0 -5
- package/dist/i18n/generated-locales/he-IL.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/he-IL.generated.js +0 -5
- package/dist/i18n/generated-locales/hu-HU.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/hu-HU.generated.js +0 -5
- package/dist/i18n/generated-locales/it-IT.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/it-IT.generated.js +0 -5
- package/dist/i18n/generated-locales/ja-JP.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/ja-JP.generated.js +0 -5
- package/dist/i18n/generated-locales/ko-KR.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/ko-KR.generated.js +0 -5
- package/dist/i18n/generated-locales/nl-NL.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/nl-NL.generated.js +0 -5
- package/dist/i18n/generated-locales/no-NO.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/no-NO.generated.js +0 -5
- package/dist/i18n/generated-locales/pl-PL.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pl-PL.generated.js +0 -5
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +1 -5
- package/dist/i18n/generated-locales/pt-BR.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pt-BR.generated.js +0 -5
- package/dist/i18n/generated-locales/pt-PT.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pt-PT.generated.js +0 -5
- package/dist/i18n/generated-locales/ro-RO.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/ro-RO.generated.js +0 -5
- package/dist/i18n/generated-locales/ru-RU.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/ru-RU.generated.js +0 -5
- package/dist/i18n/generated-locales/sr-SP.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/sr-SP.generated.js +0 -5
- package/dist/i18n/generated-locales/sv-SE.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/sv-SE.generated.js +0 -5
- package/dist/i18n/generated-locales/tr-TR.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/tr-TR.generated.js +0 -5
- package/dist/i18n/generated-locales/uk-UA.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/uk-UA.generated.js +0 -5
- package/dist/i18n/generated-locales/vi-VN.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/vi-VN.generated.js +0 -5
- package/dist/i18n/generated-locales/zh-CN.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/zh-CN.generated.js +0 -5
- package/dist/i18n/generated-locales/zh-TW.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/zh-TW.generated.js +0 -5
- package/locales/af-ZA.json +0 -21
- package/locales/ar-SA.json +0 -21
- package/locales/ca-ES.json +0 -21
- package/locales/cs-CZ.json +0 -21
- package/locales/da-DK.json +0 -21
- package/locales/de-DE.json +0 -21
- package/locales/el-GR.json +0 -21
- package/locales/en.json +3 -19
- package/locales/es-ES.json +0 -21
- package/locales/fi-FI.json +0 -21
- package/locales/fr-FR.json +0 -21
- package/locales/he-IL.json +0 -21
- package/locales/hu-HU.json +0 -21
- package/locales/it-IT.json +0 -21
- package/locales/ja-JP.json +0 -21
- package/locales/ko-KR.json +0 -21
- package/locales/nl-NL.json +0 -21
- package/locales/no-NO.json +0 -21
- package/locales/pl-PL.json +0 -21
- package/locales/pseudo.json +2 -14
- package/locales/pt-BR.json +0 -21
- package/locales/pt-PT.json +0 -21
- package/locales/ro-RO.json +0 -21
- package/locales/ru-RU.json +0 -21
- package/locales/sr-SP.json +0 -21
- package/locales/sv-SE.json +0 -21
- package/locales/tr-TR.json +0 -21
- package/locales/uk-UA.json +0 -21
- package/locales/vi-VN.json +0 -21
- package/locales/zh-CN.json +0 -21
- package/locales/zh-TW.json +0 -21
- package/package.json +4 -4
- package/src/Chat/ChatLayout.spec.md +41 -36
- package/src/Chat/ChatLayoutScrollButton.spec.md +87 -103
- package/src/Chat/ChatSendButton.spec.md +78 -67
- package/src/ContextMenu/ContextMenu.spec.md +84 -56
- package/src/ContextMenu/ContextMenuItem.spec.md +132 -0
- package/src/DropdownMenu/DropdownMenu.spec.md +198 -218
- package/src/DropdownMenu/DropdownMenu.test.tsx +117 -0
- package/src/DropdownMenu/DropdownMenu.tsx +33 -18
- package/src/DropdownMenu/DropdownMenuCheckboxItem.spec.md +192 -0
- package/src/DropdownMenu/DropdownMenuDivider.spec.md +25 -31
- package/src/DropdownMenu/DropdownMenuGroup.spec.md +145 -0
- package/src/DropdownMenu/DropdownMenuItem.spec.md +48 -42
- package/src/DropdownMenu/DropdownMenuRadioGroup.spec.md +34 -37
- package/src/DropdownMenu/DropdownMenuRadioItem.spec.md +54 -53
- package/src/DropdownMenu/DropdownMenuSubMenu.spec.md +47 -41
- package/src/EmptyState/EmptyState.spec.md +35 -29
- package/src/Outline/Outline.spec.md +165 -69
- package/src/Outline/Outline.test.tsx +67 -2
- package/src/Outline/Outline.tsx +23 -0
- package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +3 -4
- package/src/Popover/Popover.test.tsx +44 -0
- package/src/Popover/usePopover.tsx +10 -3
- package/src/RadioList/RadioList.test.tsx +56 -0
- package/src/RadioList/RadioListItem.tsx +5 -2
- package/src/Table/Table.spec.md +1 -1
- package/src/Table/Table.test.tsx +32 -0
- package/src/Table/Table.tsx +43 -11
- package/src/Table/__tests__/Table.a11y.states.ts +1 -5
- package/src/Table/plugins/sortable/useTableSortable.test.tsx +73 -144
- package/src/Table/plugins/sortable/useTableSortable.tsx +44 -73
- package/src/Table/plugins/sortable/useTableSortableState.test.tsx +9 -21
- package/src/ToggleButton/ToggleButton.test.tsx +27 -0
- package/src/ToggleButton/ToggleButtonGroup.tsx +20 -1
- package/src/Token/Token.doc.mjs +2 -2
- package/src/Token/Token.spec.md +41 -51
- package/src/Token/Token.test.tsx +67 -0
- package/src/Token/Token.tsx +60 -5
- package/src/Token/TokenLink.tsx +21 -7
- package/src/i18n/generated-locales/af-ZA.generated.ts +0 -7
- package/src/i18n/generated-locales/ar-SA.generated.ts +0 -6
- package/src/i18n/generated-locales/ca-ES.generated.ts +0 -7
- package/src/i18n/generated-locales/cs-CZ.generated.ts +0 -6
- package/src/i18n/generated-locales/da-DK.generated.ts +0 -6
- package/src/i18n/generated-locales/de-DE.generated.ts +0 -6
- package/src/i18n/generated-locales/el-GR.generated.ts +0 -7
- package/src/i18n/generated-locales/en.generated.ts +1 -6
- package/src/i18n/generated-locales/es-ES.generated.ts +0 -7
- package/src/i18n/generated-locales/fi-FI.generated.ts +0 -6
- package/src/i18n/generated-locales/fr-FR.generated.ts +0 -7
- package/src/i18n/generated-locales/he-IL.generated.ts +0 -6
- package/src/i18n/generated-locales/hu-HU.generated.ts +0 -6
- package/src/i18n/generated-locales/it-IT.generated.ts +0 -7
- package/src/i18n/generated-locales/ja-JP.generated.ts +0 -6
- package/src/i18n/generated-locales/ko-KR.generated.ts +0 -6
- package/src/i18n/generated-locales/nl-NL.generated.ts +0 -6
- package/src/i18n/generated-locales/no-NO.generated.ts +0 -6
- package/src/i18n/generated-locales/pl-PL.generated.ts +0 -7
- package/src/i18n/generated-locales/pseudo.generated.ts +1 -6
- package/src/i18n/generated-locales/pt-BR.generated.ts +0 -7
- package/src/i18n/generated-locales/pt-PT.generated.ts +0 -7
- package/src/i18n/generated-locales/ro-RO.generated.ts +0 -6
- package/src/i18n/generated-locales/ru-RU.generated.ts +0 -7
- package/src/i18n/generated-locales/sr-SP.generated.ts +0 -6
- package/src/i18n/generated-locales/sv-SE.generated.ts +0 -6
- package/src/i18n/generated-locales/tr-TR.generated.ts +0 -7
- package/src/i18n/generated-locales/uk-UA.generated.ts +0 -7
- package/src/i18n/generated-locales/vi-VN.generated.ts +0 -6
- package/src/i18n/generated-locales/zh-CN.generated.ts +0 -6
- package/src/i18n/generated-locales/zh-TW.generated.ts +0 -6
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
schema_version: 3
|
|
3
|
-
template_version:
|
|
3
|
+
template_version: 7
|
|
4
4
|
kind: component
|
|
5
5
|
id: component:ChatLayoutScrollButton
|
|
6
|
-
authority:
|
|
6
|
+
authority: current
|
|
7
7
|
archive_reason: null
|
|
8
8
|
superseded_by: null
|
|
9
|
-
approved_by:
|
|
10
|
-
approved_at:
|
|
9
|
+
approved_by: cixzhang
|
|
10
|
+
approved_at: 2026-10-31
|
|
11
11
|
owners: [cixzhang]
|
|
12
12
|
review_triggers: [public-api, behavior, accessibility, theming, testing]
|
|
13
13
|
verified_by:
|
|
@@ -38,15 +38,15 @@ system_specs: [spec:AST-029]
|
|
|
38
38
|
|
|
39
39
|
## Contract at a glance
|
|
40
40
|
|
|
41
|
-
| Area | Contract
|
|
42
|
-
| ----------------------- |
|
|
43
|
-
| Public contract | `
|
|
44
|
-
| Behavior | A pill holding one ghost Button. Hidden state paints nothing and leaves the tab order; a non-empty `label` expands the pill and becomes the visible text; omitted or empty renders icon-only.
|
|
45
|
-
| End-user impact |
|
|
46
|
-
| Builder impact | None.
|
|
47
|
-
| Compatibility/readiness |
|
|
48
|
-
| Review checks | Reject a target on the centring row, a hidden pill that keeps keyboard focus, a non-empty label that is not also the accessible name, or a state that paints identically to its opposite. FR1a, FR3a, and FR6a are recorded
|
|
49
|
-
| Governing rules | `architecture:component-theming-surface/INV4
|
|
41
|
+
| Area | Contract |
|
|
42
|
+
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
43
|
+
| Public contract | `ChatLayoutScrollButton` with required `isVisible` and `onClick`, optional `label`, and root `div` passthrough, styles, and ref. |
|
|
44
|
+
| Behavior | A pill holding one ghost Button. Hidden state paints nothing and leaves the tab order; a non-empty `label` expands the pill and becomes the visible text; omitted or empty renders icon-only. |
|
|
45
|
+
| End-user impact | Readers who scrolled up get one control back to the newest message, which is hidden and out of the tab order when they are already there; a theme that restyles its target repaints the pill they see. |
|
|
46
|
+
| Builder impact | None. |
|
|
47
|
+
| Compatibility/readiness | Records shipped behavior; no public change. Three implementation gaps (FR1a, FR3a, FR6a) are listed under Current-state impact; eight owner questions stay open. |
|
|
48
|
+
| Review checks | Reject a target on the centring row, a hidden pill that keeps keyboard focus, a non-empty label that is not also the accessible name, or a state that paints identically to its opposite. FR1a, FR3a, and FR6a are recorded gaps, not acceptances. |
|
|
49
|
+
| Governing rules | `architecture:component-theming-surface/INV4`; `architecture:component-style-authoring/INV1, INV5`; `architecture:public-component-api/INV5, INV6, INV8`; WCAG 2.2 SC 2.4.7 and SC 2.5.8 (AA). |
|
|
50
50
|
|
|
51
51
|
This table is a review projection; the body below is authoritative.
|
|
52
52
|
|
|
@@ -58,24 +58,28 @@ it owns no scroll state. ChatLayout composes it with `useChatStreamScroll` and
|
|
|
58
58
|
`useChatNewMessages` by default, and a caller may replace or remove it through
|
|
59
59
|
`ChatLayout`'s `scrollButton` slot.
|
|
60
60
|
|
|
61
|
-
This record is an observational backfill written during the 2026-09-23
|
|
62
|
-
whole-component audit. It describes verified shipped behavior and the one
|
|
63
|
-
objective repair that travels with it. It adds no behavior, default,
|
|
64
|
-
compatibility promise, or design decision.
|
|
65
|
-
|
|
66
61
|
## Compatibility and migration
|
|
67
62
|
|
|
68
|
-
- Released default preserved: yes
|
|
69
|
-
|
|
70
|
-
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
- Controlled/uncontrolled behavior: unchanged — visibility is a required caller
|
|
74
|
-
input, and the component holds no state.
|
|
75
|
-
- Migration decision: `component:ChatLayoutScrollButton/DEC-1`
|
|
63
|
+
- Released default preserved: `yes`
|
|
64
|
+
- Compatibility class: records shipped behavior; no public change
|
|
65
|
+
- Controlled/uncontrolled behavior: visibility is a required caller input, and
|
|
66
|
+
the component holds no state
|
|
67
|
+
- Migration decision: none; DEC-1 records where the target sits
|
|
76
68
|
|
|
77
69
|
Consumer migration instructions belong in consumer docs and release notes.
|
|
78
70
|
|
|
71
|
+
## Current-state impact
|
|
72
|
+
|
|
73
|
+
- **An empty label leaves the control unnamed (FR1a).** `label=''` renders an
|
|
74
|
+
icon-only control with an empty accessible name; WCAG 2.2 SC 4.1.2 requires a
|
|
75
|
+
name (OQ7).
|
|
76
|
+
- **The focus ring is clipped away (FR3a).** The pill's `overflow: hidden`
|
|
77
|
+
clips the Button's outset focus ring, so a focused control looks the same as
|
|
78
|
+
a resting one; WCAG 2.2 SC 2.4.7 requires a visible indicator (OQ8).
|
|
79
|
+
- **A theme cannot address the pill's states (FR6a).** The pill's visible and
|
|
80
|
+
expanded states change its paint, but the target exposes neither, so a theme
|
|
81
|
+
can restyle the pill only as a whole (OQ1).
|
|
82
|
+
|
|
79
83
|
## Ownership boundary
|
|
80
84
|
|
|
81
85
|
**Owns**
|
|
@@ -104,30 +108,27 @@ Consumer migration instructions belong in consumer docs and release notes.
|
|
|
104
108
|
| `label` — omitted | `undefined` | Icon-only; the translated default becomes the accessible name | always | this is the default | `component:ChatLayoutScrollButton` | stable | — |
|
|
105
109
|
| `label` — non-empty | any non-empty string | Visible text, which is also the accessible name | always | — | `component:ChatLayoutScrollButton` | stable | — |
|
|
106
110
|
| `label` — empty | `''` | Icon-only with an empty accessible name — see FR1a | always | — | `component:ChatLayoutScrollButton` | stable | no fallback is applied; the control is unnamed |
|
|
107
|
-
| `onClick` | `() => void` | Activation callback;
|
|
111
|
+
| `onClick` | `() => void` | Activation callback; its type declares no arguments | always | required | `component:ChatLayoutScrollButton` | stable | required; no default action |
|
|
108
112
|
| root `div` | `BaseProps<HTMLDivElement>` | `ref`, `xstyle`, `className`, `style`, and remaining DOM passthrough | always | none | `architecture:public-component-api` | stable | `onClick` is omitted from the inherited DOM props |
|
|
109
113
|
|
|
110
114
|
Consumer syntax and prop defaults remain in `ChatLayoutScrollButton.doc.mjs`.
|
|
111
115
|
|
|
112
116
|
## Behavioral and layout contract
|
|
113
117
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
|
|
|
121
|
-
|
|
|
122
|
-
|
|
|
123
|
-
|
|
|
124
|
-
|
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
| FR7 | The transition between states MUST collapse to `0s` under `prefers-reduced-motion: reduce`. | current behavior | settled |
|
|
129
|
-
| FR8 | The collapsed control MUST fit a 24x24 CSS-px square. | WCAG 2.2 SC 2.5.8 (AA) | settled |
|
|
130
|
-
| FR9 | Consumer `ref`, styles, and remaining DOM props MUST reach the component's outer element. | current behavior; `architecture:public-component-api/INV5, INV6, INV8` | settled |
|
|
118
|
+
| ID | Invariant | Basis |
|
|
119
|
+
| ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
120
|
+
| FR1 | The control MUST expose a non-empty accessible name in every reachable state. | WCAG 2.2 SC 4.1.2 |
|
|
121
|
+
| FR1a | `label` omitted uses the translated default; a non-empty `label` is the name. Implementation gap: `label=''` satisfies neither (Current-state impact). | Current source |
|
|
122
|
+
| FR2 | While `isVisible` is false the pill MUST paint nothing and MUST NOT accept focus, programmatic or sequential. | Current source and tests; WCAG 2.2 SC 2.4.7 |
|
|
123
|
+
| FR3 | While `isVisible` is true the pill MUST remain keyboard reachable and activate on Enter. | Current source and tests; WCAG 2.2 SC 2.1.1 |
|
|
124
|
+
| FR3a | A focused control MUST paint a visible focus indicator. Implementation gap: the ring is clipped away entirely (Current-state impact). | WCAG 2.2 SC 2.4.7; `spec:AST-020/FR1` |
|
|
125
|
+
| FR4 | A **non-empty** `label` MUST render as visible text; omitted or empty renders icon-only with no visible text. | Current source and tests |
|
|
126
|
+
| FR5 | The pill MUST take its resolved height from the element-size token so no theme scale makes it clip the Button it wraps. | Current source and tests |
|
|
127
|
+
| FR6 | The public theming target MUST sit on the pill — the element painting fill, elevation, and radius — not on the row that centres it. | `architecture:component-theming-surface/INV4` |
|
|
128
|
+
| FR6a | The target reflects neither the visible nor the expanded state that drives its paint. Implementation gap: a theme cannot address either state; whether the target should reflect them is OQ1 (Current-state impact). | Current source |
|
|
129
|
+
| FR7 | The transition between states MUST collapse to `0s` under `prefers-reduced-motion: reduce`. | Current source |
|
|
130
|
+
| FR8 | The collapsed control MUST be at least 24 by 24 CSS pixels. | WCAG 2.2 SC 2.5.8 (AA) |
|
|
131
|
+
| FR9 | Consumer `ref`, styles, and remaining DOM props MUST reach the component's outer element. | Current source and tests; `architecture:public-component-api/INV5, INV6, INV8` |
|
|
131
132
|
|
|
132
133
|
**FR1a — the empty-label naming gap.** `label=''` is permitted by the public
|
|
133
134
|
type and is what ordinary caller code produces from an empty or not-yet-loaded
|
|
@@ -141,25 +142,18 @@ operable control with no accessible name.
|
|
|
141
142
|
This record states the obligation and records the current behavior as violating
|
|
142
143
|
it. Closing the gap means choosing between falling back to the default name,
|
|
143
144
|
rejecting the value, or treating an empty label as a caller error — a public
|
|
144
|
-
API and compatibility decision that belongs to the owner
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
`overflow: hidden`, which it needs to keep the collapse/expand animation inside
|
|
152
|
-
its own rounded shape. There is therefore no room for the ring on any side and
|
|
153
|
-
none of it is painted: captured frames of the focused control are byte-identical
|
|
154
|
-
to the resting control in both configurations and both color modes.
|
|
145
|
+
API and compatibility decision that belongs to the owner. See OQ7.
|
|
146
|
+
|
|
147
|
+
**FR3a — the clipped focus ring.** The composed Button draws its focus ring as
|
|
148
|
+
an `outline` at a positive `outline-offset`, outside its border box. The button
|
|
149
|
+
fills the pill, and the pill clips with `overflow: hidden` to keep the
|
|
150
|
+
collapse/expand animation inside its rounded shape, so none of the ring is
|
|
151
|
+
painted.
|
|
155
152
|
|
|
156
153
|
Keyboard access itself is intact — the control takes focus and activates — so
|
|
157
154
|
this is a visibility defect, not a reachability one. The remedy is a production
|
|
158
155
|
change to how the pill clips or where the ring is drawn, and the choice between
|
|
159
|
-
those changes rendered geometry, so it needs a visual review
|
|
160
|
-
settled correction. `spec:AST-029/FR7` would allow a settled objective fix to
|
|
161
|
-
travel with an audit; this one is not settled, so it is outside **this** PR.
|
|
162
|
-
Recorded as OQ8.
|
|
156
|
+
those changes rendered geometry, so it needs a visual review. Recorded as OQ8.
|
|
163
157
|
|
|
164
158
|
**FR6a — the unreflected state axes.** `isVisible` selects visible/hidden
|
|
165
159
|
styles and `label` selects collapsed/expanded styles, both on the painting
|
|
@@ -184,7 +178,7 @@ the owner. See OQ1.
|
|
|
184
178
|
| State | Required invariant | Allowed variation |
|
|
185
179
|
| ------------------------- | ------------------------------------------------------------------------------------------------------------ | --------------------------------- |
|
|
186
180
|
| hidden | paints nothing, refuses focus, keeps its layout box (FR2) | transition duration |
|
|
187
|
-
| visible, label omitted | named from the default, focusable,
|
|
181
|
+
| visible, label omitted | named from the default, focusable, at least 24 by 24 CSS pixels, no visible text (FR1, FR3, FR4, FR8) | resolved pill size |
|
|
188
182
|
| visible, non-empty label | the label is both the visible text and the accessible name (FR1, FR4) | expanded width up to the ceiling |
|
|
189
183
|
| visible, empty label | **fails FR1** — icon-only with an empty accessible name (FR1a) | none; this is a recorded defect |
|
|
190
184
|
| visible, hovered | the pointer paints a state distinct from rest | the overlay the theme resolves |
|
|
@@ -198,9 +192,6 @@ the owner. See OQ1.
|
|
|
198
192
|
rendered difference from its props during render and registers no listener,
|
|
199
193
|
timer, observer, or animation frame.
|
|
200
194
|
|
|
201
|
-
Current measurements belong in the audit record; this subsection owns only
|
|
202
|
-
durable constraints and their verification target.
|
|
203
|
-
|
|
204
195
|
## Accessibility contract
|
|
205
196
|
|
|
206
197
|
- **AR1 — Named.** The control MUST expose a non-empty accessible name in every
|
|
@@ -214,8 +205,8 @@ durable constraints and their verification target.
|
|
|
214
205
|
- **AR3a — Focus is visible.** A focused control MUST paint a focus indicator
|
|
215
206
|
(WCAG 2.2 SC 2.4.7). The ring is declared but clipped away; recorded as FR3a
|
|
216
207
|
rather than resolved here.
|
|
217
|
-
- **AR3 — Target size.** The collapsed control MUST
|
|
218
|
-
(WCAG 2.2 SC 2.5.8 AA) (FR8).
|
|
208
|
+
- **AR3 — Target size.** The collapsed control MUST be at least 24 by 24 CSS
|
|
209
|
+
pixels (WCAG 2.2 SC 2.5.8 AA) (FR8).
|
|
219
210
|
- **AR4 — Reduced motion.** The state transition MUST collapse to `0s` under
|
|
220
211
|
`prefers-reduced-motion: reduce` (FR7).
|
|
221
212
|
- **AR5 — Translated strings.** The default accessible name MUST resolve through
|
|
@@ -242,23 +233,29 @@ scroll affordance today, so these rows record the gap rather than a requirement.
|
|
|
242
233
|
excludes components that merely use a Button as an action slot. See OQ4.
|
|
243
234
|
- `architecture:component-theming-surface` owns the target/anatomy relationship
|
|
244
235
|
this component's theming must satisfy; FR6 adopts INV4 directly.
|
|
245
|
-
- `spec:AST-029` owns the audit
|
|
236
|
+
- `spec:AST-029` owns the audit evidence inventory this record's verification
|
|
237
|
+
map follows.
|
|
246
238
|
|
|
247
239
|
## Verification map
|
|
248
240
|
|
|
249
|
-
| Contract | Verification
|
|
250
|
-
| -------------- |
|
|
251
|
-
| FR1, FR4 | `ChatLayoutScrollButton.test.tsx` role/name and visible-text cases
|
|
252
|
-
| FR1a |
|
|
253
|
-
| FR2, FR3 | `ChatLayoutScrollButton.test.tsx` tab cases; `ChatLayoutScrollButton.a11y.chromium.spec.ts`
|
|
254
|
-
| FR3a | `ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts` interaction pass: the focused frame's bytes are compared with a rest frame captured from the same page, and the ring's outward reach is measured against the clipping ancestor's room (recorded as `ringChangedPixels`, `outreach`, `roomPx`)
|
|
255
|
-
| FR5 | `ChatLayoutScrollButton.a11y.chromium.spec.ts` two-theme geometry pair
|
|
256
|
-
| FR6 | `ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts` placement and probe-theme reach tests
|
|
257
|
-
| FR6a | source: `themeProps()` is called with no state argument, and `Chat.doc.mjs` declares the target with no `visualProps`/`states`
|
|
258
|
-
| hover, pressed | `ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts` interaction pass — each frame asserts the pseudo-class matched and that the captured bytes differ from a rest frame of the same page
|
|
259
|
-
|
|
|
260
|
-
|
|
|
261
|
-
|
|
241
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
242
|
+
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
|
|
243
|
+
| FR1, FR4 | `ChatLayoutScrollButton.test.tsx` role/name and visible-text cases | label omitted; non-empty label | dropping `isIconOnly` renders the translated name as clipped visible text, and the name case still passes | `audit:ChatLayoutScrollButton/a11y` |
|
|
244
|
+
| FR1a | No focused test; read from source across three files (`??` passing `''` through, Button's `label !== ''` guard, the Icon's decorative default). | label empty | a fallback added here would make the derivation stale without any test failing | `audit:ChatLayoutScrollButton/a11y` |
|
|
245
|
+
| FR2, FR3 | `ChatLayoutScrollButton.test.tsx` tab cases; `ChatLayoutScrollButton.a11y.chromium.spec.ts` | hidden at rest; scrolled up; re-hidden | replacing `visibility` with `opacity` alone leaves the invisible control in the tab order | `audit:ChatLayoutScrollButton/a11y` |
|
|
246
|
+
| FR3a | `ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts` interaction pass: the focused frame's bytes are compared with a rest frame captured from the same page, and the ring's outward reach is measured against the clipping ancestor's room (recorded as `ringChangedPixels`, `outreach`, `roomPx`) | collapsed and labelled, light and dark | the result is recorded and annotated, never asserted, so repairing the clip cannot turn this check red | `audit:ChatLayoutScrollButton/a11y` |
|
|
247
|
+
| FR5 | `ChatLayoutScrollButton.a11y.chromium.spec.ts` two-theme geometry pair | neutral and butter | a literal height lets a larger element scale clip the Button the pill wraps | `audit:ChatLayoutScrollButton/theming` |
|
|
248
|
+
| FR6 | `ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts` placement and probe-theme reach tests | visible, collapsed, neutral and probe | moving the target back to the centring row leaves the pill unchanged under the probe theme | `audit:ChatLayoutScrollButton/theming` |
|
|
249
|
+
| FR6a | source: `themeProps()` is called with no state argument, and `Chat.doc.mjs` declares the target with no `visualProps`/`states` | visible/hidden; collapsed/expanded | adding either axis is new public theming surface, so no test may assert its shape before the owner decides | `audit:ChatLayoutScrollButton/theming` |
|
|
250
|
+
| hover, pressed | `ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts` interaction pass — each frame asserts the pseudo-class matched and that the captured bytes differ from a rest frame of the same page | collapsed and labelled, light and dark | a frame captured after release, or with the pointer elsewhere, fails its own engagement assertion | `audit:ChatLayoutScrollButton/design` |
|
|
251
|
+
| FR8 | `ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts` measured target box | visible, collapsed | a smaller resolved size drops the control below the AA minimum | `audit:ChatLayoutScrollButton/a11y` |
|
|
252
|
+
| FR9 | `ChatLayoutScrollButton.test.tsx` passthrough case | any | dropping `...rest` silently discards `data-*`, `id`, and ARIA the caller set | `audit:ChatLayoutScrollButton/public-api` |
|
|
253
|
+
|
|
254
|
+
Read from source, with no focused test yet:
|
|
255
|
+
|
|
256
|
+
- FR1a: `label=''` producing an empty accessible name.
|
|
257
|
+
- FR6a: the target reflecting no state.
|
|
258
|
+
- FR7: the transition collapsing to `0s` under reduced motion.
|
|
262
259
|
|
|
263
260
|
## Decision log
|
|
264
261
|
|
|
@@ -267,28 +264,15 @@ scroll affordance today, so these rows record the gap rather than a requirement.
|
|
|
267
264
|
**Reference:** `component:ChatLayoutScrollButton/DEC-1`
|
|
268
265
|
**Decider:** `cixzhang`, `2026-09-23`
|
|
269
266
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
repairs a reachability gap rather than removing a compatibility path.
|
|
280
|
-
|
|
281
|
-
Rejected: adding a second target for the pill. That is new public theming
|
|
282
|
-
surface for a component that needs exactly one, and it would leave the
|
|
283
|
-
non-painting target in place.
|
|
284
|
-
|
|
285
|
-
Rejected: moving consumer passthrough to the pill alongside the target.
|
|
286
|
-
`ref`, `xstyle`, `className`, `style`, and the remaining DOM props keep
|
|
287
|
-
landing on the outer element, which is the element the released type, the
|
|
288
|
-
existing passthrough test, and `architecture:public-component-api/INV5, INV8`
|
|
289
|
-
name as the contract's owner. Relocating them would change where a caller's
|
|
290
|
-
`id`, `data-*`, and ref resolve, which is a compatibility decision and not part
|
|
291
|
-
of this repair.
|
|
267
|
+
The component renders two elements: a full-width row that centres the pill and
|
|
268
|
+
holds the gap below it, and the pill, which paints the fill, elevation, and
|
|
269
|
+
radius. The public target sits on the pill, the element a theme restyles, as
|
|
270
|
+
`architecture:component-theming-surface/INV4` requires. Consumer `ref`, styles,
|
|
271
|
+
and DOM props stay on the outer row (FR9).
|
|
272
|
+
|
|
273
|
+
Rejected: a second target for the pill — new theming surface for one painted part.
|
|
274
|
+
|
|
275
|
+
Rejected: moving consumer passthrough to the pill — it changes where a caller's `id` and ref resolve.
|
|
292
276
|
|
|
293
277
|
## Open questions
|
|
294
278
|
|
|
@@ -327,10 +311,10 @@ of this repair.
|
|
|
327
311
|
ring on the pill, or clip on a different element — each change rendered
|
|
328
312
|
geometry and belong to a visual review.
|
|
329
313
|
- **OQ7 — What should `label=''` do?** (`human-api`)
|
|
330
|
-
It
|
|
314
|
+
It produces an unnamed control (FR1a). The candidate answers — fall
|
|
331
315
|
back to the translated default, reject the value in the type, or treat it as
|
|
332
316
|
a caller error — are a public API and compatibility decision on a released
|
|
333
|
-
prop, so
|
|
317
|
+
prop, so this record states the gap without choosing one.
|
|
334
318
|
|
|
335
319
|
## Content boundary
|
|
336
320
|
|