Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
f1b86d7
test(interaction): real touch input for browser interaction proofs
vjeux Oct 24, 2026
4076bf3
spec(AST-094): touch presentations follow UIKit
vjeux Oct 24, 2026
28343b4
chore(core): the UIKit constants table for touch presentations
vjeux Oct 24, 2026
c88087c
chore(core): UIKit's spring, decay, rubber-band and projection math
vjeux Oct 24, 2026
081b6c5
feat(core): gesture recognizer core — UIKit's state machine and the a…
vjeux Oct 24, 2026
f423ffe
feat(core): tap and long-press recognizers with UIKit's measured thre…
vjeux Oct 24, 2026
4dc2aae
feat(core): one touch presentation resolver
vjeux Oct 24, 2026
706683b
fix(build): resolve defineConsts read across files in astryxStylex
vjeux Oct 24, 2026
2a8b491
chore(core): UIKit's spring, decay, rubber-band and projection math
vjeux Oct 24, 2026
60fc05c
chore(core): a compositor animator for touch presentations
vjeux Oct 24, 2026
968b85f
fix(interaction): release only the lifted finger on a partial lift in…
vjeux Oct 24, 2026
6f2b3e0
test(interaction): curve sampling and deterministic storyboards
vjeux Oct 24, 2026
eef784b
fix(interaction): curves end before a held end value; rest judges pre…
vjeux Oct 24, 2026
dbbdebc
test(interaction): frame evidence and the BottomSheet exemplar
vjeux Oct 24, 2026
6bd39ce
test(interaction): make the busy-main-thread verdict robust to a load…
vjeux Oct 24, 2026
5cb0543
feat(core): pan gesture recognizer with UIKit's hysteresis and velocity
vjeux Oct 24, 2026
7d58f27
feat(core): swipe, pinch, rotation, screen-edge pan and hover recogni…
vjeux Oct 24, 2026
1a76704
feat(core): touch-action derivation, native scroll probe and haptics
vjeux Oct 24, 2026
cfffebf
feat(core): gesture host — claims, click suppression and the browser'…
vjeux Oct 24, 2026
0453495
feat(core): UIControl press tracking, press profiles, the press paint…
vjeux Oct 24, 2026
398d4eb
test(core): gesture browser proof in Chromium and WebKit, and the README
vjeux Oct 24, 2026
e9f1e0a
chore(core): the animator plays any curve, drives a pseudo-element, s…
vjeux Oct 24, 2026
90c34de
Merge the interaction harness (#8045) under the animator's browser pr…
vjeux Oct 24, 2026
f328e32
chore(core): useAnimator and the animator's browser proofs on the int…
vjeux Oct 24, 2026
9d2d720
feat(core): the touch presentation resolver knows the iOS idiom
vjeux Oct 24, 2026
765a094
feat(core): themes retune touch motion through motion roles
vjeux Oct 24, 2026
f87a453
Merge the theme's motion roles into the animator branch
vjeux Oct 24, 2026
65fb64b
feat(core): the animator plays each table spring and curve as its the…
vjeux Oct 24, 2026
5f79e11
Merge the gesture system (#8074) under the experimental touch exports
vjeux Oct 24, 2026
8f0d9c7
Merge the touch presentation resolver (#8062) under the experimental …
vjeux Oct 24, 2026
eaf2189
feat(core): experimental motion and gesture subpaths for touch presen…
vjeux Oct 24, 2026
7065daf
fix(core): one touch model on every device; the adaptive change is a …
cixzhang Oct 24, 2026
92ef66c
Merge main into wip/uikit-pr-touch-presentation
cixzhang Oct 24, 2026
398c723
fix(core): the touch resolver reads the theme's compact definition
cixzhang Oct 24, 2026
33ee284
docs(MoreMenu): keep the doc's own formatting; change only the compac…
cixzhang Oct 24, 2026
63e5356
fix(core): the animator's Reduced Motion follows design:reduced-motion
vjeux Oct 24, 2026
18ff410
fix(core): under Reduced Motion, travel lands with a plain write whil…
vjeux Oct 24, 2026
512b368
Merge #8062 (the touch resolver reads the theme's compact definition)…
vjeux Oct 24, 2026
1203525
Merge commit '706683bb99' into uikit/pr-export-t2
vjeux Oct 24, 2026
0268a58
test(build): the cross-file constants fixture keys on a width, not it…
vjeux Oct 24, 2026
38edf28
chore(core): the constants table names only what exists, and dates ev…
vjeux Oct 24, 2026
0783fcd
fix(core): paging past the last page lands on a real page
vjeux Oct 24, 2026
b419011
fix(core): an identity at rest leaves the element
vjeux Oct 24, 2026
1264c5b
fix(core): compact follows the active theme's md; the branch drops it…
cixzhang Oct 24, 2026
8103ede
fix(core): the animator's model holds a curve's start until the brows…
vjeux Oct 24, 2026
cea95bb
test(core): a fixed child stays fixed after a motion back to identity
vjeux Oct 24, 2026
ec08340
docs(spec): drop this branch's stacked copy of AST-094
cixzhang Oct 24, 2026
1dcf753
fix(core): theme motion role ids are checked against the generated li…
vjeux Oct 24, 2026
5aafeaf
fix(core): a motion reads a theme switched earlier in the same task
vjeux Oct 24, 2026
a5ec352
fix(core): resolveMotion observes and listens to nothing
vjeux Oct 24, 2026
6f81139
Merge #8075 (defineConsts read across files) into the experimental su…
vjeux Oct 24, 2026
9d8226d
feat(core): the Reduce Motion media keys are StyleX constants
vjeux Oct 24, 2026
ced7766
docs(ContextMenu, MoreMenu): the compact edge is the theme md breakpoint
cixzhang Oct 25, 2026
fe9fbba
fix(core): reading a motion table registers nothing, and a theme ship…
vjeux Oct 25, 2026
9fff2ac
fix(core): under Reduced Motion, two calls in one frame both land
vjeux Oct 25, 2026
14fe728
feat(core): export the Reduce Motion keys for other packages
vjeux Oct 25, 2026
7c604ea
test(lab): say what the Reduce Motion keys import test proves
vjeux Oct 25, 2026
dbd6d17
fix(core): Reduce Motion keys stay literal in a transition
vjeux Oct 25, 2026
0b81c86
fix(core): a retarget of a curve the browser has not started keeps it…
vjeux Oct 25, 2026
970c908
fix(core): a call on the clock of a waiting call from rest starts wit…
vjeux Oct 25, 2026
7091f8b
fix(core): experimental touch hooks stay out of stable search and doc…
vjeux Oct 25, 2026
4d728a7
refactor(core): drop the unused isCompactTouch from the touch present…
vjeux Oct 25, 2026
d34d800
test(core): the adaptive menu's compact edges in Chromium and WebKit
vjeux Oct 25, 2026
9960f84
refactor(core): export the animator's settle thresholds
vjeux Oct 25, 2026
65ac34a
test(core): a move during a Reduce Motion dip's fade out ends visible
vjeux Oct 25, 2026
5cb103f
fix(core): a time ahead reads a waiting curve from its call's time
vjeux Oct 25, 2026
49bb28a
fix(core): under Reduce Motion, only a call's own opacity makes it an…
vjeux Oct 25, 2026
189de88
fix(core): theme adaptations have no platform condition
vjeux Oct 25, 2026
601b54e
refactor(core): the animator reads a coarse pointer through TOUCH_QUERY
vjeux Oct 25, 2026
874ca93
docs(core): theme motion examples name a role that exists
vjeux Oct 25, 2026
2164696
fix(core): a page registers and ships only the motion tables it anima…
vjeux Oct 25, 2026
3e2d5e3
test(core): the role registry test checks every table discovery regis…
vjeux Oct 25, 2026
b496085
Merge main into the experimental subpaths (lab typechecks with its te…
vjeux Oct 25, 2026
c5efd27
Merge main into #8062 (lab typecheck fix aa3b92f320 and current main)
vjeux Oct 25, 2026
7618bd8
test(core): the theme browser proof registers the springs table it re…
vjeux Oct 25, 2026
ab35d4b
docs(core): state how a decay behaves under Reduce Motion
vjeux Oct 25, 2026
9dc4ae9
docs(cli): the theme template names no platform condition
vjeux Oct 25, 2026
b8642ba
test(core): the touch presentation's browser proofs leave for the des…
vjeux Oct 25, 2026
f809dae
test(DropdownMenu): a sub-menu's drill-in follows a theme that moves md
vjeux Oct 25, 2026
4b5e872
refactor(core): readTouchPresentation takes the compact definition it…
vjeux Oct 25, 2026
b91dd6c
refactor(core): motion and gesture stay internal, with no experimenta…
cixzhang Oct 25, 2026
54a6ffe
Merge #8062 (the compact definition follows the theme's md point) int…
vjeux Oct 25, 2026
a21ddd7
fix(core): Reduced Motion takes no override
cixzhang Oct 25, 2026
5617fbb
fix(core): the Reduce Motion constants stay internal, and the touch c…
vjeux Oct 25, 2026
ba4ce66
test(core): a component reaches a motion table only by importing it
vjeux Oct 25, 2026
a71f4e5
build(lab): Lab type-checks core's internal motion and gesture modules
vjeux Oct 25, 2026
8e4b380
feat(core): code outside a component relates to its gesture recognize…
vjeux Oct 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/build-stylex-cross-file-consts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/build': patch
---

[fix] `astryxStylex` resolves `defineConsts` values read from another file. With the `{stylexOptions}` form (the one Storybook uses), a file that read only constants from a `.stylex.ts` module lost that import once StyleX compiled it, so the constants never reached the CSS: a constant used as a media-query key failed the build ("Invalid empty selector") and one used as a value shipped as a custom property nothing declares. A constant defined on the library side of the layer split and read on the product side stayed unresolved in either form. A production build now also warns, naming the variable and the rule that reads it, when its stylesheet reads a StyleX-generated custom property that nothing declares; set `undefinedVariables: 'error'` to fail the build instead.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-browser-negotiation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: touch-action derivation, the native scroll probe and haptics for the gesture system. Not exported from the package entry point; no component uses them yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-delegates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: code outside a component relates to the component's own gesture recognizers by name, the way a UIKit app uses a recognizer's name and its own delegate (`gestureNamed`, `addGestureDelegate`, `useGestureDelegate`). Not exported from the package entry point; no component uses it yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-host.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the gesture system's DOM host (claims, click suppression, the browser's takeover, the pointer behind a contact). Not exported from the package entry point; no component uses it yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-pan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the pan gesture recognizer, with UIKit's measured hysteresis and velocity filter. Not exported from the package entry point; no component uses it yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-press-tracking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: UIControl press tracking with control and cell profiles, the shared press paint, delegated press tracking and the gesture hooks. Not exported from the package entry point; no component uses them yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the gesture system's README for component authors, with its browser proof in Chromium and WebKit.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-recognizer-core.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the gesture recognizer core for touch presentations: UIKit's recognizer state machine and the arena that settles which recognizer owns a touch. Not exported from the package entry point; no component uses it yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-swipe-pinch-rotation-edge-hover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: swipe, pinch, rotation, screen-edge pan and hover gesture recognizers. Not exported from the package entry point; no component uses them yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-tap-long-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: tap and long-press gesture recognizers with UIKit's measured thresholds. Not exported from the package entry point; no component uses them yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/reduced-motion-record.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[fix] The experimental touch animator follows design:reduced-motion. Under Reduce Motion every `animate` and `play` (travel or opacity, on a spring or any curve) becomes an opacity-only cross-fade of 200 ms on a coarse primary pointer, or for a call marked `touchRelease`, and completes at once on any other pointer; nothing moves, scales or springs, and travel lands with a plain style write while the element is transparent, never as an animated transform. `timing.reducedMotionCrossFadeMs` is now 200 ms and is no longer a theme role. The `reducedMotion` and `crossfade` call options are removed; `crossfadeAs` stays. `animator.reducedMotion()` reports the mode for feedback with no fade of its own, `reducedMotionFade()` gives the same fade to code beside the animator, and `reducedMotion.stylex.ts` holds its constants for StyleX transitions.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The animator and reducedMotion.stylex.ts are internal (no ./motion export), so this [fix] would announce removed options on an "experimental" API nobody can import. Please write it as [chore] Internal: … like the sibling changesets, or fold it into uikit-motion-animator-curves.md.


@vjeux
8 changes: 8 additions & 0 deletions .changeset/touch-motion-theme-roles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@astryxdesign/core': patch
'@astryxdesign/cli': patch
---

[feat] Themes can retune touch motion roles. `defineTheme({motion})` takes `springs`, `durations` and `curves` keyed by motion role, the table path of a measured touch motion value (`springs.sheetPresent`, `navigation.transitionMs`), at the root or in an adaptation rule, and writes one `--motion-*` variable per role it sets. A role the theme leaves out keeps its measured default, an unknown role id is refused, and the duration scale (`fast`, `medium`, `ratio`) is optional when a theme sets only roles. No shipped component reads the roles yet. `astryx docs motion` lists every motion role with its CSS variable and default, and the theme authoring guide and template describe the new fields.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/touch-presentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[feat] `presentation="adaptive"` on DropdownMenu, ContextMenu, MoreMenu, Selector and MultiSelector, and a DropdownMenu's or ContextMenu's default sub-menu drill-in, now use the theme's compact definition with a coarse primary pointer: a viewport narrower than the theme's `md` point (768px by default) or shorter than 500px. This is a behavior change. The old default was a bottom sheet at 768px wide and below on a coarse pointer. Now a coarse-pointer screen exactly 768px wide keeps the anchored popover, and a phone in landscape (wider than 768px but shorter than 500px) gets the bottom sheet and the drilled-in sub-menu. A theme that moves `md` moves the edge with it. To pin a component's presentation regardless of the rule, pass `presentation="popover"` or `presentation="bottom-sheet"`; to keep a sub-menu's flyout, pass `presentation="flyout"` on `DropdownMenuSubMenu`. Mouse and keyboard users on desktop see no change, because every case requires a coarse pointer.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/uikit-constants-table.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the UIKit constants table for touch presentations (springs, scroll physics, timing curves and navigation values), each value with its source and a checked list of the simulator measurements still owed. Not exported from the package entry point; no component reads it yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/uikit-motion-animator-curves.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the touch presentations' animator plays any closed-form curve (such as a spring chasing a moving target), drives a pseudo-element such as a dialog's ::backdrop, follows design:reduced-motion (under Reduce Motion, an opacity-only cross-fade of 200 ms on a coarse pointer or after a touch release and an immediate change on any other pointer, with no option or theme value that keeps travel or lengthens the fade; an entrance or exit role orients a slide that changes no opacity), and starts a call from rest when the browser commits it, as Core Animation does. Not exported from the package entry point.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/uikit-motion-animator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: a compositor animator for touch presentations. It plays UIKit's springs, scroll coasts and any closed-form curve as Web Animations on translate, rotate, scale and opacity, so they keep running while JavaScript is busy, and reads values and velocities back from the curves for interruption, retargeting and Reduce Motion cross-fades. Not exported from the package entry point; no component uses it yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/uikit-motion-math.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the spring, decay, rubber-band and projection math of UIKit for touch presentations, as pure functions checked against an independent integration and against values read from UIKit. Not exported from the package entry point; no component uses it yet.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/uikit-motion-use-animator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: useAnimator, a React wrapper around the touch presentations' animator that never re-renders per frame, and browser proofs on the interaction harness that the animator's motion keeps running through a blocked main thread in Chromium and WebKit. Not exported from the package entry point.

@vjeux
49 changes: 49 additions & 0 deletions .github/scripts/ci-test-routing.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -612,3 +612,52 @@ describe('deploy.yml push gating', () => {
}
});
});

