Skip to content

Phase 2 step 6: accessibility hardening for the Astryx beta path - #110

Merged
ryandmonk merged 20 commits into
mainfrom
claude/blocks-astryx-accessibility
Oct 17, 2026
Merged

ryandmonk merged 20 commits into
mainfrom
claude/blocks-astryx-accessibility

Conversation

@ryandmonk

@ryandmonk ryandmonk commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Phase 2 step 6 (docs/blocks/PHASE-2-RELEASE-PLAN.md, §10), as the owner authorized it on 2026-10-16: accessibility hardening for the Astryx beta path, to WCAG 2.2 AA. Not for automatic merge. No deploy, no Cloudflare change, no paid model call.

What it adds

Automated coverage (e2e/composer-blocks-a11y.spec.ts, in the experimental config that CI runs). Each Astryx block (records-table, auth-form and guide-steps) is built through the hosted path with a stubbed gateway, so no model is called. Each is built from four calls: the approved example, the smallest valid call, every optional prop at its maximum, and the same with unbroken tokens.

Every surface is checked at 1280 x 900, at 640 x 512 and 320 x 256 (200% and 400% zoom of a 1280 x 1024 window), and at 320 x 720:

  • axe with wcag2a, wcag2aa, wcag21a, wcag21aa and wcag22aa over the whole canvas, with nothing excluded, at every size and with a dark preference. Contrast that axe leaves undecided is decided from computed colors, compositing a translucent color over what lies beneath it. Text that another element covers fails.

  • Structure: the title is the surface's one level-2 heading, and each table region is named by its columns.

  • Keyboard:

    • Tab and Shift+Tab reach every control in order, by name, with no trap.
    • Every focused control has its own indicator of 3:1 or more.
    • In forced colors, every focused control keeps an outline.
    • No focused control is entirely hidden (SC 2.4.11) at any size.
  • Layout:

    • targets of 24 px;
    • text spacing (SC 1.4.12) at every size;
    • reflow at every size: nothing scrolls sideways or is cut off, and no box but a table's region scrolls its own content;
    • every line of text is scrolled into view and sampled across its width. A line fails if another element covers it, even one that ignores the pointer, or a box that does not scroll cuts it off;
    • neighbouring parts of a surface must not overlap: their boxes must not intersect;
    • no column is crushed.

    A data table scrolls only inside its own focusable region.

Two specs check Preview. One covers an accepted block: heading, axe and focus ring, plus layout, text spacing, axe and keyboard at the narrow sizes. The other covers every Astryx reference surface's axe, layout and text spacing at every size. It allows one pre-existing violation outside the blocks, by name. A third imports two model-shaped surfaces the blocks never produce (inline dialogs, a children-mode table, Astryx's large type, empty display text) and checks them the same way. Checks are soft, so one run lists every defect. apps/composer/app/astryx-renderers-a11y.test.ts server-renders the Astryx renderers. The shadcn/ui v3 scans run for information only and never fail.

Fixes. Each failed first; details and file references are in docs/blocks/ACCESSIBILITY.md. Rows marked (review) came from the first independent review, (second review) from the second, and (verification) from its verification.

