diff --git a/docs/architecture/component-style-authoring.md b/docs/architecture/component-style-authoring.md index ba98105e5d519..a267e49c07413 100644 --- a/docs/architecture/component-style-authoring.md +++ b/docs/architecture/component-style-authoring.md @@ -17,7 +17,13 @@ verified_by: internal/eslint-plugin-astryx/no-stylex-null-override.test.mjs, internal/eslint-plugin-astryx/no-style-only-wrapper.test.mjs, ] -deciding_specs: [spec:AST-080/DEC-4, spec:AST-017/DEC-26, spec:AST-017/DEC-29] +deciding_specs: + [ + spec:AST-080/DEC-4, + spec:AST-080/DEC-5, + spec:AST-017/DEC-26, + spec:AST-017/DEC-29, + ] --- # Component style authoring architecture @@ -101,9 +107,10 @@ not evidence of conformance or violation. in, including its transitive modules, and that component's gzip CSS delta in its package stylesheet (`spec:AST-080` FR8). A feature that runs only on a coarse pointer and adds more than 2 KB gzip of JavaScript or 1 KB gzip of CSS - to one component is flagged. Its pull request either loads that JavaScript - lazily, only when a coarse pointer needs it, so the component's import does - not carry it, or justifies the cost. For flagged CSS, its pull request + to one component is flagged. Its pull request either takes that JavaScript + from the shared internal touch modules (`architecture:react-component-runtime` + INV13), which a page carries once however many components use them, or + justifies the cost. For flagged CSS, its pull request justifies the cost, because that CSS stays in the package stylesheet (`spec:AST-080` FR1, FR8). Sizes are gzip at the default compression level, and 1 KB is 1,024 bytes; measured as `spec:AST-080` FR8 measures CSS, a @@ -274,6 +281,8 @@ before assigning a verdict. - `spec:AST-080/DEC-4` — every pull request reports component JavaScript and CSS size; touch-only JavaScript over 2 KB or CSS over 1 KB is flagged, and shared value tables ship only the rows a component uses (INV6). +- `spec:AST-080/DEC-5` — touch primitives come from the shared internal touch + modules, or the cost is justified; no component lazy-loads its own (INV6). - `spec:AST-017/DEC-26` — components are built from minimal DOM, with two budgets and an owner-recorded exception path; reducing DOM is compatible (INV7). diff --git a/docs/architecture/public-component-api.md b/docs/architecture/public-component-api.md index c84890ad9f231..15068b81b1469 100644 --- a/docs/architecture/public-component-api.md +++ b/docs/architecture/public-component-api.md @@ -136,7 +136,10 @@ guidance owns the process used to propose and test APIs. return, utility signature, default, or operation reachable from a supported package path is public even when its source comment, filename, or current use calls it internal. Privacy requires removing the export or moving the capability - behind a non-exported boundary. + behind a non-exported boundary. Core's `./internal/*` subpaths are the one + exception: reachable, but reserved for Core and Lab, undocumented, versioned + in lockstep with Core, limited by lint to those two packages, and outside + the compatibility promise (`spec:AST-017` FR21). - **INV13 — Public deltas route to their authority owner.** Every public API update and every public behavior change follows the result and disposition defined by `architecture:knowledge-contracts`. Tests, a Changeset, or a generated manifest @@ -271,7 +274,7 @@ copy the component matrix. | INV5, INV6, INV7 | BaseProps/passthrough lint and representative runtime tests | Consumer ARIA/data/style/events are dropped, clobber component semantics, or fail to compose | | INV9 | Published-surface, Changeset, migration, and public-type checks | A released API changes without explicit compatibility evidence | | INV11 | Public-API admission review plus owning theming/component tests | A public semantic custom property exposes derivable or unsupported implementation detail | -| INV12, INV13 | Generated declaration/export/behavior inventory plus canonical-owner mapping | A reachable supporting type or behavior is called internal, or mechanical evidence is treated as permission to accept it | +| INV12, INV13 | Generated declaration/export/behavior inventory plus canonical-owner mapping | A reachable supporting type or behavior outside `./internal/*` is called internal, or mechanical evidence is treated as permission to accept it | | INV14 | `AppShell.test.tsx` | A component hardcodes a divergent point or treats equality as below | | INV15 | Public-API review of the generated declaration inventory | A part or StyleX-typed styling prop ships without class-name and inline-style counterparts on the same element | | INV16 | Public-API review of touch presentations plus coarse- and fine-pointer runtime tests | A stable component that presents a different surface or switches an open surface's input mode under a coarse pointer and has no `presentation` value that keeps its fine-pointer surface and mode; such a touch mode callers cannot reach; a default that overrides an explicit caller choice | diff --git a/docs/architecture/react-component-runtime.md b/docs/architecture/react-component-runtime.md index 55aa43644fe40..d3f62df21a126 100644 --- a/docs/architecture/react-component-runtime.md +++ b/docs/architecture/react-component-runtime.md @@ -36,7 +36,8 @@ deciding_specs: [spec:AST-086/DEC-2, spec:AST-099/DEC-1, spec:AST-105/DEC-1] "INV8", "INV10", "INV11", - "INV12" + "INV12", + "INV13" ] } } @@ -174,6 +175,21 @@ those contracts. each moving element is one the page already renders (`architecture:component-style-authoring` INV7). The module follows INV3 and INV5. +- **INV13 — One shared set of internal touch modules.** Core's touch + behavior comes from one always-present set of internal gesture and touch + modules: the gesture core, the recognizers, the arena, the animator, and the + touch press recognizer. Components import them statically, and the bundler + keeps one copy per page however many components use them. There is no + install call, no public entry, and no public installed check; tests reset + the modules through an internal hook only. A component's own touch logic is + small glue in its own build over these modules. Where the primary pointer + is fine, no recognizer attaches and no touch listener is registered + (`spec:AST-094` FR19, FR24). The modules are not public exports and are not + documented for consumers; Core and Lab components reach them through + `@astryxdesign/core/internal/touch`, a subpath reserved for Astryx's own + packages: undocumented, outside the compatibility promise, versioned in + lockstep with Core, and limited by lint to Core and Lab (`spec:AST-017` + FR21). ## Authoring guidance @@ -310,14 +326,15 @@ observable-behavior predicates require review or runtime evidence. Likewise, a focused test proves only its named behavior and mutation expectation; a green component suite is not blanket conformance with every invariant. -| Invariant | Evidence | Failure signal | -| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| INV1–INV2 | Initial-render and interaction tests | Output flashes a known-wrong state, or one action is delayed or repeated through an Effect | -| INV3, INV5 | StrictMode setup/cleanup and dependency-change tests | Duplicate callback, leaked work, stale subscription, or missing resynchronization | -| INV1, INV4, INV6 | Ref attach/null/replacement, DOM-in-state rejection, and old-node cleanup-before-new-attach mutations | A DOM node or collection enters state, old node remains active, cleanup misses replacement, or ref mutation changes semantics silently | -| INV7 | Real-browser geometry plus N-consumer observer-instance/subscription mutations | Geometry is invalid, observer instances grow with repeated consumers, one unsubscribe silences peers, or cleanup removes another subscriber | -| INV8 | Owner-observable behavior across documented composition replacement; optional focused internal mechanism tests | Behavior disappears or changes owner when a documented subpart is replaced | -| INV9 | Provider/external-store identity and render-count tests | Unchanged semantic payload changes identity, or snapshot identity creates a render loop | -| INV10 | Shared test over every adopter of the announcement module | An adopter holding its own Effect, observer, timer, or listener for its own-content announcement | -| INV11 | Source boundary check plus hook tests across StrictMode replay and unmount | A second keyboard listener or measurement in Core; a consumer reading a CSS custom property on the root or a shared ancestor for the height; an export of the hook; a component outside Core measuring by a rule other than AST-099 FR2, or riding the keyboard; a listener left after unmount; a Core rider driven by JavaScript per frame or by its own transition; or more than one layout read per keyboard change | -| INV12 | Source boundary check plus real-browser tests on a throttled phone profile over a 10-level by 50-sibling tree and two changes in one frame | A per-frame height or layout, a walk past the area or into a subtree other than `display: contents`, an element moved twice for one frame, an added element, or an exported reporting function fails | +| Invariant | Evidence | Failure signal | +| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| INV1–INV2 | Initial-render and interaction tests | Output flashes a known-wrong state, or one action is delayed or repeated through an Effect | +| INV3, INV5 | StrictMode setup/cleanup and dependency-change tests | Duplicate callback, leaked work, stale subscription, or missing resynchronization | +| INV1, INV4, INV6 | Ref attach/null/replacement, DOM-in-state rejection, and old-node cleanup-before-new-attach mutations | A DOM node or collection enters state, old node remains active, cleanup misses replacement, or ref mutation changes semantics silently | +| INV7 | Real-browser geometry plus N-consumer observer-instance/subscription mutations | Geometry is invalid, observer instances grow with repeated consumers, one unsubscribe silences peers, or cleanup removes another subscriber | +| INV8 | Owner-observable behavior across documented composition replacement; optional focused internal mechanism tests | Behavior disappears or changes owner when a documented subpart is replaced | +| INV9 | Provider/external-store identity and render-count tests | Unchanged semantic payload changes identity, or snapshot identity creates a render loop | +| INV10 | Shared test over every adopter of the announcement module | An adopter holding its own Effect, observer, timer, or listener for its own-content announcement | +| INV11 | Source boundary check plus hook tests across StrictMode replay and unmount | A second keyboard listener or measurement in Core; a consumer reading a CSS custom property on the root or a shared ancestor for the height; an export of the hook; a component outside Core measuring by a rule other than AST-099 FR2, or riding the keyboard; a listener left after unmount; a Core rider driven by JavaScript per frame or by its own transition; or more than one layout read per keyboard change | +| INV12 | Source boundary check plus real-browser tests on a throttled phone profile over a 10-level by 50-sibling tree and two changes in one frame | A per-frame height or layout, a walk past the area or into a subtree other than `display: contents`, an element moved twice for one frame, an added element, or an exported reporting function fails | +| INV13 | Source boundary check plus bundle analysis of a page with several touch components, and real-browser tests on a throttled phone profile, under a fine pointer, and on a hybrid device | A second copy of a touch module on one page, a component bundling its own copy of a module's primitive, an `import()` of a module in a component, a public export or doc entry for the modules, an install call or installed check in the public API, a touch listener under a fine primary pointer, or an import of `@astryxdesign/core/internal/*` outside Core and Lab fails | diff --git a/docs/specs/AST-017/spec.md b/docs/specs/AST-017/spec.md index 0014be414a4a0..ff2e07d06d84a 100644 --- a/docs/specs/AST-017/spec.md +++ b/docs/specs/AST-017/spec.md @@ -373,7 +373,10 @@ one removal merged ahead of the decision to allow it. live inside one optional `experimental` prop object; omitting that object preserves the component's stable defaults and behavior. A behavior or utility module without a public contract, such as motion, gesture, or keyboard handling, stays internal: it is - never exported, as experimental or otherwise. In Core, an experimental public + never exported, as experimental or otherwise, except through Core's + `./internal/*` subpaths, which are reserved for Astryx's own packages, + undocumented, outside the compatibility promise, versioned in lockstep with + Core, and limited by lint to Core and Lab (`architecture:react-component-runtime` INV13). In Core, an experimental public API's status is visible at the call site where it is used, carried by the name the caller writes, not only in docs, declarations, or a file path; a component's @@ -433,7 +436,7 @@ one removal merged ahead of the decision to allow it. behavior fix under FR53; a breaking change lands only in a declared minor (FR31). Documentation catalog names and routes follow FR45. An internal label, low adoption, or lack of a source-level API change does - not weaken this rule. + not weaken this rule; Core's `./internal/*` subpaths are outside it (FR21). - **FR26 — Public lifecycle state is explicit.** Each public surface is `experimental`, `stable`, `deprecated`, or `removed`; private and unreleased surfaces remain outside the published lifecycle. Stable cannot become experimental. Deprecated @@ -873,7 +876,8 @@ Public lifecycle classification remains in force with these operational effects: alongside touch work, such as a new modal backdrop dim, becomes its own design decision (FR59); - no file path or subpath marks a surface experimental and Core's behavior and - utility modules without a public contract stay unexported (FR21). + utility modules without a public contract stay unexported, except through + the reserved `./internal/*` subpaths (FR21). Main declares the next planned version, and Changeset admission uses that declaration to refuse `[breaking]` work on a patch plan. Pull-request lint does not compare the @@ -899,34 +903,34 @@ lands, maintainers apply its requirement in review and release approval. ## Verification -| Contract | Verification | Representative states | Mutation or failure expectation | -| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| FR1–FR3 | PR compatibility statement plus latest stable package inspection | released export, narrowed type, changed interaction default; unreleased and private surface; visual-only spacing, typography, layout styling, or default appearance; a new touch presentation or open-surface touch mode as a default | A change is labeled from diff size or possibility alone, a functional contract loss is hidden as visual-only, or a pixel-only change is tagged `[breaking]` even though callers need no source or configuration change, or such a touch default names no `presentation` value that restores the previous surface and mode | -| FR4–FR6 | Old-usage type/runtime/CLI regression test | alias retained, deprecation warning, broad rewrite, low-adoption caller | Contractual old usage fails despite a nonbreaking label, or risk is substituted for compatibility | -| FR7–FR8 | `pnpm check:changesets` (tag, bump, contributors, declared tier) plus FR8 migration review of `[breaking]` text | existing `[fix]`, `[feat]`, `[breaking]`, and `[experimental]` tags paired with compatible, deprecation, planned-removal, or incompatible-fix metadata | A new lifecycle replaces an established tag, tag/metadata/bump diverge, an incompatible correction uses bare `[fix]`, or migration is unusable | -| FR9–FR13 | CLI contract tests, response-schema/type snapshots, text projections, generated consumer docs, and template catalog/output tests | slug rename, metadata edit, source rebuild, optional field addition, command or schema change | Catalog data is frozen as API, a command/schema incompatibility is mislabeled as catalog-only, or a response field lacks a complete projection | -| FR12 | Minimum and representative supported-version tests plus manifest and release-note review | retained range, narrowed range, adapter, coordinated upgrade | An in-range combination breaks under a nonbreaking label, or release coordination hides the affected package or migration | -| FR14 | Help/manifest snapshots, public API and consumer docs, and focused contract tests | command, option, API/config, private rollout/test hook | Supported behavior is hidden, an environment variable changes behavior, or automation lacks a documented API | -| FR15 | Full manifest-derived command matrix, supported-consumer evidence, and scope-specific contract tests | global invariant, scoped command group, single command, programmatic API | A global control is a no-op for any command, has different meanings, or replaces a narrower owning surface | -| FR16–FR18 | Boundary inventory, hostile side-effect probes, response snapshots, and concurrent API tests | known and new extension, partial result, text/JSON/API parity, independent concurrent calls | A route bypasses the guarantee, omitted work looks complete, or one invocation changes another | -| FR19–FR20 | Proposal evidence with a regression fixture for the detection failure, plus composition and provenance tests | convention covers the case, detection fails, app plus two integrations, refusal, failing contribution, repeated load | A key ships without a reproduced detection failure, a contribution displaces the app or applies twice, a part of the effective value has no inspectable source, or a failure silently weakens protection | -| FR21–FR24 | Published-surface comparison, declaration/doc metadata checks, import-boundary checks, and Changeset classification review | experimental prop object, experimental subpath, patch evolution, promotion, unpublished adjacent field, stable warning behavior, integration theme; a component or module `/experimental` subpath; an `_experimental` export; a behavior module | A prose-only marker excludes a stable API, adjacency falsely publishes a new field, an experimental export leaks through a stable path, a patch changes stable behavior, or a promoted API remains unprotected, a file path or subpath alone marks a surface experimental, a behavior, utility, or internal module without a public contract is exported, or an experimental public API in Core does not show its status at the call site in the name the caller writes | -| FR25–FR27 | Stable-surface inventory and experimental-boundary matrix | package/API, CSS/token, CLI/config, persistence, build tooling, docs, whole Lab component | An unmarked experiment changes stable defaults or a stable surface bypasses compatibility review | -| FR28–FR31 | Declared-tier check: on a minor, the `[breaking]` Changeset describes the change (old usage, replacement, migration); on a patch, a breaking Changeset is refused and the deprecation ships instead; declaration/doc/build/CLI warning projections; old/new fixtures; dry-run/idempotence/migration tests; maintained-source and latest-stable upgrade evidence | whole component, prop/value, theme field/helper/token/target/source, CLI command, CLI option; old-only, new-only, both, static/dynamic/uncertain migration, text/JSON, development/production | A warning has no usable modernization route, production runtime changes for deprecation, maintained guidance teaches the old surface, a migration guesses, old behavior degrades early, cadence invents a wait, or one cleanup id covers multiple incompatible deltas | -| FR32–FR35 | Latest-stable victim fixture, pre-existing authority named in the Changeset or pull request, owner review, declared-tier check, and emergency attestation | victim-free `[fix]`, `[fix]` plus incompatible-fix metadata, coexistence, cleanup transition, critical-harm emergency | A released victim uses bare `[fix]`, a new Changeset tag replaces metadata, a correction record disagrees with its public-surface state, or urgency bypasses migration | -| FR36–FR40 | Owner-declared target, locked final-patch receipt, three-way delta classification against the merged `[breaking]` Changesets, and separated release notes | ordinary cadence, immediate pair, cleanup, release metadata, pre/post-lock compatible fix | Cadence becomes eligibility, the final patch is recut for bookkeeping, a feature or incompatible fix enters the incidental-fix lane, or release notes merge cleanup with fixes | -| FR41–FR44 | Schema validation, PR declaration, semantic stable/base/head comparisons, exact-main gate, and immutable publish/rollback receipt | duplicate ids, missing evidence, route/schema removal, old-client metadata, partial publish, safe rollback | A label passes without semantics, a delta maps zero or multiple times, fixed-group membership drifts, or a release rebuilds under one identity | -| FR45 | Docs route inventory, repository-reference checks, and Changeset review | catalog entry rename, former-route miss, stable command and JSON schema | Catalog routing is frozen as API, or a rename silently changes the contractual command or response schema | -| FR46–FR47 | Declared-tier Changeset admission, canary publication ordering, and release-versioning fixtures | owner increase/decrease, above/equal/below stable, split group, `[breaking]` on patch and minor plans | Pending Changesets derive the plan, `[breaking]` passes on a patch or fails on a minor, equality publishes a canary, or a lower/split declaration publishes instead of failing | -| FR48 | Category and classification admission under each declared tier | patch release with `[breaking]`, patch release with a deprecation, minor release with `[breaking]` | Incompatible work passes in a patch release, or a deprecation is refused in one | -| FR49 | Malformed and inconsistent declaration fixtures | disagreeing group versions, canary identifier, release version unlike the cut manifest, already-released version, skipped version, no stable tag | An untrustworthy or mismatched declaration is released | -| FR50 | Three-way squash merge-back fixtures bound to the immutable tag, branch head, and plan digest | exact branch outputs, frozen deletion, branch-only marker exclusion, post-cut Changeset/manifest edit, newer main declaration, merge conflict, regenerated output | Merge-back differs from release bytes, lands lifecycle files, consumes post-cut work, overwrites a newer main declaration or unrelated field, or chooses a version | -| FR51 | Cut-time successor-plan preparation plus manifest/pin/lockfile/compatibility-asset and lifecycle fixtures | default patch successor, explicitly named minor, abort before any tag or publication, immutable tag before merge-back, pre-advanced main plan, unrelated main work during equality | The plan merges before the cut version is non-reusable, survives a terminal cut with neither an immutable tag nor stable publication, skips the default patch, derives a minor from pending Changesets, splits pins/assets, or the prepared plan or merge-back lowers or duplicates an existing newer plan | -| FR52 | Promotion fixtures plus every checked-in source consumer run before and after codemod promotion | staged transform, promoted transform, missing source, ambiguous candidates, changed entry identity | Evidence hardcodes only `next/`, keeps a duplicate source, silently chooses among candidates, or passes when the promoted transform no longer exposes the same identity and entries | -| FR53 | Changeset classification review against the governing component record's accessibility evidence | a fix that replaces documented keyboard or focus behavior to meet an adopted APG pattern's required parts; a fix that changes a role, state, or accessible name to meet WCAG 2.2 A/AA; a fix that adds an optional prop | An accessibility fix is held for a minor, given a coexistence path, or classified `[breaking]`; or a change that needs an app-layer code edit, or that removes, renames, or narrows public surface, ships as an accessibility `[fix]` | -| FR54 | Changeset classification review against the surface's current record, then the same tier admission every Changeset receives | a CLI correction the CLI record classifies as `[fix]`; a CLI change the CLI record leaves undecided; a CLI change the CLI record classifies as `[breaking]` on a patch plan; a component change | A non-component change is classified by a rule this record restates instead of its surface record, a surface rule turns on risk or adoption, a surface-classified `[breaking]` change ships in a patch or a surface rule makes it patch-eligible, or a component change is classified by a surface record | -| FR59 | Pull request review of fine-pointer before-and-after evidence | a touch change that also fixes a defect fine-pointer users hit; a deliberate cross-device consistency change; a touch-only change; a new fine-pointer look proposed with touch work | A touch change alters fine-pointer behavior or appearance with neither reason, shows no fine-pointer before-and-after evidence, or ships a new fine-pointer look without its own owner decision | -| FR60 | Element counts per story at rest and open, fine and coarse pointer, against architecture INV7 | a surface component at rest; an open layer; a DOM reduction; a recorded exception; an open layer under touch with extra elements | An element beyond INV7's budgets with no recorded owner exception, a hidden twin or second presentation, or a DOM reduction that removes a theme target or documented anatomy, or that is classified as breaking; an open layer more than 5 elements over its fine-pointer count, an added element that is themeable, a new anatomy part, or without a job in its record, or a closed layer, trigger, or repeated component above its fine-pointer count | +| Contract | Verification | Representative states | Mutation or failure expectation | +| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FR1–FR3 | PR compatibility statement plus latest stable package inspection | released export, narrowed type, changed interaction default; unreleased and private surface; visual-only spacing, typography, layout styling, or default appearance; a new touch presentation or open-surface touch mode as a default | A change is labeled from diff size or possibility alone, a functional contract loss is hidden as visual-only, or a pixel-only change is tagged `[breaking]` even though callers need no source or configuration change, or such a touch default names no `presentation` value that restores the previous surface and mode | +| FR4–FR6 | Old-usage type/runtime/CLI regression test | alias retained, deprecation warning, broad rewrite, low-adoption caller | Contractual old usage fails despite a nonbreaking label, or risk is substituted for compatibility | +| FR7–FR8 | `pnpm check:changesets` (tag, bump, contributors, declared tier) plus FR8 migration review of `[breaking]` text | existing `[fix]`, `[feat]`, `[breaking]`, and `[experimental]` tags paired with compatible, deprecation, planned-removal, or incompatible-fix metadata | A new lifecycle replaces an established tag, tag/metadata/bump diverge, an incompatible correction uses bare `[fix]`, or migration is unusable | +| FR9–FR13 | CLI contract tests, response-schema/type snapshots, text projections, generated consumer docs, and template catalog/output tests | slug rename, metadata edit, source rebuild, optional field addition, command or schema change | Catalog data is frozen as API, a command/schema incompatibility is mislabeled as catalog-only, or a response field lacks a complete projection | +| FR12 | Minimum and representative supported-version tests plus manifest and release-note review | retained range, narrowed range, adapter, coordinated upgrade | An in-range combination breaks under a nonbreaking label, or release coordination hides the affected package or migration | +| FR14 | Help/manifest snapshots, public API and consumer docs, and focused contract tests | command, option, API/config, private rollout/test hook | Supported behavior is hidden, an environment variable changes behavior, or automation lacks a documented API | +| FR15 | Full manifest-derived command matrix, supported-consumer evidence, and scope-specific contract tests | global invariant, scoped command group, single command, programmatic API | A global control is a no-op for any command, has different meanings, or replaces a narrower owning surface | +| FR16–FR18 | Boundary inventory, hostile side-effect probes, response snapshots, and concurrent API tests | known and new extension, partial result, text/JSON/API parity, independent concurrent calls | A route bypasses the guarantee, omitted work looks complete, or one invocation changes another | +| FR19–FR20 | Proposal evidence with a regression fixture for the detection failure, plus composition and provenance tests | convention covers the case, detection fails, app plus two integrations, refusal, failing contribution, repeated load | A key ships without a reproduced detection failure, a contribution displaces the app or applies twice, a part of the effective value has no inspectable source, or a failure silently weakens protection | +| FR21–FR24 | Published-surface comparison, declaration/doc metadata checks, import-boundary checks, and Changeset classification review | experimental prop object, experimental subpath, patch evolution, promotion, unpublished adjacent field, stable warning behavior, integration theme; a component or module `/experimental` subpath; an `_experimental` export; a behavior module; an `./internal/*` subpath imported from Core, from Lab, and from another package | A prose-only marker excludes a stable API, adjacency falsely publishes a new field, an experimental export leaks through a stable path, a patch changes stable behavior, or a promoted API remains unprotected, a file path or subpath alone marks a surface experimental, a behavior, utility, or internal module without a public contract is exported other than through `./internal/*`, an `./internal/*` subpath is documented or imported outside Core and Lab, or an experimental public API in Core does not show its status at the call site in the name the caller writes | +| FR25–FR27 | Stable-surface inventory and experimental-boundary matrix | package/API, CSS/token, CLI/config, persistence, build tooling, docs, whole Lab component | An unmarked experiment changes stable defaults or a stable surface bypasses compatibility review | +| FR28–FR31 | Declared-tier check: on a minor, the `[breaking]` Changeset describes the change (old usage, replacement, migration); on a patch, a breaking Changeset is refused and the deprecation ships instead; declaration/doc/build/CLI warning projections; old/new fixtures; dry-run/idempotence/migration tests; maintained-source and latest-stable upgrade evidence | whole component, prop/value, theme field/helper/token/target/source, CLI command, CLI option; old-only, new-only, both, static/dynamic/uncertain migration, text/JSON, development/production | A warning has no usable modernization route, production runtime changes for deprecation, maintained guidance teaches the old surface, a migration guesses, old behavior degrades early, cadence invents a wait, or one cleanup id covers multiple incompatible deltas | +| FR32–FR35 | Latest-stable victim fixture, pre-existing authority named in the Changeset or pull request, owner review, declared-tier check, and emergency attestation | victim-free `[fix]`, `[fix]` plus incompatible-fix metadata, coexistence, cleanup transition, critical-harm emergency | A released victim uses bare `[fix]`, a new Changeset tag replaces metadata, a correction record disagrees with its public-surface state, or urgency bypasses migration | +| FR36–FR40 | Owner-declared target, locked final-patch receipt, three-way delta classification against the merged `[breaking]` Changesets, and separated release notes | ordinary cadence, immediate pair, cleanup, release metadata, pre/post-lock compatible fix | Cadence becomes eligibility, the final patch is recut for bookkeeping, a feature or incompatible fix enters the incidental-fix lane, or release notes merge cleanup with fixes | +| FR41–FR44 | Schema validation, PR declaration, semantic stable/base/head comparisons, exact-main gate, and immutable publish/rollback receipt | duplicate ids, missing evidence, route/schema removal, old-client metadata, partial publish, safe rollback | A label passes without semantics, a delta maps zero or multiple times, fixed-group membership drifts, or a release rebuilds under one identity | +| FR45 | Docs route inventory, repository-reference checks, and Changeset review | catalog entry rename, former-route miss, stable command and JSON schema | Catalog routing is frozen as API, or a rename silently changes the contractual command or response schema | +| FR46–FR47 | Declared-tier Changeset admission, canary publication ordering, and release-versioning fixtures | owner increase/decrease, above/equal/below stable, split group, `[breaking]` on patch and minor plans | Pending Changesets derive the plan, `[breaking]` passes on a patch or fails on a minor, equality publishes a canary, or a lower/split declaration publishes instead of failing | +| FR48 | Category and classification admission under each declared tier | patch release with `[breaking]`, patch release with a deprecation, minor release with `[breaking]` | Incompatible work passes in a patch release, or a deprecation is refused in one | +| FR49 | Malformed and inconsistent declaration fixtures | disagreeing group versions, canary identifier, release version unlike the cut manifest, already-released version, skipped version, no stable tag | An untrustworthy or mismatched declaration is released | +| FR50 | Three-way squash merge-back fixtures bound to the immutable tag, branch head, and plan digest | exact branch outputs, frozen deletion, branch-only marker exclusion, post-cut Changeset/manifest edit, newer main declaration, merge conflict, regenerated output | Merge-back differs from release bytes, lands lifecycle files, consumes post-cut work, overwrites a newer main declaration or unrelated field, or chooses a version | +| FR51 | Cut-time successor-plan preparation plus manifest/pin/lockfile/compatibility-asset and lifecycle fixtures | default patch successor, explicitly named minor, abort before any tag or publication, immutable tag before merge-back, pre-advanced main plan, unrelated main work during equality | The plan merges before the cut version is non-reusable, survives a terminal cut with neither an immutable tag nor stable publication, skips the default patch, derives a minor from pending Changesets, splits pins/assets, or the prepared plan or merge-back lowers or duplicates an existing newer plan | +| FR52 | Promotion fixtures plus every checked-in source consumer run before and after codemod promotion | staged transform, promoted transform, missing source, ambiguous candidates, changed entry identity | Evidence hardcodes only `next/`, keeps a duplicate source, silently chooses among candidates, or passes when the promoted transform no longer exposes the same identity and entries | +| FR53 | Changeset classification review against the governing component record's accessibility evidence | a fix that replaces documented keyboard or focus behavior to meet an adopted APG pattern's required parts; a fix that changes a role, state, or accessible name to meet WCAG 2.2 A/AA; a fix that adds an optional prop | An accessibility fix is held for a minor, given a coexistence path, or classified `[breaking]`; or a change that needs an app-layer code edit, or that removes, renames, or narrows public surface, ships as an accessibility `[fix]` | +| FR54 | Changeset classification review against the surface's current record, then the same tier admission every Changeset receives | a CLI correction the CLI record classifies as `[fix]`; a CLI change the CLI record leaves undecided; a CLI change the CLI record classifies as `[breaking]` on a patch plan; a component change | A non-component change is classified by a rule this record restates instead of its surface record, a surface rule turns on risk or adoption, a surface-classified `[breaking]` change ships in a patch or a surface rule makes it patch-eligible, or a component change is classified by a surface record | +| FR59 | Pull request review of fine-pointer before-and-after evidence | a touch change that also fixes a defect fine-pointer users hit; a deliberate cross-device consistency change; a touch-only change; a new fine-pointer look proposed with touch work | A touch change alters fine-pointer behavior or appearance with neither reason, shows no fine-pointer before-and-after evidence, or ships a new fine-pointer look without its own owner decision | +| FR60 | Element counts per story at rest and open, fine and coarse pointer, against architecture INV7 | a surface component at rest; an open layer; a DOM reduction; a recorded exception; an open layer under touch with extra elements | An element beyond INV7's budgets with no recorded owner exception, a hidden twin or second presentation, or a DOM reduction that removes a theme target or documented anatomy, or that is classified as breaking; an open layer more than 5 elements over its fine-pointer count, an added element that is themeable, a new anatomy part, or without a job in its record, or a closed layer, trigger, or repeated component above its fine-pointer count | ## Decision log diff --git a/docs/specs/AST-080-package-stylesheets/spec.md b/docs/specs/AST-080-package-stylesheets/spec.md index 6779891b3d3cb..0f0e5d7c1a8e6 100644 --- a/docs/specs/AST-080-package-stylesheets/spec.md +++ b/docs/specs/AST-080-package-stylesheets/spec.md @@ -276,6 +276,19 @@ uses, so one component's tuning does not ride along with every other import. Rejected: splitting touch-only CSS into a separate stylesheet, because it breaks the one-import setup DEC-3 keeps. +### DEC-5 — Touch JavaScript comes from the shared internal touch modules + +**Reference:** `spec:AST-080/DEC-5` +**Decider:** `cixzhang`, `2026-10-26` + +Touch primitives over the budget come from the shared internal touch modules +components import (`architecture:react-component-runtime` INV13), which a +page carries once, or the cost is justified; a component does not lazy-load +its own. There is no install step: the modules are always present. This +supersedes DEC-4's lazy-load remedy; its reporting and budget stand. + +Rejected: lazy loading inside each component, and an app-installed module. + ## Open questions None.