describe('ci.yml interaction proofs', () => {
const workflow = load('ci.yml');
const job = workflow.jobs['pr-interaction'];
const step = name => job.steps.find(candidate => candidate.name === name);

it('runs the interaction config in Chromium against the exact PR head', () => {
expect(KNOWN_RUNNERS.has(job['runs-on'])).toBe(true);
expect(job.needs).toEqual(
expect.arrayContaining(['check-scope', 'build-storybook']),
);
const checkout = job.steps.find(candidate =>
candidate.uses?.startsWith('actions/checkout@'),
);
expect(checkout?.with?.ref).toBe(
'${{ github.event.pull_request.head.sha }}',
);
const run = step('Run interaction proofs').run;
expect(run).toContain('--config playwright.interaction.config.ts');
expect(run).toContain('--project chromium');
const packageJson = JSON.parse(read('package.json'));
expect(packageJson.scripts['test:interaction']).toBe(
'playwright test --config playwright.interaction.config.ts',
);
});

it('runs whenever the harness, its config, a component or a story changes', () => {
const scope = step('Check interaction scope').run;
expect(scope).toContain('.github/scripts/pr-scope-touches.sh');
for (const path of [
'internal/interaction-harness/',
'playwright\\.interaction\\.config\\.ts',
'packages/[^/]+/src/',
'apps/storybook/',
]) {
expect(scope).toContain(path);
}
// Unknown scope (exit 2) runs the proofs; only "out of scope" skips them.
expect(scope).toContain('if [ "$scope" = 1 ]; then');
});

it('typechecks the harness in both workflows', () => {
for (const file of ['ci.yml', 'deploy.yml']) {
expect(runLines(load(file).jobs.typecheck), file).toContain(
'@astryxdesign/interaction-harness typecheck',
);
}
});
});
88 changes: 88 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,12 @@ jobs:
if: needs.check-scope.outputs.docsite_only != 'true' && needs.check-scope.outputs.spec_only != 'true' && needs.check-scope.outputs.tooling_only != 'true'
run: pnpm -F @astryxdesign/native typecheck:types

# The interaction harness is consumed as TypeScript source by Vitest and
# Playwright, which do not typecheck; this is the only gate over it.
- name: Typecheck interaction harness
if: needs.check-scope.outputs.docsite_only != 'true' && needs.check-scope.outputs.spec_only != 'true' && needs.check-scope.outputs.tooling_only != 'true'
run: pnpm -F @astryxdesign/interaction-harness typecheck

# Sandbox preview build — the single slowest piece of PR CI (~4min), run as
# its own job so it overlaps build-storybook instead of serializing after it.
# PR-only: merge_group runs never deploy previews, and docsite-only PRs skip
Expand Down Expand Up @@ -3803,6 +3809,88 @@ jobs:
retention-days: 30
delete-merged: true

# Touch interaction and animation proofs: the harness in
# internal/interaction-harness and every *.interaction.browser.spec.ts.
# Real touch input, composited frames, rendering traces and presented
# animation values only exist in a real engine, and the specs read stories
# from this PR's own Storybook build. The harness's self-test runs with
# them, so a change to an instrument is proven by its negative controls
# before a component spec relies on it. Chromium only on these runners;
# see the harness README for running WebKit.
pr-interaction:
needs: [check-scope, build-storybook]
if: github.event_name == 'pull_request' && needs.check-scope.outputs.docsite_only != 'true' && needs.check-scope.outputs.spec_only != 'true' && needs.check-scope.outputs.tooling_only != 'true'
# Frame evidence measures timing: four cores, one Playwright worker.
runs-on: 4-core-ubuntu
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.head.sha }}

