Skip to content

chore: Predictability - #80

Merged
codecaaron merged 16 commits into
mainfrom
chore/dechunk
Aug 7, 2026
Merged

chore: Predictability#80
codecaaron merged 16 commits into
mainfrom
chore/dechunk

Conversation

@codecaaron

Copy link
Copy Markdown
Owner

No description provided.

codecaaron and others added 16 commits August 6, 2026 14:26
… slot (ANI-028, ANI-029)

addConditions/addSelectors now reject keys colliding with the opposite
alias registry (built-in or chain-registered) via branded validators in
value position, keeping the record type param a naked inference site.
NarrowedAliases guards the gate against widened Record registrations so
non-literal maps cannot poison subsequent legal calls; the
construction-time throw remains the backstop. compose() and
composeWithContext() now require the exact PascalCase Root slot in their
input type; the runtime throw stays for untyped consumers and gains its
previously missing compose()-side test.

Type tests pin all four collision directions, extend()-sourced alias
threading, same-registry overrides staying legal, widened-registry
tolerance, and phantom-union inference survival through the validators.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nit fallback

The runtime drop warning and style witness were gated behind
typeof-process checks that no bundler rewrites, so browser bundles never
fired them in dev or prod. Both now share a single IS_DEV constant that
survives define-replacement, stays silent in production, and cannot
throw under the QuickJS system-loader eval (which has no process and no
import.meta).

The shared dynamicPropConfig read the manifest's fields in snake_case
while the engine serializes camelCase, so every entry shipped as {} and
the whole shared dynamic path was dead at runtime. The builder now reads
the real spelling (legacy accepted), fails loud when neither is present,
and carries property/properties so the unit fallback tests CSS
properties against the unitless set instead of --animus-* var names —
numerics on unitless properties no longer gain a wrong px suffix.

Tests: vm host-matrix for the dev gate, engine-shaped manifest fixture
for the config builder, and unit-fallback coverage for single, multi,
and mixed property sets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ails, token-candidate membership

Selector lists now split on top-level commas only (paren/bracket/quote
aware), and stored branches keep authored descendant combinators — a
comma list like '& p + ul, & ul + p' no longer emits concatenated
non-matching selectors, and :is()/:has() arguments survive intact. The
comma-bearing first branch also sorts on its full text, fixing a
mis-tier when the matching token lived in the truncated tail.

Variant stage parsing records a skip diagnostic for every shape it
cannot read (identifier maps, spreads at map and config level,
non-static base/prop/defaultVariant) instead of silently emitting a
class with zero CSS behind a surviving default.

