Skip to content

feat: animation quality defaults and ui_validate keyframes/monorepo token fixes - #124

Merged
owjs3901 merged 6 commits into
mainfrom
devfive/3099154426bffbeef8b62999b80bb038/animation-quality
Nov 10, 2026
Merged

owjs3901 merged 6 commits into
mainfrom
devfive/3099154426bffbeef8b62999b80bb038/animation-quality

Conversation

@devfive-relay-c70423

@devfive-relay-c70423 devfive-relay-c70423 Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Makes restrained entrance/transition motion a default delivery item (requirements 1-4) and fixes two devup_ui_validate problems (requirement 5): a false runtime-value on the documented same-file keyframes constant, and token validation in repositories with several apps.

Rules that can be proven from source or measured in a browser are checks. What is left as guidance is only what is a judgement (whether motion suits a block, whether it is restrained).

What changed, per requirement

1. Propose and implement restrained motion by default; the user's design wins

  • New skill reference devfive-frontend/references/animation.md: survey sections and repeated content, choose motion by role (table of defaults, including "none" for first viewport, body copy, forms, navigation), tell the user the proposal, then implement. A stated design always wins.
  • SKILL.md links it up front and in References. delivery-quality.md gains a binding summary section; that file is embedded in devup://guide/usage, so clients without installed skills receive it too.
  • developmentContract v3 names the default (default-motion-and-safe-progressive-enhancement, one-shot-reveals-without-ssr-flash); bootstrap instructions rule 6 mentions default motion (1490 bytes, under the 1500 budget).
  • behaviorPlan section-entrance candidates point at the rules and list the wider verification set.

2. Reveal when visible, once; staggered groups; fixed backgrounds; synced progress

  • devup_ui_validate warnings: reveal-replays (whileInView without viewport={{ once: true }}; useInView without a one-shot option is the same finding at info), reveal-moves-background, stagger-too-long (per-item step above 150ms).
  • devup_runtime_audit reveal check: records the trigger position (measured.trigger) and fails an entrance that starts more than 1px off screen or only after 400ms of full visibility; then scrolls away and back and fails a re-hide, a replay, or an unobservable re-entry.
  • Opt-in progress-sync scenario: each item must appear within 200ms of the progress line reaching it.

3. No flash or blank during SSR/hydration/reload/navigation; no-JS, reduced motion, focus

  • ssr-hidden-reveal covers an always-rendered motion.* element (or a component built with motion.create()/motion() in the same file) whose initial is opacity: 0. Elements inside AnimatePresence or a conditional render are exempt; a variant declared elsewhere is not followed.
  • motion-without-reduced-motion (paths/files modes): entrance motion while no validated source, nor projectRoot/src in paths mode, handles reduced motion. Read from the syntax tree, so a comment or an unused import does not count.
  • Shipped runtime fix: motion-runtime.ts animated sections that were already on screen when its observer attached, hiding visible content to replay an entrance on first load, reload and back/forward. Those sections now stay as rendered.
  • Opt-in audit scenarios reload, history and focus measure this.

4. Verify start/middle/end frames and trigger timing on mobile and desktop

  • Reveal checks always measure intermediate and final frames, trigger position and re-entry. motion.scenarios adds fast-scroll, reload, history, image-delay, image-failure, focus, progress-sync, each from a fresh load. A scenario that cannot be observed fails, and scenarios not requested are listed in measured.scenariosNotRun.
  • animation.md section 4 maps each evidence item to its scenario; contract evidence names computed-style-and-animation-start-middle-end and motion-trigger-reentry-history-and-image-failure.

5a. Same-file static keyframes reference is not runtime-value

  • Root cause: every identifier inside css()/globalCss()/keyframes() was treated as a runtime value. static_consts.rs resolves module-level consts of the same file that hold a literal, a keyframes()/css() name, or a template/arithmetic/object/array built from those (css({ animationName: spin })).
  • Still errors: imports, parameters (including one shadowing the const), let, undeclared names, consts declared inside a function, call results, anything built from those, and object/array consts the file mutates.

5b. Per-app devup.json; unverified scope is never a pass

  • paths and files modes check each source against the nearest devup.json above it. tokens.scopes lists each theme with file/reference counts; tokens.verification is verified, partial or skipped.
  • Files no theme governs are skipped, named in themeGuardrail.unverifiedPaths, and themeAvailable / tokens.unknownTokenCheckRan are true only when every file was checked. okReason is never clean when unverified references exist.
  • A single project theme still applies to files outside any package, but no longer crosses into another package.
  • A supplied bundle with several devup.json files is matched per directory instead of being refused.
  • A single tsx is located by a relative sourceName (apps/front/src/Hero.tsx): the nearest devup.json above it is used and named in tokens.devupJson. The file is never opened; a bare label or a path leaving the project changes nothing.

Reply budget

export_context_summarizes_a_large_project_under_fifteen_kib had 31 bytes of headroom. A behaviorPlan that is complete on its first page no longer carries the page-joining instruction (it only matters when more pages follow), which frees room for the two new contract ids. Headroom after this PR: 104 bytes. The budget itself is unchanged.