- name: Verify evidence checkout
env:
EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: test "$(git rev-parse HEAD)" = "$EXPECTED_HEAD_SHA"

# The helper comes from the base, since an older PR head lacks it.
# Exit 1 means out of scope; 0 in scope; 2 unknown, so run.
- name: Check interaction scope
id: scope
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
scope=2
helper="$(mktemp)"
if git fetch --quiet --no-tags --depth=1 origin "$BASE_SHA" \
&& git show "$BASE_SHA:.github/scripts/pr-scope-touches.sh" > "$helper"; then
scope=0
bash "$helper" "$BASE_SHA" '^(internal/interaction-harness/|playwright\.interaction\.config\.ts$|packages/[^/]+/src/|apps/storybook/)' || scope=$?
fi
if [ "$scope" = 1 ]; then
echo "Neither the interaction harness nor a component changed; skipping the interaction proofs."
echo 'run=false' >> "$GITHUB_OUTPUT"
else
echo 'run=true' >> "$GITHUB_OUTPUT"
fi

- name: Setup Node and pnpm
if: steps.scope.outputs.run == 'true'
uses: ./.github/actions/setup
with:
install: 'false'

- name: Download Storybook artifact
if: steps.scope.outputs.run == 'true'
uses: actions/download-artifact@v8
with:
name: storybook-${{ needs.build-storybook.outputs.short_hash }}
path: apps/storybook/dist