Defect on main WCAG 2.2 Fix
Block titles were <p>: no headings 1.3.1, 2.4.6 The display variants render h1 and h2. h3 stays a paragraph: it is Astryx's body-scale large (review). A variant without text is never an empty heading (review)
Every table region was named "Table" 2.4.6 Named by its columns
Header text cut off with an ellipsis 1.4.4, 1.4.10 Header text wraps
Columns crushed below a letter's width at 320 px 1.4.10 A 96 px column floor in data tables and, from the review, children-mode tables; the table scrolls in its region
Long words and labels ran out of the card 1.4.10 Words break when they must; badges wrap
guide-steps' tables overlapped by 20 px, hiding a row; every table region overlapped its neighbours by 4 px 1.4.3, 1.4.10 The rule removing Astryx's vertical table bleed now matches Composer's canvas (review)
The auth form held the surface 24 px past 320 px 1.4.10 Surfaces narrow with the Astryx canvas
An inline dialog overflowed a 320 px viewport; narrowed, it was cut off, then its 75vh cap scrolled its content in a box of its own at 320 x 256. A fullscreen one kept the viewport's height, and an alert dialog's footer cut off "Keep my archived projects" under text spacing 1.4.10, 1.4.12, 2.1.1 A dialog fits its container's width (review) and takes its content's height (second review; fullscreen too, verification). Its footer's buttons wrap onto rows of their own (verification)
Button labels cut off with an ellipsis: "Continue with Microsoft" under text spacing at 320 px 1.4.12 Labels wrap; a one-line button keeps Astryx's 32 px (review). A word too long for its line breaks, and icon-only buttons keep Astryx's own size (second review)
The studio's green focus ring at 2.41:1 on the canvas white 1.4.11 Astryx's own indicators (#0064E0, 5.4:1); an inset ring on table regions
In forced colors a focused field showed only its caret 2.4.7 An outline on the focused field (review)
A dark OS preference retinted the canvas; Astryx's dark primary button is 3.11:1 1.4.3 The Astryx canvas draws light
The sticky header hid focused controls: with Shift+Tab at 640 and 320 px, and every one at 320 x 256. From 721 to 960 px it was up to 325 px tall 2.4.11 At 960 px wide or less, or 600 px tall or less, the header scrolls away (review; widened in the second); otherwise scroll padding follows its height
The Build options made the page 11 px too wide at 320 px 1.4.10 Each option wraps under its label

Docs.

  • docs/blocks/ACCESSIBILITY.md (new) covers:
    • what the checks prove, and each fix;
    • what this environment could and could not check by hand;
    • what still needs a person, and the proposed waivers;
    • one finding outside the blocks, for information.
  • docs/blocks/BETA.md (new): the beta's known gaps, the auth-form SC 1.3.5 exception first, with its host mitigation.
  • The decision record gains "Step 6, authorized", with the owner's scope in the owner's words.
  • The release plan's status note and docs/BLOCKS.md record that step 6 is built.

Production-visible changes

These fixes are in shared code, so the default build carries them too:

  • packages/astryx-renderers (Text, Table, Badge): every Astryx surface in production's Preview, and the Studio exhibit. The exhibit gets only these, not Composer's CSS or shell.
  • apps/composer/app/globals.css, scoped to [data-astryx-canvas]:
    • the focus indicators and the light scheme;
    • surface narrowing;
    • the table bleed;
    • dialog width and height, and dialog footers that wrap;
    • wrapping of buttons named by their label;
    • the forced-colors field outline.
  • The composer shell:
    • The header is no longer sticky at 960 px wide or less, or at 600 px tall or less. On a phone, a tablet or a narrow window it now scrolls away (af-ui.css).
    • The scroll padding applies only while the header is sticky (composer.tsx).
    • The Build options wrap (build-view.tsx).

They change no request byte, no /api/models reply and no block code path. The default artifact still carries none of check-default-artifact's markers. They change the default artifact's bytes, by design; the comparison is below. If the owner prefers, the shell changes (709725f, ec1a3e9, and the header rule in a6ffba4) can be split out.

Boundaries kept

  • No block signature, template, prompt, model selection, frozen evaluation evidence or release-admission decision changes.
  • No vocabulary is added upstream. auth-form's SC 1.3.5 gap is documented, not fixed.
  • No shadcn renderer change. An earlier commit narrowed every surface in the shared A2uiCanvas, but the shadcn information scan showed it would move a wide shadcn table into a container shadcn does not make focusable. So 7aa423c reverts A2uiCanvas and scopes the rule to the Astryx canvas.
  • No deploy, Cloudflare change or paid model call. A push triggers Cloudflare Workers Builds' preview build of the branch, as on every branch; production is unchanged (below).

Not changed, with reasons.

  • Table-region names stay complete, however long, and two tables with the same columns share a name. Truncating would drop the columns that tell two tables apart.
  • The plan-comparison reference's aria-required-children. It is not a block, and it predates this step. It is recorded in ACCESSIBILITY.md, for a separate change.
  • A row of buttons outside a dialog's footer still overflows when the longest words of its labels together are wider than the row. Only a model-written row could; the blocks' rows fit at every size, with text spacing too. Breaking words anywhere would split "Cancel" in every tight two-button row at 320 px.
  • The button rule's two latent edges, neither possible today: an icon-only button with an empty label, and a labelled button while it loads. Both are recorded in ACCESSIBILITY.md.

Commits

Commit What
6429340 Fail-first tests: the e2e spec and the renderer unit tests
59eb822 Fail-first tests for the column floor and the dark scheme; a fix to the spec's own unfocused-border baseline
f5c0ed9 Astryx renderer semantics and reflow (Text, Table, Badge)
22ba739 The Astryx canvas keeps Astryx's focus indicators and draws light
709725f Composer shell: focus clear of the sticky header; 320 px Build options
7aa423c Surface narrowing scoped to the Astryx canvas (shared A2uiCanvas back to main's)
6a5ec26 Docs: ACCESSIBILITY.md, BETA.md, the step 6 authorization
057606a Docs, outside step 6: the owner's step-5 confirmations, recorded
be6864f Fail-first tests for the review's findings
3521742 Renderers: h3 stays body text, no empty heading, a children-mode column floor
3203749 The Astryx canvas: the table bleed, dialog fit, button wrapping, the forced-colors field outline
ec1a3e9 Composer shell: a narrow or short window lets the header scroll away
09f4e60 Docs: the accessibility record after the review
38082fe Fail-first checks for the second review's findings: text line by line, scroll boxes, contrast everywhere, reference axe
a6ffba4 Canvas and shell: dialog height, button rule scoped and breaking long words, header static to 960 px
0af44ba Docs: the accessibility record after the second review
316081b Fail-first checks for the verification's findings: every line sampled, covers that ignore the pointer, the surfaces beyond the blocks
f04061d The overlap check: neighbours overlap only where their boxes intersect
c8824ee Canvas: a dialog takes its content's height; its footer's buttons wrap
d6b2859 Docs: the accessibility record after the verification

Evidence

Fail-first.

  • Against main (d7251db): the renderer tests failed 8 of 10; the two that passed guard what must not change. The e2e failed all 13 Astryx specs, and the 6 shadcn scans passed, as information.

  • After the first fixes: the 320 px reflow check still failed on the 8-column call (crushed columns), and the added dark pass failed 9 of 12 surfaces. The column-floor unit test failed before its fix.

  • The review's findings, against 057606a's build: the new checks failed all 14 Astryx specs:

    • overlaps on every block with a table, and a covered cell in guide-steps;
    • every focused control hidden at 320 x 256, in Build and Preview;
    • "Continue with Microsoft" cut off under text spacing at 320 px;
    • caret-only field focus in forced colors;
    • the inline dialog cut off at 320 px.

    3 of 13 renderer tests failed. All pass after the fixes.

  • The second review's findings, against 09f4e60's build: the strengthened checks failed only the reference spec. The delete-project dialog at 320 x 256 scrolled its content in a box of its own, with and without text spacing, and axe reported scrollable-region-focusable on it.

  • The verification's findings, against 0af44ba's build: the new beyond-the-blocks spec failed.

    • At 320 x 256 the fullscreen dialog cut its content off.
    • With text spacing at 640 x 512 and 320 x 720, the alert dialog's footer cut off "Keep my archived projects".
  • The text check finds what it claims. With defects injected into the page, it caught each of these, and reported nothing on the clean page:

    • a row moved over the next;
    • a caption's covered lower half;
    • text cut off by a box;
    • a dialog footer pulled over its description;
    • a cover over the last two lines of a 2,529 px cell at 320 x 256;
    • a cover that ignores the pointer.

    The check before the verification missed the last two.

  • The overlap check still catches the original defect. With Astryx's vertical table bleed injected again, it reports the original 4, 20 and 4 px overlaps.

Suites.

  • Full local run at d6b2859, CI's steps in order, with a clean tree before and after: green except the known local agent timeout.
    • check-sync passes, and typecheck is clean.
    • Unit tests:
      • composer-core 445/445, composer 485/485 (13 new) and block-bench 268/268; every other workspace passes.
      • The agent workspace is 49/50. Only rediscover > restoredConflict fails, at 5401 ms against its 5000 ms limit. It times out locally in every run; CI passes it, and this change does not touch apps/agent.
    • Smoke 39/39, agent e2e 49/49, and the exhibit 106 passed with 4 skipped.
    • Experimental 57/57, 21 of them new: the 12 Astryx surfaces, the 3 Preview specs and the 6 shadcn scans.
    • The composer build passes, with check-default-artifact: 29 files clean of 15 markers. So do the experimental build, the beta deploy check and the web build.
  • CI on d6b2859: green. test (run 39001296397) passed at 04:17:18Z, and Workers Builds' preview build of the branch passed.

Production unchanged where it must be.

  • Request bytes. The Phase 0 oracle in hosted-build.test.ts passes, and /api/models is pinned byte for byte by blocks-beta.test.mjs; both are in the composer unit run above. No request builder, Worker, sidecar or block code changed.
  • The default artifact at d6b2859 against main's (d7251db), built locally. 21 of 29 files are byte-identical. Its JavaScript is byte-identical to 09f4e60's build: every change to the artifact since is CSS. The 8 that differ:
    • the HTML and index.txt: Next's per-build ids and asset names only;
    • the CSS: the [data-astryx-canvas] rules and the header rule;
    • the app chunk holding the composer and the Astryx renderers: the step 6 code;
    • one Astryx vendor chunk, re-bundled because TableRender now imports Astryx's own proportional();
    • the page chunk: the new chunk names only, at the same size;
    • the webpack runtime: the new chunk names and hashes, and the minifier's renamed local variables, at the same size.
  • Production still serves build E_2CpGYfe1p1n1xjWkdjJ with main's CSS and runtime: checked at 04:21:42Z, after the last push's preview build and CI. Each push's Workers Builds run is a preview build of the branch.

Independent accessibility review.

  • The first review, of 057606a, found two blocking findings: the guide-steps table overlap, and focus hidden at 320 x 256. It also found four should-fix findings (dialog clipping, large as h3, button labels under text spacing, forced-colors field focus), nits, and gaps in the list of checks that need a person. All are addressed above, except the two left as they are, with reasons.

  • The second review, of 09f4e60, found nothing blocking in the three blocks, and confirmed five of the first review's six findings fixed. Its should-fix findings:

    • F1: inline dialogs cut off at 320 x 256 by their height cap;
    • F2: the "nothing drawn over" claim was broader than the checks.

    Its nits: F3 icon-only buttons, F4 long words in labels, F5 a tall sticky header from 721 to 960 px, and F6 doc wording. All are addressed above; the F4 remainder is recorded.

  • The verification of 0af44ba confirmed:

    • F1, F3 and F5 fixed;
    • F2 fixed, with a sampling gap;
    • F4 and F6 partly fixed.

    It found the three blocks ready for the owner's decision, apart from the checks that need a person. It asked for three things, all done in d6b2859:

    • N1, a fullscreen dialog's height;
    • the dialog footer wrap;
    • every line of text sampled.

    N3, covers that ignore the pointer, is fixed too, and N4 is recorded.

  • The final check of d6b2859 found it ready for the owner's decision on the three blocks, apart from the checks that need a person.

    • Everything listed is fixed: N1, the F4 footer, N2, N3, the overlap check, and the new spec.
    • The new spec failed on 0af44ba's build exactly on the two defects.
    • Nothing new needs fixing. One optional nit, a try/finally around the text check's temporary style, is left as is.
    • In a regression sweep, all 6 reference surfaces and the reviewer's 6 imported probe surfaces are clean at every size, apart from plan-comparison's known violation.

Still needs a person

No screen reader is installed here, and Chromium is the only browser: no NVDA, VoiceOver or TalkBack pass was done, and none is claimed. Still needed (ACCESSIBILITY.md has the detail):

  1. Screen-reader passes: NVDA with Firefox or Chrome, VoiceOver with Safari on macOS and iOS, and TalkBack on Android. They include listening to long and shared table-region names.
  2. A keyboard pass in Firefox and Safari.
  3. Real 200% and 400% browser zoom, at desktop window heights too; text-only zoom; text spacing with a bookmarklet.
  4. Windows forced colors, in a light and a dark theme, beyond Chromium's light emulation.
  5. Focus indicators with motion on: every automated pass runs with reduced motion.
  6. Touch on a phone, in portrait and landscape.
  7. Speech input (SC 2.5.3).
  8. Input purpose by hand, in a host (SC 1.3.5).
  9. guide-steps' tables by eye, in Build and Preview.
  10. Astryx surfaces that are not blocks: model-generated and imported.
  11. The composer shell on a tablet, and at 721 to 1000 px wide.
  12. The Studio exhibit's heading outline, now h1 and h2.
  13. The judgements in the proposed waivers.

Proposed waivers (for the owner)

  1. auth-form input purpose (SC 1.3.5): the recorded exception and host mitigation (docs/blocks/BETA.md). GA stays blocked.
  2. Astryx's resting field border, 1.5:1 (SC 1.4.11): a judgement. Each field is labelled, and its focus border is 5.4:1. The alternative is a canvas-only override in Composer.
  3. "Start" and "Save guide" (SC 2.4.6): template text, unchanged in this step.
  4. shadcn/ui v3 findings: not fixed, since shadcn/ui v3 is withheld. These are the wireframe Separator chips' contrast, and records-table's page overflow at 320 px.

🤖 Generated with Claude Code

https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi

…urfaces

Tests only; the fixes follow. Target: WCAG 2.2 AA on the Astryx beta path.

- e2e/composer-blocks-a11y.spec.ts (experimental config): builds
  records-table, auth-form and guide-steps on Astryx through the hosted
  path with a stubbed gateway (no model call), for each block's approved
  example and three boundary calls (smallest valid call, every optional
  prop at its maximum length and count, and the same with unbroken
  tokens). It checks axe with wcag2a, wcag2aa, wcag21a, wcag21aa and
  wcag22aa on the canvas with no exclusion; the title as the surface's
  one level-2 heading; table regions named by their columns; Tab and
  Shift+Tab order with no trap; a focus indicator of 3:1 that is never
  entirely hidden (SC 1.4.11, 2.4.11); 24 px targets; text spacing;
  200% zoom (640 px) and 320 px reflow, a table scrolling only inside
  its own region; and an accepted block in Preview. Checks are soft, so
  one run reports every defect. shadcn/ui v3 scans run for information
  only and never fail.
- apps/composer/app/astryx-renderers-a11y.test.ts: server-rendered
  checks of the shared Astryx renderers (headings, table names, header
  and badge wrapping, long-word wrapping).

Against main (d7251db): the renderer tests fail 8 of 10 (the two that
pass guard what must not change); the e2e fails all 13 Astryx tests
(no heading; tables named only "Table"; the studio's green focus ring
at 2.41:1 on the canvas white; header cells cut off; long text and the
auth form overflowing at 320 px; the page 11 px too wide at 320 px;
focused controls entirely hidden under the sticky header at 640 and
320 px). The 6 shadcn/ui v3 scans pass, as information.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
…heme

Tests only.

- A data table's columns get a minimum width (the renderer unit test).
  Without one, an 8-column table at 320 px crushed its first and last
  columns below a letter's width and cut their text off (the e2e's
  320 px reflow check, still red after the first fixes).
- The surface keeps axe and its focus indicators when the reader prefers
  a dark scheme (an e2e pass at 1280 px). Astryx's reset declares
  `color-scheme: light dark`, so the canvas's tokens turned dark inside
  a white canvas, and Astryx's dark primary button is 3.11:1 (white on
  #2694FE): 9 of 12 surfaces failed this pass.
- A traversal fix in the e2e: it reads each field's unfocused border
  after taking focus off the fields. The target-size check had left the
  last field focused, so the 640 px pass read that field's focused
  border as its unfocused one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
The shared Astryx renderers and the A2UI canvas. These reach the default
build too: every Astryx surface in Preview and the Studio gains them.

- Text: a heading variant renders as h1, h2 or h3 (Astryx Text renders
  heading elements for display types), so a block's title is a level-2
  heading (WCAG 1.3.1, 2.4.6). A container heading keeps role="heading"
  on its own text and never wraps its block children in a heading. Every
  variant breaks a long unbroken word (overflow-wrap: anywhere, 1.4.10).
  Astryx's reset already zeroes heading margins and sizes, so nothing
  moves.
- Table: the keyboard-scrollable region is named by its columns
  ("Table: Ticket, Subject, ...") instead of Astryx's "Table" for every
  table (2.4.6, 4.1.2); header text wraps instead of truncating with an
  ellipsis (1.4.4, 1.4.10); and a data column gets a 96 px floor with
  Astryx's proportional() widths, as Astryx advises, so a narrow table
  scrolls inside its region (the reflow exception for two-dimensional
  content) instead of crushing columns below a letter's width. Eight
  columns still fit the composer's canvas at 1280 px.
- Badge: a long label wraps inside the badge (a guide's facts ran out of
  the card at 640 and 320 px); a one-line badge keeps Astryx's height.
- A2uiCanvas: each surface's grid item may shrink with the canvas
  (min-width: 0); a form's inputs held it 24 px past a 320 px viewport.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
… draws light

Composer CSS scoped to [data-astryx-canvas] (Build and Preview). It ships
in the default build's stylesheet.

- Focus: the studio's unlayered :focus-visible ring (#97B063, 2.41:1 on
  the canvas white, below SC 1.4.11's 3:1) replaced Astryx's own. Inside
  the canvas, revert-layer hands each focused element back to Astryx: its
  accent ring on buttons, its accent border on fields (#0064E0, 5.4:1).
  Astryx's table scroll region has no indicator of its own, so it gets the
  same accent ring, inset because the region bleeds to its card's edge.
- Scheme: Astryx's reset declares `color-scheme: light dark` on the root,
  so a reader who prefers dark got Astryx's dark tokens inside the white
  canvas, where its primary button is 3.11:1 (SC 1.4.3 needs 4.5:1). The
  canvas now draws light, as its white background and the existing note
  in this file ("a global dark scheme would silently retint the canvas
  surfaces") intend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
… and fits 320 px

Two composer shell defects on the beta path's Build page; both ship in the
default build, and neither touches a request or a block code path.

- Focus not obscured (SC 2.4.11): Shift+Tab through a block surface at
  640 and 320 px left focused fields, buttons and short table regions
  entirely under the sticky header (141 and 255 px tall there). The root's
  scroll-padding-top now follows the header's measured height, so the
  browser scrolls a focused control into view below it.
- Reflow (SC 1.4.10): the Build options row's context select ("auto -
  inferred from your goal") made the page 11 px wider than a 320 px
  viewport. Each option now wraps under its label and is capped at the
  row's width.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
The shared A2uiCanvas change (f5c0ed9) let every surface's grid item
shrink with its canvas. The shadcn/ui v3 information-only scan then
showed its side effect outside the beta path: a wide shadcn table moved
from overflowing the page into shadcn's own table container, which is
not keyboard-focusable, so axe reported scrollable-region-focusable even
at 1280 px on the 8-column call. shadcn renderer fixes are deferred.

So A2uiCanvas is back to main's, and the same rule applies in composer
CSS to the Astryx canvas only: [data-astryx-canvas] [data-a2ui-surface]
{ min-width: 0 }. The Astryx results are unchanged (all pass), and the
shadcn scans match main's again (page overflow at 320 px from its wide
table; the wireframe Separator chips' contrast).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
…e authorization

Docs only.

- docs/blocks/ACCESSIBILITY.md (new): what the automated checks prove,
  each defect found on main and its fix, what this environment could and
  could not check by hand (no screen reader is installed; the
  accessibility tree as Chromium exposes it is recorded; keyboard by the
  test runner, in Chromium only), the checks that still need a person,
  the waivers proposed to the owner, and the shadcn/ui v3 scans as
  information.
- docs/blocks/BETA.md (new): the beta's known gaps, the auth-form SC 1.3.5
  exception first, with its host mitigation (each field's type and
  autocomplete token by its template id, paste and password managers,
  host-owned errors and credentials). The rest of this page is a §11
  deliverable for the plan's documentation step.
- The decision record gains "Step 6, authorized" with the owner's scope in
  the owner's words; the release plan's status note and docs/BLOCKS.md
  record that step 6 is built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
Docs only, and separate from step 6's accessibility work, so it can be
split out. When directing step 5's merge (#109),
the owner confirmed the field reading of the unmetered rule that
BETA-OPERATIONS.md left "the owner's to confirm", and accepted the
decision-record wording and the default artifact's minifier-only runtime
rename. The decision record quotes the four confirmations in the owner's
words under "Step 5, authorized"; BETA-OPERATIONS.md now says the reading
is confirmed. The step 6 section's opening now says when the owner
authorized it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
dspack-studio-composer d6b2859 Oct 17 2026, 04:08 AM

claude added 12 commits October 17, 2026 01:59
The independent review of 057606a found defects the spec could not see.
These tests catch each one, and fail against that head's build (all 14
Astryx e2e specs; 3 of 13 renderer unit tests):

- Content drawn over other content: each Astryx stack's children are
  checked for overlap. guide-steps' two tables overlapped by 20 px, and
  every table region by 4 px with the element after it. A contrast node
  axe leaves undecided because another element in the canvas covers it
  now fails, instead of being decided from colors.
- 400% zoom: 320 x 256 joins 640 x 512 and 320 x 720, with reflow, text
  spacing, axe and the keyboard pass at each. At 320 x 256 the sticky
  header hid every focused control (SC 2.4.11), in Build and Preview.
  The pass starts from script, so a covered canvas fails the check
  instead of blocking a click.
- Text spacing at the narrow sizes: a provider button's label was cut
  off at 320 px (SC 1.4.12).
- Forced colors: a keyboard pass where every stop must carry an
  outline. Fields showed only the caret.
- Every Astryx reference surface in Preview at 1280 px and the narrow
  sizes: the inline confirmation dialog was cut off at 320 px.
- Crushed columns: a header narrower than 90 px fails.
- Contrast arithmetic composites a translucent color over what lies
  beneath it.
- Renderer units: h3 (Astryx's body-scale `large`) is not a heading; a
  display variant without text renders no empty heading; a
  children-mode table's columns get the same 96 px floor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
…mode column floor

Review findings on the shared Astryx renderers:

- h3 is the profile's projection of Astryx's `large`, which the
  contract keeps body text ("Headings are expressed via the display
  types ..., never via the rendered element"). It rendered as <h3>; it
  is a paragraph again, as on main. h1 and h2, from the display types,
  stay headings.
- A display variant without text rendered an empty heading, which a
  screen reader announces blank. It renders a paragraph.
- A children-mode table (a model's nested cells; no block uses one)
  crushed six columns to 37 px at 320 px. Its header cells get the same
  96 px floor as data columns, so it scrolls in its focusable region. In
  Chromium's automatic table layout the floor holds: six columns make a
  588 px table that scrolls in a 200 px region, where they were 26 to
  47 px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
…is cut off, and fields keep focus in forced colors

Review findings, each caught by the new tests:

- Table regions overlapped their neighbours. Astryx bleeds a table's
  scroll region 16 px on every side; main's rule zeroing the vertical
  bleed matched only the Studio's [data-canvas], never Composer's
  canvas. guide-steps' two tables overlapped by 20 px, hiding a row
  behind the next header, and every table region overlapped the element
  after it by 4 px. The rule now matches [data-astryx-canvas] too.
- An inline Astryx dialog (400 px, capped at 90vw) was wider than a
  320 px canvas once surfaces narrowed with it: its edge and buttons were
  cut off. It now fits its container.
- Astryx keeps a button's label on one line with an ellipsis. With
  WCAG's text spacing at 320 px, "Continue with Microsoft" was cut off
  (SC 1.4.12). Labels wrap. Astryx's height for the button's size is a
  minimum, and the vertical padding is what one line leaves of it. So a
  one-line button keeps Astryx's height (32 px, measured), and a wrapped
  one grows.
- In forced colors an Astryx field's focus (an accent border and an
  inset shadow) disappeared, leaving only the caret. A focused field gets
  an outline there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
…oll away

At 320 x 256 (400% zoom of a 1280 x 1024 window) the sticky header was
255 px tall, its nav wrapped, and it covered the whole viewport: every
focused control was hidden under it (WCAG 2.2 SC 2.4.11), in Build and
Preview. Under 720 px wide, where the nav wraps, or under 600 px tall,
the header is static and scrolls away with the page.

The scroll padding that keeps focus clear of the header applies only
while the header is sticky. A resize re-checks it, since the header's
position can change without its size changing (1280 x 900 sticky,
1280 x 500 static, measured).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
docs/blocks/ACCESSIBILITY.md:
- The checks, with exact sizes: 1280 x 900, 640 x 512, 320 x 256 and
  320 x 720. Also overlap, forced colors, crushed columns, covered text,
  and both Preview specs.
- The review's fixes, marked "review", and the fail-first counts.
- The review's corrections. The table names are 2.4.6, not 4.1.2. The
  table region had the studio's ring, not none. The first round's sizes
  were 640 x 450 and 320 x 720. The Studio exhibit gets the renderer
  changes only, not Composer's canvas CSS or shell.
- "Still needs a person" gains TalkBack, desktop-height zoom,
  text-only zoom, Windows forced colors beyond Chromium's emulation,
  landscape (1.3.4), speech input (2.5.3), the guide-steps overlap by
  eye, and Astryx surfaces that are not blocks.
- Outside the blocks, for information: axe finds aria-required-children
  on the plan-comparison reference, unchanged by this step.

The release plan's step 6 note and docs/BLOCKS.md name the added
checks. Two CSS comments are corrected: the header is over 200 px
tall at 320 px wide (223 px measured with a short project name, not a
fixed 255), and a table overlaps the elements on both sides of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
The re-review of 09f4e60 found the spec's "nothing drawn over" claim
broader than its checks, and an inline dialog cut off at 320 x 256 that
no check saw. These checks close both gaps:

- Text, line by line: every line of text in the canvas is sampled
  across its width and height, after scrolling it into view. It fails
  when another element in the canvas draws over it, or when a box that
  does not scroll cuts it off. Text in a table's region may be scrolled
  out of view, and screen-reader-only text is skipped. It runs with
  every reflow measurement: 1280 px, every narrow size, and with text
  spacing at each.
- A box other than a table's region that scrolls its own content fails.
- Contrast axe leaves undecided is decided at every size and in dark,
  not only at 1280 px in light.
- Preview: the accepted block gets text spacing, axe and the contrast
  rule at each narrow size. Every Astryx reference surface gets axe,
  the contrast rule, reflow and text spacing at every size. One
  violation outside the blocks that predates step 6 is allowed by name:
  plan-comparison's aria-required-children, recorded in
  ACCESSIBILITY.md.

Against 09f4e60's build, only the reference spec fails: the
delete-project dialog at 320 x 256 scrolls its content inside a
108 px box, with and without text spacing, and axe reports
scrollable-region-focusable on it. Every block surface passes the
new checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
…ule spares icon-only buttons; the header scrolls away while its nav wraps

The re-review's findings on 09f4e60:

- F1. An inline dialog kept Astryx's 75vh height cap, and so did the
  Layout inside it, which reads --container-max-height. At 320 x 256 the
  delete-project dialog showed 108 px of its content in a scroll box of
  its own. axe flags that box as not keyboard-focusable
  (scrollable-region-focusable). In the canvas an inline dialog is part
  of the page, so it now takes its content's height. The new reference
  checks fail before this and pass after.
- F3. The button rule's `height: auto` met Astryx's aspect-ratio on an
  icon-only button. A clone with Astryx's compiled icon-only classes was
  976 x 976 in a stretching column, where Astryx draws it 976 x 32. The
  rule now applies only to a button named by its visible label. Astryx
  gives icon-only buttons and custom-content ones an aria-label and
  sizes them itself. Measured: 976 x 32 in a column, 32 x 32 in a row
  or a box, as in Astryx.
- F4. A label's unbroken word now breaks where it would overflow
  (overflow-wrap: break-word). A 70-character word fills a full-width
  button in three lines (204 x 72). `anywhere` was tried and rejected:
  it let a row of buttons split "Cancel" into "Canc / el" at 320 px.
  The remainder is a word wider than its whole row, which only a
  model-written label could hold; it is recorded in ACCESSIBILITY.md.
- F5. From 721 to 960 px wide the nav wraps by itself, and the sticky
  header was up to 325 px tall: 54% of a 601 px window. The header now
  scrolls away at 960 px wide or less, as well as at 600 px tall or
  less. Measured with a project open: static to 960 px, with no scroll
  padding; sticky from 961 px, 135 px tall, with its padding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
docs/blocks/ACCESSIBILITY.md now claims what the checks prove:
- the line-by-line text check;
- the scroll-box check;
- undecided contrast at every size and in dark;
- Preview's subset of checks, stated exactly;
- the one violation outside the blocks, allowed by name in the
  reference spec.

It records the detection evidence: each injected defect caught. It
also records the second review's fixes, marked "second review": the
dialog's height, word breaks in labels, icon-only buttons spared, and
the header static to 960 px.

"Still needs a person" gains:
- listening to long and shared table-region names;
- Windows high contrast in a dark theme too;
- focus indicators with motion on;
- the shell at tablet and 721 to 1000 px sizes;
- the Studio exhibit's heading outline.

"Outside the blocks" records the one residual from the label fix: a
model-written word wider than its whole row of buttons. The release
plan's step 6 note names the line-by-line check and the dialog fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
The verification of 0af44ba found gaps in the text check, and two
defects outside the blocks that no check reached:

- The text check now brings every line of a text to the middle of the
  window before sampling it. At 320 x 256 a tall cell's lines had run
  past the window unsampled: 487 of guide-steps' 940 lines. It also
  hit-tests every element in the canvas, so a cover that ignores the
  pointer no longer escapes. Neither change fails a block or reference
  surface.
- A new Preview spec imports two model-shaped surfaces the blocks never
  produce, under the same renderers and canvas rules. The first holds a
  standard, a fullscreen and an alert dialog with long labels. The second
  holds a children-mode table, Astryx's large type and display text with
  no words. Each is checked at every size with layout, text spacing,
  axe and the contrast rule; the second's headings are checked too.

Against 0af44ba's build only the new spec fails, with two defects:
- At 320 x 256 the fullscreen dialog keeps Astryx's 100dvh height and
  cuts its content off, and the next dialog draws over the overflow.
- With text spacing at 640 x 512 and 320 x 720, the alert dialog's
  footer cuts off "Keep my archived projects".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
The overlap check compared neighbours in a horizontal stack by their
edges alone. A row that wraps puts the next item on the line below, to
the left of the first one's right edge. That read as an overlap: a
false failure of 114 px once a dialog's footer row wraps, which the
next commit makes it do. Neighbours now overlap only where their boxes
intersect, in either direction.

Detection holds. With Astryx's 16 px vertical table bleed injected
again into guide-steps, the check reports the original 4, 20 and 4 px
overlaps; without it, none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
…ooter's buttons wrap

The verification of 0af44ba:

- N1. A fullscreen inline dialog kept Astryx's 100dvh height. At
  320 x 256 it cut its own content off, and the next dialog drew over
  the overflow. A canvas dialog's height is now its content's
  (`height: auto`), as for the height cap before. Standard and alert
  dialogs measure as they did.
- F4, the rest. An alert dialog's two long labels were each narrower
  than its footer but too wide together. With WCAG's text spacing at
  640 x 512 and 320 x 720, "Keep my archived projects" was cut off.
  Astryx's footer row now wraps, so each button takes a row of its own
  when they do not fit. At 320 px the delete-project dialog shows
  "Cancel" and then "Delete project", each whole; at 640 px they still
  sit side by side.

The spec that failed for both now passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
docs/blocks/ACCESSIBILITY.md now records the verification round:

- The text check scrolls every line into view and hit-tests every
  element, even one that ignores the pointer. It caught a cover over
  the last two lines of a 2,529 px cell at 320 x 256, and a cover with
  pointer-events: none. The check before the verification missed both;
  the record says so.
- Neighbours overlap only where their boxes intersect.
- A new spec imports model-shaped surfaces the blocks never produce:
  dialogs, a children-mode table, Astryx's large type and empty display
  text.
- The dialog row in "What changed" gains two fixes, marked
  "verification": a fullscreen dialog's viewport height, and footer
  buttons that wrap.

"Outside the blocks" narrows the label residual: a row of buttons
outside a dialog's footer whose longest words together are wider than
it. It also records the button rule's two latent edges: an icon-only
button with an empty label, and a labelled button while it loads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R6Ax8qvfN3gYUC85oSGnoi
@ryandmonk
ryandmonk merged commit 7ff13c9 into main Oct 17, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants