@godxjp/ui 19.4.0 → 19.4.1

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.
@@ -19,7 +19,7 @@ export interface BadgeProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "
19
19
  * `<div>` would be invalid HTML: inside a `TabsTrigger`/`PopoverTrigger`/`Button` (all render a
20
20
  * `<button>`, whose content model is phrasing content only), inside a `<label>` or a `<p>`.
21
21
  * It swaps the TAG only — the chip's own `inline-flex` box, icon and label are unchanged, and
22
- * both the icon `<svg>` and the label `<span>` are already phrasing content (gh#354).
22
+ * both the icon `<svg>` and the label `<span>` are already phrasing content.
23
23
  */
24
24
  as?: "div" | "span";
25
25
  /** Corner shape — `default` (badge radius) · `pill` (fully rounded) · `sharp` (square). */
@@ -9,7 +9,7 @@ export declare function PopoverAnchor(props: React.ComponentProps<typeof Popover
9
9
  * edge and draw its own separators across the full width. The popover drops its own padding by
10
10
  * zeroing `--popover-space-inset` ON THE PANEL, so the inset stays one token (a service that
11
11
  * retunes `--popover-space-inset` still owns every padded popover) and no consumer has to reach
12
- * for a zero-padding utility, which no service theme can reach (gh#354).
12
+ * for a zero-padding utility, which no service theme can reach.
13
13
  */
14
14
  type PopoverContentFlush = {
15
15
  flush?: FlushProp;
@@ -65,7 +65,7 @@ export declare const TableHead: React.ForwardRefExoticComponent<React.ThHTMLAttr
65
65
  /**
66
66
  * The cell's CONTENT owns its inset — an expanded detail panel, a nested table, a full-bleed media
67
67
  * strip. The cell drops its own padding so the child reaches the cell edges; without it the only
68
- * route was a zero-padding utility at the call site, which no service theme can reach (gh#354).
68
+ * route was a zero-padding utility at the call site, which no service theme can reach.
69
69
  */
70
70
  type TableCellFlush = {
71
71
  flush?: FlushProp;
@@ -1,7 +1,7 @@
1
1
  import type { MobileShellProp } from "../../props/components/layout.prop.js";
2
2
  export type { MobileShellProp, MobileShellProp as MobileShellProps, } from "../../props/components/layout.prop.js";
3
3
  /**
4
- * MobileShell — the HANDHELD app shell (gh#354 §6): a status band, an app bar, ONE scroll region,
4
+ * MobileShell — the HANDHELD app shell: a status band, an app bar, ONE scroll region,
5
5
  * a sticky action bar and a bottom tab bar, in that fixed order.
6
6
  *
7
7
  * The fourth root shell. The other three cannot express a phone app: `AppShell` REQUIRES a sidebar
@@ -140,7 +140,7 @@ export type BadgeProp = {
140
140
  /**
141
141
  * Render element — `div` (default) or `span` when the chip sits in a phrasing context where a
142
142
  * `<div>` is invalid HTML (inside a `<button>` rendered by TabsTrigger/PopoverTrigger/Button,
143
- * a `<label>`, a `<p>`). Swaps the tag only (gh#354).
143
+ * a `<label>`, a `<p>`). Swaps the tag only.
144
144
  */
145
145
  as?: "div" | "span";
146
146
  variant?: "default" | "secondary" | "outline";
@@ -107,7 +107,7 @@ export type FlexProp = React.HTMLAttributes<HTMLDivElement> & {
107
107
  * `<div>` would be invalid HTML: inside a `TabsTrigger`/`PopoverTrigger`/`Button` (all of which
108
108
  * render a `<button>`, whose content model is phrasing content only), inside a `<label>`, or
109
109
  * inside a `<p>`. Same closed shape as `ListRow`'s `as` — it swaps the TAG, nothing else: the
110
- * `.ui-flex` rules carry `display: flex`, so the box is identical either way (gh#354).
110
+ * `.ui-flex` rules carry `display: flex`, so the box is identical either way.
111
111
  */
112
112
  as?: "div" | "span";
113
113
  direction?: FlexDirectionProp;
@@ -282,7 +282,7 @@ export type AuthShellProp = {
282
282
  * `AppShell` REQUIRES a sidebar (its bar is a grid area beside the nav rail), `AuthShell` is the
283
283
  * UNAUTHENTICATED root and centres a ~24rem card, and `CenteredShell` is a scrolling DOCUMENT —
284
284
  * its `main` scrolls the page, which is exactly what a handheld app must not do. Composing one out
285
- * of `Card` + `ui-card-inset*` (what docs/showcase/case6 did before gh#354) reproduces the look
285
+ * of `Card` + `ui-card-inset*` (what docs/showcase/case6 once did) reproduces the look
286
286
  * and none of the two behaviours that matter on a real device:
287
287
  *
288
288
  * 1. The shell is the only scroll container. The root is exactly one screen tall, so the DOCUMENT
@@ -57,7 +57,7 @@ export type AuthShellPresetProp = "default" | "login" | "registration" | "device
57
57
  * `"none"` is a DELIBERATE zero, not the absence of a value: two lines that read as ONE block —
58
58
  * a name over its role, a weekday over its date, a tab bar with no seam between its triggers.
59
59
  * Without it those stacks had to carry `gap="xs"`, which is a visual change forced by a missing
60
- * step rather than by design (gh#354).
60
+ * step rather than by design.
61
61
  */
62
62
  export type GapProp = "none" | "xs" | "sm" | "md" | "lg" | "xl";
63
63
  /** DataTable row density subset. */
@@ -20,11 +20,56 @@ A control the **user cannot bring into the frame** — not merely one whose box
20
20
 
21
21
  - `check:contrast` (`scripts/check-contrast.mjs`) — a hand-picked set of ~6 pages checked for WCAG-AA text contrast. It never saw the other ~100 component frames. - `audit:examples` (`pnpm audit docs`) — a static source-regex linter. It can catch a hard-coded hex value but cannot see what actually renders: computed contrast, DOM landmark structure, focus order, or anything a Radix portal produces at runtime.
22
22
 
23
- ## Chrome vs component: two independently-tracked scopes
23
+ ## Chrome vs component vs overlay: three independently-tracked scopes
24
24
 
25
- `check:frame-axe` runs axe **twice** per frame/viewport:
25
+ `check:frame-axe` runs axe **twice** per frame/viewport, and a **third** time on the frames that declare an open step:
26
26
 
27
27
  - **Preview chrome** = the zoom / dimension **toolbar** (`.demo-block-toolbar`, `preview/src/demo-block.tsx`). We own this. It **must be 0** — a chrome violation fails the build.
28
+ - **Component/demo** = everything else, in the frame's **default** state (the rendered frame _and_ its Radix portals). Allowlisted in `baseline.component`.
29
+ - **Overlay** = the same scope re-measured **after opening an overlay** — see below. Allowlisted separately in `baseline.overlay`.
30
+
31
+ ### The overlay scope: opening the thing before measuring it (#355)
32
+
33
+ The gate used to load each frame in its default state and scan once, so every axe rule whose _condition only exists while an overlay is open_ — `aria-hidden-focus` first among them — was **outside its field of view**, not merely un-triggered. 164 frames × 2 viewports reported clean while `aria-hidden-focus` fired on the app background the moment a `Select` or `DropdownMenu` opened in a consumer app.
34
+
35
+ A frame opts in **declaratively**, from the demo itself:
36
+
37
+ ```tsx
38
+ // the trigger itself, when it forwards DOM props
39
+ <DropdownMenuTrigger asChild>
40
+ <Button data-axe-open …>…</Button>
41
+ </DropdownMenuTrigger>
42
+
43
+ // the region that owns the trigger, when the component renders it internally
44
+ <CardContent data-axe-open>
45
+ <FormField id="status" label="状態"><Select … /></FormField>
46
+ </CardContent>
47
+
48
+ // right-click, for the one overlay that opens on no other gesture
49
+ <ContextMenuTrigger data-axe-open="contextmenu" …>
50
+ ```
51
+
52
+ What the gate does with it, per frame/viewport, **after** the two default-state scans (opening an overlay is destructive to the state those measure):
53
+
54
+ 1. Take the **first** `[data-axe-open]` on the page. No declaration ⇒ the frame skips this scope entirely and costs nothing.
55
+ 2. Press **Escape** up to three times while any overlay is mounted. Several demos deliberately render one open at rest, and a modal one makes the rest of the page inert — so the trigger is unreachable until it is dismissed. This also makes the measurement a real **closed → open transition** rather than whatever the demo happened to leave mounted.
56
+ 3. Resolve the click target: the declaring element itself when it matches `button, [role=button], [role=combobox], [role=menuitem], a[href], summary`, otherwise the first such control inside it. (`DatePicker`, the data-driven `Select` and `SearchSelect` own their trigger DOM and forward no `data-*` to it — hence the "declare on the region" form. `DatePicker`'s combobox `<input>` opens the calendar on click, which is what gets pressed there.)
57
+ 4. Click (or right-click), then wait until **one more** overlay is mounted (`[data-radix-popper-content-wrapper], [role=dialog], [role=alertdialog], [role=menu], [role=listbox]`) plus a 400 ms animation settle — never a bare timer.
58
+ 5. Re-run axe with the same scope as the component pass and record it under `baseline.overlay`.
59
+
60
+ A declaration whose overlay never opens is an **infrastructure error** (blocking), not a silent skip: a broken declaration must not read as a clean frame.
61
+
62
+ **Frames declaring an open step today** (the overlay families named in #355): `data-entry-select`, `data-entry-select-matrix` (SearchSelect), `data-entry-date-picker`, `data-display-popover`, `navigation-dropdown-menu`, `navigation-context-menu`, `feedback-dialog`, `feedback-sheet`.
63
+
64
+ **Cost**: the overlay pass only runs on declaring frames, so it is ~8 frames × 2 viewports, measured at **+~55 s on a ~9.5 min local sweep (~10 %)**. Both viewports are kept for now; if the declaring set grows, narrow `OVERLAY_VIEWPORTS` in the script to `["desktop"]` — an overlay is portalled to `<body>` and its aria-hidden background is the same tree at either width.
65
+
66
+ **What is still outside this scope** — deliberately, and worth knowing before trusting a green run:
67
+
68
+ - Only the **first** declaration per frame is exercised. A frame with several distinct overlays (the Sheet demo has five) is measured on one of them.
69
+ - Only **one step deep**: submenus (`DropdownMenuSub`), an overlay opened _from inside_ another overlay, and the state _after_ a selection are not reached.
70
+ - **Keyboard-only opening** is not exercised — the gate clicks. A trigger that opens on click but not on `Enter`/`Space` would pass.
71
+ - The overlay's own **focus trap and focus order** are still not asserted by axe here (`focus-order-semantics` needs the focus to actually move); `src/components/__tests__/overlay-hidden-background.a11y.test.tsx` remains the DOM-level guard for the aria-hidden/inert contract, and it is what fails first if the `inert` mirror in `src/components/general/inert-background.ts` regresses in jsdom.
72
+ - Frames with **no declaration** (156 of 164) are unchanged: default state only.
28
73
 
29
74
  ## The component allowlist — and how it shrinks to zero
30
75
 
@@ -43,6 +88,10 @@ git add scripts/frame-axe.baseline.json && git commit -m "chore(a11y): tighten f
43
88
 
44
89
  There is **no blanket suppression**: a documented axe false positive would be an explicit per-rule entry with a comment, never a mute.
45
90
 
91
+ `baseline.overlay` is the same shape and obeys the same rule, for the overlay-open scope. It is only rewritten for frames whose open step **actually ran** — a frame that stopped declaring one has not been proven clean, it has been left unmeasured.
92
+
93
+ **Keys that are not violation data are preserved (#356).** The baseline also carries human notes — `scopeGap` records what the gate cannot see and which violations are deliberately unfixed. `--update-baseline` used to rebuild the file from scratch and delete them without a word. It now regenerates only the keys it owns (`generatedAt`, `note`, `component`, `overlay`), carries every other key over verbatim, and **names them in its log line** (`carried over: scopeGap`) so a disappearing key cannot go unnoticed.
94
+
46
95
  `0` chrome violations, `0` regressions. See [Remaining baseline debt](#remaining-baseline-debt--the-12-frames) below for exactly what's left and the path to zero.
47
96
 
48
97
  ## Running it locally
@@ -68,28 +117,47 @@ pnpm exec playwright install chromium # local dev — downloads Playwrigh
68
117
  | `pnpm check:frame-axe --format json` | Machine-readable result on stdout (same exit-code semantics) — no colored human report |
69
118
  | `pnpm check:frame-axe --update-baseline` | Regenerate the allowlist (2-pass union by default) — see below |
70
119
  | `AXE_RUNS=3 pnpm check:frame-axe --update-baseline` | More union passes, for a component with flaky portal timing |
120
+ | `AXE_SHARD=2/4 pnpm check:frame-axe` | Run only shard 2 of 4 (1-based) for CI fan-out — see below |
71
121
  | `pnpm check:frame-axe http://localhost:6008` | Point at an already-running preview instead of building+serving one |
72
122
 
123
+ #### Sharding the sweep (`AXE_SHARD=i/n`)
124
+
125
+ `AXE_SHARD=2/4` runs only the frames of shard 2 of 4. The partition is taken over frame ids sorted **by code point**, not in manifest/directory order, so the same tree always yields the same partition, every frame lands in exactly one shard, and the union of all `n` shards is exactly the full sweep. Unset (the default) leaves the frame list untouched — sharding changes nothing until you ask for it.
126
+
127
+ Two things are deliberately _not_ the same under a shard, because a shard cannot know them:
128
+
129
+ - **"Baseline rule no longer fires" (shrink hints) is skipped.** The baseline is keyed per frame, so _new_ rule detection stays exact on a partial run — but a frame this shard never visited looks identical to a frame that got fixed. Only a full sweep can tell those apart.
130
+ - **`--update-baseline` refuses to run** with `AXE_SHARD` set (exit 2). Writing the baseline from a fraction of the frames would delete every frame the shard did not visit.
131
+
132
+ `AXE_FRAMES_LIMIT` truncates the list instead of partitioning it; it is a local smoke-test knob, not a fan-out mechanism.
133
+
73
134
  If no preview server answers at the base URL (`http://localhost:6008` by default), the script builds one itself: `pnpm preview:build` then serves the static output with `vite preview` (`ensurePreviewServer` in `scripts/frame-harness.mjs`) — deterministic and stable under a long headless sweep, unlike the dev server's per-request recompilation. If a `pnpm preview` dev server (or a remote base) is already reachable, it's reused as-is and never rebuilt.
74
135
 
75
136
  ### Reading the output
76
137
 
77
- Terminal report (human mode) has two blocks:
138
+ Terminal report (human mode) has three blocks:
78
139
 
79
140
  ```
80
141
  Preview-chrome axe (blocking — must be 0):
81
- ✓ 0 chrome violations across all 107 frame(s).
142
+ ✓ 0 chrome violations across all 164 frame(s).
82
143
 
83
144
  Component/demo axe (allowlisted — baseline may only shrink):
84
- 47 component violation node(s) remaining across 12 frame(s).
145
+ 0 component violation node(s) remaining across 0 frame(s).
85
146
  ✓ no new component violation types (rule-set within baseline).
147
+
148
+ Overlay axe (frames declaring data-axe-open — baseline may only shrink):
149
+ 8 frame(s) opened an overlay before scanning; 128 violation node(s) while open.
150
+ ✓ no new overlay violation types (rule-set within baseline).
86
151
  ```
87
152
 
88
153
  - A **red `✗ <rule>`** under chrome is always blocking.
89
154
  - A **red `✗ N NEW violation-type(s) not in baseline`** under component means a frame gained a
90
155
  rule the baseline didn't have — that's the actual CI failure mode for a component regression.
156
+ - A **red `✗ N NEW violation-type(s) not in the overlay baseline`** is the same failure for the
157
+ overlay-open state — that is what turns red when, say, the `inert` background patch regresses
158
+ and `aria-hidden-focus` comes back.
91
159
  - A **yellow `↓ shrink hint`** means a baseline rule no longer fires anywhere it's listed — free
92
- baseline tightening available via `--update-baseline`.
160
+ baseline tightening available via `--update-baseline`. Not emitted under `AXE_SHARD`.
93
161
 
94
162
  ### Evidence JSON
95
163
 
@@ -103,19 +171,27 @@ Shape:
103
171
  "base": "http://localhost:6008",
104
172
  "summary": {
105
173
  "status": "ok", // "ok" | "fail"
106
- "frames": 107,
174
+ "frames": 164,
175
+ "shard": null, // "i/n" when AXE_SHARD is set
107
176
  "viewports": ["desktop", "mobile"],
108
177
  "chromeViolations": 0, // total chrome violation NODE count
109
- "componentViolations": 47, // total component violation NODE count (reporting only)
178
+ "componentViolations": 0, // total component violation NODE count (reporting only)
110
179
  "componentRegressions": 0, // rules present now but not in the baseline
180
+ "overlayFrames": 8, // frames whose declared open step ran
181
+ "overlayViolations": 128, // total NODE count measured with an overlay open
182
+ "overlayRegressions": 0, // overlay rules present now but not in baseline.overlay
111
183
  "infrastructureErrors": 0, // frames that failed to load / axe crashed
112
184
  },
113
185
  "chrome": {/* rule → { impact, nodes, frames: ["<frameId>@<viewport>", …] } */},
114
186
  "componentCurrent": {/* frameId → viewport → ruleId → node count, this run */},
115
187
  "regressions": [/* { frame, rule } — new rules, the actual gate failure */],
116
188
  "shrinkHints": [/* { frame, rule } — baseline rules that no longer fire */],
117
- "infraErrors": [/* { frame, viewport, message } */],
118
- "results": [/* per-frame { id, viewports: { desktop: { chrome, component }, mobile: {…} } } */],
189
+ "overlayCurrent": {/* frameId viewport → ruleId → node count, with the overlay OPEN */},
190
+ "overlayFrames": [/* frame ids whose declared open step ran */],
191
+ "overlayRegressions": [/* { frame, rule } — new rules in the overlay-open state */],
192
+ "overlayShrinkHints": [/* { frame, rule } */],
193
+ "infraErrors": [/* { frame, viewport, message } — includes "overlay open: …" failures */],
194
+ "results": [/* per-frame { id, viewports: { desktop: { chrome, component, overlay? }, … } } */],
119
195
  }
120
196
  ```
121
197
 
@@ -140,6 +216,8 @@ Playwright Inspector / browser devtools with the `axe-core` extension against th
140
216
  violation, not fixed an old one).
141
217
  4. Commit the baseline alongside the fix in the same PR.
142
218
 
219
+ `--update-baseline` also preserves every non-violation key in the file verbatim (see #356 above) and refuses to run under `AXE_SHARD`.
220
+
143
221
  `AXE_FRAMES_LIMIT` + `--update-baseline` together only touch the frames actually run — the merge
144
222
  logic in `check-frame-axe.mjs` overwrites `runFrameIds` and leaves every other frame's baseline
145
223
  entry untouched, so a scoped re-snapshot (e.g. after fixing just `Select`) never silently drops
@@ -258,10 +336,14 @@ Before opening a PR that adds or meaningfully changes a `/frame/**` example:
258
336
  8. If you do land a frame with a tracked violation, add it to `scripts/frame-axe.baseline.json` via
259
337
  `--update-baseline` (never hand-edit the JSON) and note _why_ + the follow-up issue in the PR
260
338
  description — the baseline file's own `note` field explains it may only shrink from here.
261
- 9. Register the case you authored in `preview/frame-coverage.ledger.json` so
262
- `check:frame-coverage-ledger` stops reporting that dimension `UNTESTED`. You **cannot** hand-write
263
- a verdict add an entry to the ledger's `cases` array (frame path + case heading + resolvable
264
- evidence paths + reviewer + HTTPS review link + ISO timestamp) and run
265
- `pnpm gen:frame-coverage-ledger`; the gate recomputes every cell from that evidence and rejects
266
- any verdict it cannot reproduce. See [FRAME-COVERAGE-LEDGER.md](./FRAME-COVERAGE-LEDGER.md) and
267
- [FRAME-COVERAGE-STANDARD.md](./FRAME-COVERAGE-STANDARD.md).
339
+ 9. **If the frame demos an overlay, declare its open step**: `data-axe-open` on the trigger (or on
340
+ the region that owns it when the component renders the trigger itself), `data-axe-open="contextmenu"`
341
+ for a context menu. Without it the frame is only ever measured closed, and the whole
342
+ `aria-hidden-focus` family of rules cannot fire see [the overlay scope](#the-overlay-scope-opening-the-thing-before-measuring-it-355).
343
+ 10. Register the case you authored in `preview/frame-coverage.ledger.json` so
344
+ `check:frame-coverage-ledger` stops reporting that dimension `UNTESTED`. You **cannot** hand-write
345
+ a verdict — add an entry to the ledger's `cases` array (frame path + case heading + resolvable
346
+ evidence paths + reviewer + HTTPS review link + ISO timestamp) and run
347
+ `pnpm gen:frame-coverage-ledger`; the gate recomputes every cell from that evidence and rejects
348
+ any verdict it cannot reproduce. See [FRAME-COVERAGE-LEDGER.md](./FRAME-COVERAGE-LEDGER.md) and
349
+ [FRAME-COVERAGE-STANDARD.md](./FRAME-COVERAGE-STANDARD.md).
@@ -3,8 +3,8 @@
3
3
  > Generated by `scripts/frame-coverage.mjs` (issue #163). Do not edit by hand — run `pnpm check:frame-coverage`.
4
4
  > Standard: [FRAME-COVERAGE-STANDARD.md](./FRAME-COVERAGE-STANDARD.md). A missing dimension is **UNTESTED**, never a pass.
5
5
 
6
- - Public components: **124**
7
- - With ≥1 frame: **124** · zero-frame: **0**
6
+ - Public components: **126**
7
+ - With ≥1 frame: **126** · zero-frame: **0**
8
8
  - Fully covered (every contract axis declared): **0**
9
9
 
10
10
  ## Per-component contract axes
@@ -19,6 +19,7 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
19
19
  | AppShell | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
20
20
  | AuthShell | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
21
21
  | CenteredShell | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
22
+ | MobileShell | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
22
23
  | Sidebar | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
23
24
  | Topbar | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
24
25
  | MasterDetail | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
@@ -47,6 +48,7 @@ Legend: ✓ covered · N/A reasoned skip · · UNTESTED (blank cell = UNTESTED).
47
48
  | CodeBlock | ✓ | · | N/A | N/A | · | · | · | · | · | · |
48
49
  | Prose | ✓ | · | N/A | N/A | · | · | · | · | · | · |
49
50
  | Timeline | ✓ | ✓ | N/A | N/A | · | · | · | · | · | · |
51
+ | TimelineGrid | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
50
52
  | Table | ✓ | N/A | N/A | N/A | · | · | · | · | · | · |
51
53
  | DataState | ✓ | N/A | · | N/A | · | · | · | · | · | · |
52
54
  | InfiniteQueryState | ✓ | N/A | · | N/A | · | · | · | · | · | · |
@@ -135,7 +135,10 @@ export default function Demo() {
135
135
  <Flex direction="row" wrap gap="sm">
136
136
  <Popover>
137
137
  <PopoverTrigger asChild>
138
- <Button variant="outline">side=top</Button>
138
+ {/* data-axe-open: opened by check-frame-axe before its overlay scan. */}
139
+ <Button data-axe-open variant="outline">
140
+ side=top
141
+ </Button>
139
142
  </PopoverTrigger>
140
143
  <PopoverContent side="top" aria-label="上側の配置例">
141
144
  <PopoverDescription>上に開きます。</PopoverDescription>
@@ -33,7 +33,8 @@ export default function Demo() {
33
33
  形式に整形され、 解釈できない入力は元の値へ復帰する。
34
34
  </CardDescription>
35
35
  </CardHeader>
36
- <CardContent>
36
+ {/* data-axe-open: check-frame-axe opens the calendar before scanning. */}
37
+ <CardContent data-axe-open>
37
38
  <FormField id="issue-date" label="発行日" required>
38
39
  <DatePicker
39
40
  id="issue-date"
@@ -130,7 +130,7 @@ export default function Demo() {
130
130
 
131
131
  <Card>
132
132
  <CardHeader>
133
- <CardTitle level={2}>Field identity · data-field (gh#337)</CardTitle>
133
+ <CardTitle level={2}>Field identity · data-field</CardTitle>
134
134
  <CardDescription>
135
135
  項目キーを control の data-field に出す。既定は id から導出。id が DOM
136
136
  一意化のためのもので項目キーと違う場合だけ field を明示する。AppProvider
@@ -176,7 +176,7 @@ export default function Demo() {
176
176
 
177
177
  <Card>
178
178
  <CardHeader>
179
- <CardTitle level={2}>Static value · mixed with editable fields (gh#294)</CardTitle>
179
+ <CardTitle level={2}>Static value · mixed with editable fields</CardTitle>
180
180
  <CardDescription>
181
181
  staticText renders a read-only VALUE as a FormField itself: Descriptions.Item's exact
182
182
  value typography, no control, no id/aria-* wiring. An immutable name/email row and an
@@ -142,7 +142,8 @@ export default function Demo() {
142
142
  references. The internal search textbox has its own visible-purpose accessible name.
143
143
  </CardDescription>
144
144
  </CardHeader>
145
- <CardContent>
145
+ {/* data-axe-open: check-frame-axe opens the searchable panel before scanning. */}
146
+ <CardContent data-axe-open>
146
147
  <FormField
147
148
  id="searchable-invalid"
148
149
  label="担当拠点"
@@ -82,7 +82,9 @@ export default function Demo() {
82
82
  Pass an options array; name= submits the value with the form.
83
83
  </CardDescription>
84
84
  </CardHeader>
85
- <CardContent>
85
+ {/* data-axe-open: check-frame-axe opens this Select before scanning, so the
86
+ overlay-open a11y state is measured. See docs/FRAME-A11Y-CI.md. */}
87
+ <CardContent data-axe-open>
86
88
  <FormField id="status" label="状態">
87
89
  <Select
88
90
  id="status"
@@ -109,7 +109,8 @@ export default function Demo() {
109
109
  <CardContent>
110
110
  <Dialog open={detailOpen} onOpenChange={setDetailOpen}>
111
111
  <DialogTrigger asChild>
112
- <Button variant="outline" size="sm">
112
+ {/* data-axe-open: opened by check-frame-axe before its overlay scan. */}
113
+ <Button data-axe-open variant="outline" size="sm">
113
114
  取引詳細を見る
114
115
  </Button>
115
116
  </DialogTrigger>
@@ -213,7 +213,8 @@ export default function Demo() {
213
213
  <CardContent>
214
214
  <Sheet open={editOpen} onOpenChange={setEditOpen}>
215
215
  <SheetTrigger asChild>
216
- <Button variant="ghost" size="sm">
216
+ {/* data-axe-open: opened by check-frame-axe before its overlay scan. */}
217
+ <Button data-axe-open variant="ghost" size="sm">
217
218
  取引先を編集
218
219
  </Button>
219
220
  </SheetTrigger>
@@ -282,8 +282,8 @@ export default function Demo() {
282
282
  を切り替え、sidebar・topbar・page actions・grid・split pane
283
283
  の変換を同じ実用構成で確認する。900px 以下ではドックされたサイドバーが隠れ、
284
284
  AppShell 所有のモバイルドロワー(トップバー左のハンバーガー → フォーカストラップ付き
285
- Sheet · Esc で閉じてトリガーへフォーカス復帰)が navigation を提供する(gh#165)。
286
- このデモは mobileNav を渡していないため、同じ Sidebar がドロワーに再利用される。
285
+ Sheet · Esc で閉じてトリガーへフォーカス復帰)が navigation を提供する。 このデモは
286
+ mobileNav を渡していないため、同じ Sidebar がドロワーに再利用される。
287
287
  </CardDescription>
288
288
  <CardDescription>
289
289
  「トップバーなし(チャット型)」を押すと topbar / topbarLeft / topbarRight / logo の
@@ -297,8 +297,8 @@ export default function Demo() {
297
297
  で代用しないこと ― それは 4 スロットが埋まった状態と同じで、バーの行は残る。
298
298
  </CardDescription>
299
299
  <CardDescription>
300
- 横方向のページ余白軸の持ち主は <code>--space-page-x</code> ひとつ(gh#330)。
301
- トップバーは <code>.app-main</code> と同じグリッド列に載っている ―
300
+ 横方向のページ余白軸の持ち主は <code>--space-page-x</code> ひとつ。 トップバーは{" "}
301
+ <code>.app-main</code> と同じグリッド列に載っている ―
302
302
  つまりバー先頭のコントロールとページタイトルは同じ縦線に並ぶべきもので、
303
303
  <code>--app-shell-bar-inset</code> はその持ち主を読む (
304
304
  <code>--app-shell-bar-inset-compact</code> も同様に
@@ -35,8 +35,8 @@ export default function Demo() {
35
35
  列、右の狭いサイドバー(約 20rem)は 1 列に折り返す。ビューポート幅は同一なので、
36
36
  折り返しの基準がコンテナ幅であることが分かる。ResponsiveGrid は自身のクエリコンテナ
37
37
  (container-type: inline-size)を持つため、外側に @container を用意しなくても正しく
38
- 折り返す(gh#165)。閾値はコンテナ幅 40 / 48 / 64rem。 列間の gap は
39
- var(--space-stack-md) に固定で、prop では変更できない。
38
+ 折り返す。閾値はコンテナ幅 40 / 48 / 64rem。 列間の gap は var(--space-stack-md)
39
+ に固定で、prop では変更できない。
40
40
  </CardDescription>
41
41
  </CardHeader>
42
42
  <CardContent>
@@ -72,7 +72,7 @@ export default function Demo() {
72
72
  <CardDescription>
73
73
  固定幅の親の中でも、同じ columns=&#123;4&#125; が親幅で列数を変える。親には
74
74
  container-type を付けていない。ResponsiveGrid が自身のスコープでクエリするため、
75
- 外側にコンテナ宣言がなくても 1 列に潰れない(gh#165)。
75
+ 外側にコンテナ宣言がなくても 1 列に潰れない。
76
76
  </CardDescription>
77
77
  </CardHeader>
78
78
  <CardContent>
@@ -402,7 +402,7 @@ export default function Demo() {
402
402
  <CardDescription>
403
403
  行の中身まで利用側が書く旧エスケープハッチ。これが原因で
404
404
  <code>&lt;Link&gt;{"{item.label}"}&lt;/Link&gt;</code>
405
- がアイコンを全て落とす回帰が発生しました(gh#213)。 現在は rowProps.children
405
+ がアイコンを全て落とす回帰が発生しました。 現在は rowProps.children
406
406
  にライブラリ製の行内容が入るので、それを描画すれば 正規の行が復元されます(ここでは
407
407
  その右にお気に入りスターを添えています)。 新規コードでは linkComponent か
408
408
  SidebarItem asChild を使ってください。
@@ -436,7 +436,7 @@ export default function Demo() {
436
436
  sections を使わず SidebarSection / SidebarItem を直接組み立てて、
437
437
  ナビゲーション全体を 自前で構成します。右は同じ構成に SidebarItem の asChild
438
438
  を足したものです。子として ルーターの Link
439
- を「要素だけ」渡すと、アイコン・ラベル・バッジは ライブラリが差し込みます(gh#213。
439
+ を「要素だけ」渡すと、アイコン・ラベル・バッジは ライブラリが差し込みます(。
440
440
  children は書きません)。
441
441
  </CardDescription>
442
442
  </CardHeader>
@@ -590,7 +590,7 @@ export default function Demo() {
590
590
  "badgeTone で未読(neutral)と自分宛て(destructive)を色だけで区別",
591
591
  "disabled=true で項目を非活性化(クリック不可)",
592
592
  "footer prop でスクロール外にユーザー情報を固定",
593
- "linkComponent · ルーター Link は「要素だけ」渡す。行の中身はライブラリが組み立てる(gh#213)",
593
+ "linkComponent · ルーター Link は「要素だけ」渡す。行の中身はライブラリが組み立てる",
594
594
  "linkComponent は葉・サブメニュー・折りたたみレール・フライアウトの全てに適用される",
595
595
  "グループのトリガーは aria-expanded を持つ開閉ボタンのままなので linkComponent は適用されない",
596
596
  ].map((note) => (
@@ -302,8 +302,8 @@ export default function Demo() {
302
302
  </Text>
303
303
  <Text as="p" tone="muted">
304
304
  パネル自身の幅が 48rem 未満になると縦積みにフォールバックします
305
- (ビューポートではなくコンテナ基準 · gh#165)。
306
- 常にサイドバイサイドが必要な場合は CSS Grid を使用してください。
305
+ (ビューポートではなくコンテナ基準)。 常にサイドバイサイドが必要な場合は CSS
306
+ Grid を使用してください。
307
307
  </Text>
308
308
  </Flex>
309
309
  </CardContent>
@@ -410,9 +410,8 @@ export default function Demo() {
410
410
  <CardHeader>
411
411
  <CardTitle level={2}>Container stress · narrow vs wide embed</CardTitle>
412
412
  <CardDescription>
413
- 大きな viewport 内でも、パネル自身の幅で分割が決まる(gh#165)。狭い埋め込み(約
414
- 28rem)は 1 カラムに潰れ、広い埋め込み(約 56rem)は 2 カラムに分割する。ビューポート
415
- 幅は同一。
413
+ 大きな viewport 内でも、パネル自身の幅で分割が決まる。狭い埋め込み(約 28rem)は 1
414
+ カラムに潰れ、広い埋め込み(約 56rem)は 2 カラムに分割する。ビューポート 幅は同一。
416
415
  </CardDescription>
417
416
  </CardHeader>
418
417
  <CardContent>
@@ -48,7 +48,12 @@ export default function Demo() {
48
48
  <CardContent>
49
49
  <ContextMenu>
50
50
  {/* ui-audit-disable-next-line no-hand-rolled-surface — the dashed area is the right-click target the demo asks you to use */}
51
- <ContextMenuTrigger className="border-border h-36 w-full cursor-context-menu rounded-md border border-dashed">
51
+ {/* data-axe-open="contextmenu": check-frame-axe right-clicks here before its
52
+ overlay scan. */}
53
+ <ContextMenuTrigger
54
+ data-axe-open="contextmenu"
55
+ className="border-border h-36 w-full cursor-context-menu rounded-md border border-dashed"
56
+ >
52
57
  <Flex align="center" justify="center" gap="xs">
53
58
  <Text tone="muted">ここを右クリックしてください</Text>
54
59
  </Flex>
@@ -52,7 +52,8 @@ export default function Demo() {
52
52
  <Text>JE-0042 · 売上計上 ¥480,000</Text>
53
53
  <DropdownMenu>
54
54
  <DropdownMenuTrigger asChild>
55
- <Button variant="ghost" size="icon-sm" aria-label="行アクション">
55
+ {/* data-axe-open: opened by check-frame-axe before its overlay scan. */}
56
+ <Button data-axe-open variant="ghost" size="icon-sm" aria-label="行アクション">
56
57
  <MoreHorizontal />
57
58
  </Button>
58
59
  </DropdownMenuTrigger>
@@ -73,7 +73,7 @@ export default function Demo() {
73
73
  <CardTitle level={2}>先頭タブが disabled · フォールバック選択</CardTitle>
74
74
  <CardDescription>
75
75
  defaultValue/value を渡さない場合、Tabs は先頭の ENABLED タブを自動選択する(disabled
76
- の先頭タブは選ばない)。全タブが disabled の場合は何も選択しない (gh#175)。
76
+ の先頭タブは選ばない)。全タブが disabled の場合は何も選択しない。
77
77
  </CardDescription>
78
78
  </CardHeader>
79
79
  <CardContent>
@@ -106,7 +106,7 @@ export default function Demo() {
106
106
  <CardTitle level={2}>320px stress · 長いローカライズラベル</CardTitle>
107
107
  <CardDescription>
108
108
  狭いコンテナでも長いラベルはクリップされず、水平タブリストが自身でスクロールする
109
- (gh#175)。compact navigation への変換は Tabs API に存在しないため擬似実装しない —
109
+ 。compact navigation への変換は Tabs API に存在しないため擬似実装しない —
110
110
  スクロールが意図した縮退動作。
111
111
  </CardDescription>
112
112
  </CardHeader>
@@ -333,7 +333,7 @@ function SectionHeader({ children, count }: { children: React.ReactNode; count?:
333
333
  // ── Phone shell ──────────────────────────────────────────────────────────────
334
334
 
335
335
  /**
336
- * gh#354 §6 — this file used to BUILD the shell: a `Card` + `CardContent flush` phone frame, a
336
+ * This file used to BUILD the shell: a `Card` + `CardContent flush` phone frame, a
337
337
  * `ui-card-inset-x h-9` status row, an `h-14 border-b` header, a `flex-1 overflow-y-auto` body, a
338
338
  * `shrink-0 border-t` action strip and a `ResponsiveGrid` tab bar. It reproduced the look and
339
339
  * neither behaviour that matters on a device — the document still scrolled, and nothing padded out
@@ -962,7 +962,7 @@ export default function AgencyHandyShowcase() {
962
962
 
963
963
  // SELECT MODE REPLACES THE APP BAR — it does not stack a second strip under it. That is the
964
964
  // platform pattern on both iOS and Android, and it is what the `header` slot is for: one bar to
965
- // read at a time. Before gh#354 this file rendered the title bar AND a contextual strip, because
965
+ // read at a time. This file once rendered the title bar AND a contextual strip, because
966
966
  // there was no bar to swap.
967
967
  const header = selectMode ? (
968
968
  <Flex align="center" justify="between" gap="xs" className="w-full">
@@ -120,7 +120,7 @@ function OrgSwitcher({ organizations, currentId, onSelect, onCreate, onJoin }: O
120
120
  className="w-full justify-between"
121
121
  >
122
122
  {/* Inside a Button, i.e. inside a <button>, whose content model is phrasing content
123
- only — so every Flex on this branch renders as a <span> (gh#354). */}
123
+ only — so every Flex on this branch renders as a <span>. */}
124
124
  <Flex as="span" align="center" gap="sm" className="min-w-0">
125
125
  <Avatar className="size-7 rounded-md">
126
126
  <AvatarFallback>{monogram(current.name)}</AvatarFallback>
@@ -61,7 +61,7 @@ import { AlertDialog } from "@godxjp/ui/feedback";
61
61
  import { Flex, PageContainer } from "@godxjp/ui/layout";
62
62
 
63
63
  // ── Label bundles: the SAME screen at its longest JA / EN / VI wording ────────────────────────
64
- // Long-label stress is a first-class acceptance criterion (#216), so the bundle intentionally
64
+ // Long-label stress is a first-class acceptance criterion, so the bundle intentionally
65
65
  // carries the verbose form of each label instead of the tidy demo form.
66
66
 
67
67
  type LabelBundle = {
@@ -348,7 +348,7 @@ export default function SettingsAccountSectionsShowcase() {
348
348
  title={b.pageTitle}
349
349
  subtitle={b.pageSubtitle}
350
350
  breadcrumb={[{ label: b.pageTitle }]}
351
- // PageHeader action composition (#216): the header owns the page-level actions through
351
+ // PageHeader action composition: the header owns the page-level actions through
352
352
  // `extra`. It wraps at 390 instead of overflowing, so no page-local header geometry.
353
353
  extra={
354
354
  <Flex direction="row" align="end" gap="sm" wrap>
@@ -417,7 +417,7 @@ export default function Demo() {
417
417
  </Card>
418
418
 
419
419
  {/* ── Empty detail state (nothing selected) ──
420
- Fixed-track split, so the geometry belongs to MasterDetail, not this page (gh#223):
420
+ Fixed-track split, so the geometry belongs to MasterDetail, not this page:
421
421
  rail="master" keeps the leading 一覧 on the fixed track while the detail surface stays
422
422
  fluid, and collapseBelow="md" stacks 一覧→詳細 once the card body itself is too narrow
423
423
  for two tracks. No grid tracks, no per-breakpoint widths, no page-local spacing. */}
@@ -369,7 +369,7 @@ const DOT_CLASS: Record<SavedView["dot"], string> = {
369
369
 
370
370
  // TabsTrigger renders a <button>, whose content model is phrasing content only — so the row and
371
371
  // the count chip take `as="span"`. Before Flex/Badge had that seam the only valid shape here was a
372
- // raw `<span className="flex …">`, i.e. utility layout (gh#354).
372
+ // raw `<span className="flex …">`, i.e. utility layout.
373
373
  function ViewTrigger({ view, count }: { view: SavedView; count: number }) {
374
374
  return (
375
375
  <Flex as="span" align="center" gap="sm">
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "19.4.0",
4
- "godxUiMcp": "19.4.0",
3
+ "version": "19.4.1",
4
+ "godxUiMcp": "19.4.1",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",