- name: Install Playwright
if: steps.scope.outputs.run == 'true'
run: pnpm install --frozen-lockfile && npx playwright install chromium

- name: Run interaction proofs
if: steps.scope.outputs.run == 'true'
run: |
pnpm exec playwright test \
--config playwright.interaction.config.ts \
--project chromium \
--output interaction-proof-output

# Storyboards (PNG contact sheets) and curve traces, for review.
- name: Upload interaction evidence
if: always() && steps.scope.outputs.run == 'true'
uses: actions/upload-artifact@v7
with:
name: interaction-evidence
path: test-results/interaction-evidence
if-no-files-found: ignore
retention-days: 7

# Build package test owner. The plugin's production CSS partition is a Build
# integration contract: source inspection cannot prove which declaration
# paints after Vite emits the final bundle. The `test-build` name makes that
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ jobs:
- name: Typecheck accessibility spec-test contracts
run: pnpm -F @astryxdesign/a11y-spec typecheck

- name: Typecheck interaction harness
run: pnpm -F @astryxdesign/interaction-harness typecheck

# Stable join for the three post-merge validation lanes.
test:
needs: [test-ui, test-node, typecheck]
Expand Down
1 change: 1 addition & 0 deletions apps/sandbox/vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ export default defineConfig({
unstable_moduleResolution: stylexResolution.stylexModuleResolution,
},
stylexPrefix: 'x',
undefinedVariables: 'error',
}),
react({
babel: {
Expand Down
6 changes: 6 additions & 0 deletions apps/storybook/.storybook/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const runtimeImports = [
describe('storybook main.ts workspace source aliases', () => {
let viteAliases: Record<string, string>;
let stylexAliases: Record<string, string[]>;
let undefinedVariables: unknown;
const stylexSpy = vi.spyOn(build, 'astryxStylex');

beforeAll(async () => {
Expand All @@ -69,6 +70,11 @@ describe('storybook main.ts workspace source aliases', () => {
throw new Error('Expected the Storybook StyleX options');
}
stylexAliases = options.stylexOptions!.aliases as Record<string, string[]>;
undefinedVariables = options.undefinedVariables;
});

it('fails the build on a generated variable nothing declares', () => {
expect(undefinedVariables).toBe('error');
});

afterAll(() => stylexSpy.mockRestore());
Expand Down
3 changes: 3 additions & 0 deletions apps/storybook/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ const config: StorybookConfig = {
},
},
libraryPattern: 'packages/',
// CI catches a style that reads a generated variable nothing
// declares (a dropped declaration) instead of shipping it.
undefinedVariables: 'error',
}),
],
resolve: {
Expand Down
2 changes: 1 addition & 1 deletion apps/storybook/stories/ContextMenu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ export const AdaptivePresentation: Story = {
docs: {
description: {
story:
'Uses the built-in adaptive policy: BottomSheet at 768px and below with a coarse primary pointer, cursor-positioned popover otherwise. Important actions still need a visible MoreMenu or equivalent entry point.',
'Uses the built-in adaptive policy: BottomSheet on a compact touch screen (a coarse primary pointer, and a viewport narrower than the theme `md` breakpoint (768px by default) or shorter than 500px), cursor-positioned popover otherwise. Important actions still need a visible MoreMenu or equivalent entry point.',
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion apps/storybook/stories/MoreMenu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ export const AdaptivePresentation: Story = {
docs: {
description: {
story:
'Uses the built-in adaptive policy: BottomSheet at 768px and below with a coarse primary pointer, anchored popover otherwise. Use device emulation to exercise the touch branch.',
'Uses the built-in adaptive policy: BottomSheet on a compact touch screen (a coarse primary pointer, and a viewport narrower than the theme `md` breakpoint (768px by default) or shorter than 500px), anchored popover otherwise. Use device emulation to exercise the touch branch.',
},
},
},
Expand Down
13 changes: 7 additions & 6 deletions internal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ Internal tooling packages not published to npm.

<!-- SYNC: When files in this directory change, update this document. -->

| Directory | Role | Purpose |
| ------------- | ------- | ----------------------------------------------------------------------- |
| `a11y-spec/` | Testing | Reusable accessibility spec-test contracts and their evidence harnesses |
| `scripts/` | Tooling | One-off automation scripts (Crowdin screenshot upload, etc.) |
| `test-utils/` | Testing | Shared testing utilities and setup for Vitest |
| `vibe-tests/` | Testing | LLM vibeability test harness for component system |
| Directory | Role | Purpose |
| ---------------------- | ------- | --------------------------------------------------------------------------------- |
| `a11y-spec/` | Testing | Reusable accessibility spec-test contracts and their evidence harnesses |
| `interaction-harness/` | Testing | Playwright instruments for touch interaction and animation proofs and storyboards |
| `scripts/` | Tooling | One-off automation scripts (Crowdin screenshot upload, etc.) |
| `test-utils/` | Testing | Shared testing utilities and setup for Vitest |
| `vibe-tests/` | Testing | LLM vibeability test harness for component system |
Loading
Loading