Extension parents that resolve inside the analyzed universe but are not
extractable chains now bail loudly ("could not resolve parent
component" / "failed evaluation before extension") instead of silently
emitting only the child's delta; aliased local exports resolve through
the export fact to their declarator, so 'export { Impl as Public }'
parents genuinely inherit; import-then-re-export barrels and true
external parents keep the standalone fallback.

External-token candidates are checked against the consumer theme before
being reported: a non-emitted identity-valued token (space.0 -> '0')
resolves byte-identically to its key and previously round-tripped into
a false "missing token" strict failure.

Member-expression skip reasons now name the binding and distinguish
undiscovered keyframes collections, missing members, and evaluation
without extraction-time statics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Single registered drift (intent extension-bail-witness-20260806): the
per-file extension-child unit's silently-dropped chain now leaves the
'could not resolve parent component' bail diagnostic. Diagnostics
surface only, both modes; all other units byte-identical. Register
retired after refresh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e-gated invalidation

The HMR bridge is now delivered as a dev-only head-prepend script tag
from transformIndexHtml instead of an import spliced into one
transformed module behind a server-lifetime flag — delivery no longer
depends on any single module's transform result surviving Vite's
invalidation paths (the deps-optimizer's invalidateAll left documents
with zero adopted stylesheets until restart).

transformSource rejects virtual ids before the extension gate, ending
ghost fileCache entries and two spurious whole-project analyses per
cold start. New-file detection logs at standard level per spec. Changed
files are read through Vite's retry-guarded read() so an editor's
atomic save can no longer poison the cache with empty content. Files
that are both system dependencies and component sources stay fresh in
fileCache across geological resets, and are pruned on delete.

The system-props virtual module is invalidated only when its generated
module text actually changes, decided against the same single source
that serves it; create/delete paths keep their unconditional
invalidation per spec.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The map-redelivery barrier matched the whole served module, and the
populated dynamicPropConfig now carries scale keys, so the bare value
matched startup text before the watcher event was delivered — the
scenario exited early and its pending event raced the next scenario's
edit into concurrent analyses. The barrier now watches the
systemPropMap export line. The invalidation unit test's fabricated
dynamic-prop meta gains the manifest's required varName/slotClass shape
(the config builder fails loud on entries missing them — which is how
this fixture was caught).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rust-policy gate checks real code and manifests on every clippy
run; a vitest suite exercising the validator's internal functions was
authoring-time scaffolding for drawing the policy boundary, not a
durable fixture — meta-tests of verification machinery invite infinite
regress and are removed once the policy has settled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ble dev define

createComposedFamily now enforces the exact PascalCase Root slot like
its source and context twins — dev and extracted production builds
agree (the extractor never emits a Root-less family, so the guard is
unreachable on valid output). The compose spec scenario that implied a
Root-less call succeeds now states the rejection contract.

extend() is a terminal-only affordance: the builder-stage classes
declare it protected, so pre-terminal chains reject it at compile time
while terminal components, extension chains, and the internal bind are
unchanged at runtime. builder-chain spec gains the rejection scenario.

Runtime diagnostics gate on a plugin-supplied __ANIMUS_DEV__ define
read via typeof ahead of the NODE_ENV fallback: the Vite plugin defines
it from the config hook, the Next plugin via DefinePlugin (Turbopack
builds keep the fallback). The initializer is a ternary because the
constant must be the conditional of the initializer itself for rollup's
tree-shaking to eliminate the gated code — an early return inside the
IIFE ships every diagnostic string. The vite-app build assertion now
pins the fold by requiring the drop-warning marker absent from dist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Six scripts/verify test files exercised validators' internals on
fabricated inputs — boundary probes from when each policy was authored,
whose real enforcement runs independently (packed.sh, verify:lint's
topology scan, the shell precondition helpers, the NAPI freshness
callers). They are removed; recreation recipes now live in the
validators' own headers where the contract wasn't already
self-documenting, and the sole coupling the build-extract suite
provided — Rust toolchain channel vs CI's pinned action ref — moves
into workers-config's pin-parity checks as a real-repo gate.

The three suites that ARE gates in test clothing stay (ci-graph: sole
reader of the CI workflow; owner-graph: task-graph and owner-closure
enforcement; workers-config: pin parity, deploy topology, bindgen
absence) minus their fabricated-input pockets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A composed child's compound whose conditions name shared axes now
emits an ancestor-form rule beside its flat rule, in the same
compounds layer: shared axes group per-axis on the Root
(one alternative bare, several as :is(value..., default-last) — Root
or child owning the default branch by axis owner), child-only axes
chain on the slot, and every shared axis contributes one
:not(.{child}--{axis}-{option}) per option the slot declares but the
conditions do not accept — so an explicit slot override suppresses the
ancestor form while agreement and defaulted slots inherit from Root,
mirroring the variant layer's override contract. Single selector per
rule; no cartesian growth.

Flat rules, ordinals, config lists, per-component fragments, and every
pre-existing emission path are byte-identical; the expansion only fires
for compose families whose child compounds reference shared keys. The
reconciler's shared-key force-marking is now load-bearing for the
exclusion sets as well as the variant rules, and the compose
propagation spec gains the requirement, scenarios, and the corrected
transport rationale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reuse: isUnitlessProperty joins @animus-ui/properties with a
precomputed camel+kebab spelling set, replacing a per-render regex and
the runtime's private camelToKebab; the Root-slot guard has one home
(assertRootSlot) called by all four composition entry points on their
input; the dev-diagnostic fold witness moves into @animus-ui/assertions
and now also gates the Next production bundle; layer wrapping is one
wrap_layer helper.

Efficiency: the system-props module is generated once per analysis and
memoized for serving and change detection; comma-free pseudo selectors
take an allocation-free fast path; the compounds layer append no longer
concatenates every flat fragment twice; nested-selector normalization
splits once instead of join-then-resplit; extension-parent
classification is one chain traversal and one shared ladder.

Simplification: the speculative snake_case manifest reader is retired
(the engine emits camelCase only; the fail-loud guard stays), widened
alias registrations are narrowed at union accumulation so the published
type surface cannot be poisoned, variant-skip records collapse through
one constructor, and the test suites share their scaffolding
(index-html context, plugin-context probe, NODE_ENV loader, compound
expansion builders, diagnostics filters) with redundant cases folded.

Comments consolidate to single homes with pointers; behavior-changing
findings were deliberately not applied and remain on the ledger.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… Root validation

Document-rendering SSR hosts (Remix, React Router) never invoke
transformIndexHtml, so tag-only bridge delivery left their dev sessions
with variables and global CSS but no component rules. Dev transforms now
prepend the bridge import to every component-bearing module —
unconditional per transform, so no cache invalidation can strand a
client and hydration carries the bridge to SSR documents — while the
tag remains the early-adoption path for index.html apps. The bridge
body no-ops without a document, so server-side evaluation is safe, and
production output stays engine-verbatim.

Root-slot validation now requires an own enumerable property, matching
the Object.entries iteration every composition implementation performs:
an inherited Root (Object.create({ Root })) previously passed the `in`
check and produced a family without a Root.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ates

The dev bridge import now lands below the directive prologue the engine
hoists to byte 0 — prepending above it demoted 'use client'/'use strict'
to plain expression statements on exactly the RSC hosts the import
serves. New-file detection invalidates the virtual modules
unconditionally: a usage-only file moves the system-prop map without
defining components, and a non-invalidated module is served from cache
for the life of the server. The bridge's hot-accept callback no-ops
without a document (the server module runner has a hot channel too),
pinned by driving the callback in a document-less vm.

Numeric compound condition values keep their ancestor-form branch (the
runtime interpolates size={2} as --size-2); the utility-hash selector
constraint is now an enforced debug assertion rather than prose; the
lint-parity gate fails loud when a manifest has no [lints.*] tables to
compare; a failed re-read of a dependency-and-source file warns instead
of silently leaving stale text for every later geological reset; the
system-props memo stores on generate so serving and change detection
always read the same bytes; and a process shim without env reads as dev
(no production signal), with the guard ordered so define-rewritten
browser bundles skip it instead of throwing — the vm harness caught
that ordering mistake in this very round.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecaaron
codecaaron merged commit 206945c into main Aug 7, 2026
14 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.

1 participant