chore: Predictability - #80
Merged
Merged
Conversation
… 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.