Tests

  • cargo fmt --all -- --check, cargo clippy --locked --workspace --all-targets --all-features -- -D warnings: clean.
  • cargo insta test --workspace --all-features --check: 2286 passed, 0 failed, 2 ignored; no snapshot changes.
  • New Rust tests (25): 4 static-const, 5 theme-scope unit, 2 scope-resolver unit, 6 MCP integration (two apps with different themes, a token valid in one and unknown in the other, files with no resolvable devup.json, package boundary, multi-theme bundle, located sourceName), 8 motion-rule. Extended: contract, plan validation, paging and reply-budget tests.
  • Node: CI list (explore_script_behavior.mjs, withdraw.test.mjs, checks.test.mjs) 82 pass, 1 skipped. harness/runtime-audit/test/motion.test.mjs 4 pass in Chromium: the shipped controller passes every page scenario; fixtures that break one rule each (early trigger, late trigger, replay, re-hide, hydration flash on reload and history, stuck after fast scroll, animating a focused control, image-gated reveal, unsynced progress line) fail on that rule; blocks with nothing to observe fail as unobserved.

One existing assertion changed: fetch_tests.rs counted 6 installed frontend documents and now counts 7, because a reference was added.

What remains

  • Judgement, not measurement: whether motion suits a block and whether it is restrained.
  • The static motion rules read framer-motion/motion props and useInView. Other animation libraries (GSAP, AOS, hand-written observers) are covered only by the runtime audit, which measures the rendered result whatever produced it.
  • The reveal scenarios are opt-in per check because each reloads the page; a report lists the ones not run.

🤖 Generated with Claude Code

leader and others added 6 commits November 10, 2026 15:47
…rance-motion rules

runtime-value no longer fires on the documented same-file constant pattern
(css({ animationName: spin }) with spin a keyframes() const): module-level
consts holding a literal, a keyframes()/css() name, or a template/arithmetic
over those resolve as build-time values. Imports, parameters, shadowed names,
let bindings, call results and object constants the file mutates stay errors.

paths and files modes check each source against the nearest devup.json above
it, so one call over a repository of several apps uses each app's theme.
tokens.scopes lists every theme with its file/reference counts, files no
theme governs are named in themeGuardrail.unverifiedPaths, and
themeAvailable/unknownTokenCheckRan are true only when every file was checked.
A single project theme no longer crosses into another package.

New warnings proven from source alone: ssr-hidden-reveal for always-rendered
motion elements starting at opacity 0, reveal-replays for whileInView without
a one-shot viewport, reveal-moves-background, and (multi-file modes)
motion-without-reduced-motion.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
After the intermediate and final frames, a reveal check scrolls the target
out of view and back. Content re-hidden while away, a replay on re-entry, or a
target that cannot leave the viewport at that size (re-entry unobserved) fails
instead of passing on the first entrance alone.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…t item

Adds the devfive-frontend animation reference (survey and propose by role,
one-shot visible scroll reveals, fixed backgrounds, synced progress lines, no
SSR/hydration flash, no-JS/reduced-motion/focus, and start/middle/end frame
plus trigger-timing verification at mobile and desktop), links it from
SKILL.md, and carries the binding summary in delivery-quality, which the usage
guide ships to clients without installed skills. The development contract,
section-entrance candidates and bootstrap instructions name the default; the
contract renames two ids instead of adding any to stay inside the 15 KiB
export reply budget. Includes the changepack.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…cenarios

A reveal check now scrolls toward the target in steps and records how much
of it was on screen when the entrance began; starting more than 1px off
screen, or only after 400ms of full visibility, fails. Opt-in scenarios run
from a fresh load: fast-scroll, reload, history, image-delay, image-failure,
focus and progress-sync (items appear as the progress line reaches them).
A scenario that cannot be observed fails, and those not requested are listed
in measured.scenariosNotRun. The plan validates scenario names and selectors.

The shipped motion runtime animated sections that were already on screen
when its observer attached, hiding visible content to replay an entrance on
first load, reload and back/forward. Such sections now stay as rendered.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…motion rules

A single tsx has no path, so at a repository root with several themes its
tokens stayed unverified. A relative sourceName now selects the nearest
devup.json above that location (named in tokens.devupJson); the file itself
is never opened, and a path leaving the project or a bare label changes
nothing. Another package's theme is still not borrowed.

Motion rules read components built with motion.create()/motion(), report an
in-view hook without a one-shot option at info, and add stagger-too-long for
a per-item step above 150ms. Reduced-motion handling is read from the syntax
tree, so a comment or an unused import no longer counts as handling it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…reply budget

A behaviorPlan that is complete on its first page no longer carries the
page-joining instruction, which only matters when more pages follow. That
frees room in the 15 KiB export reply (headroom 31 -> 104 bytes after this
change) for the contract to name one-shot-reveals-without-ssr-flash and the
motion-trigger-reentry-history-and-image-failure evidence item. The animation
reference documents the measurable scenarios and what remains a judgement.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@owjs3901
owjs3901 marked this pull request as ready for review November 10, 2026 08:27
@owjs3901
owjs3901 merged commit 5436249 into main Nov 10, 2026
9 checks passed
@owjs3901
owjs3901 deleted the devfive/3099154426bffbeef8b62999b80bb038/animation-quality branch November 10, 2026 08:38
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