Audited file: interact-documentation-site.md (4,733 lines, 27 pages)
Audited against: packages/interact/src/** (v2.5.4), packages/interact/rules/full-lean.md, packages/interact/rules/{click,hover,viewenter,viewprogress,pointermove,integration,validate}.md, packages/interact/README.md, packages/interact-validate/src/**, packages/motion-presets/src/**, packages/motion/src/types.ts
Date: 2026-07-26
Every technical claim below was checked against source. Where a claim was verified by running code, it is marked [verified by execution]. Where the shipped agent rules (rules/*.md) and the source disagree, the source is treated as truth and the rules are listed for a separate fix in Appendix A.
Line numbers refer to the current state of interact-documentation-site.md.
- Summary
- Blocking issues
- Technical errors
- Missing information
- Content to remove
- Structure and navigation
- Authoring style — the house style decision
- Terminology consistency
- Code-sample consistency
- Markdown and formatting defects
- Per-page action list
- Appendix A — upstream fixes outside this file
- Appendix B — verification notes
The documentation is structurally sound — the page inventory and the order of pages are right and should not change. The problems fall into five buckets:
| Bucket | Count | Severity |
|---|---|---|
| Technical errors (wrong, will mislead or break user code) | 22 | Blocking / High |
| Missing information (correct but incomplete) | 24 | Medium |
| Authoring residue (owners, TODOs, Google Docs artifacts, dead links) | ~90 instances across 12 classes | Blocking (cosmetic but public-facing) |
| Structural / navigation defects | 14 | High |
| Style & consistency (authoring voice, terminology, code style, markdown) | 11 classes | Medium |
Two pages are not shippable at all in their current state: "The final result + examples links" (an unwritten outline) and "Using lists" (all code samples are rendered as single-cell Markdown tables).
The most-repeated technical error is the casing guidance for state-effect styleProperties (§3.1) — it appears on two pages across five examples. Under the decision below the examples stop being broken, but the guidance is still wrong: it presents camelCase as the required form on a surface where kebab-case is the house style and both forms are accepted.
| Date | Decision | Audit items superseded |
|---|---|---|
| 2026-07-28 | @wix/interact will accept both camelCase and kebab-case on every input that takes a general CSS property name (keyframeEffect.keyframes, transition.styleProperties, transitionProperties) and normalise internally per usage — camelCase for WAAPI keyframes, kebab-case for generated CSS. CSS custom properties (--*) are used verbatim. Implementation plan: dual-casing-support-plan.md. |
B1, §3.1, §11 (click & hover, what are effects?, transition Effects), Appendix A A1 (+ new A12) |
| 2026-07-28 | Reduced motion is detected from the browser. Interact.forceReducedMotion is no longer a plain false default: when it is not set explicitly it reports matchMedia('(prefers-reduced-motion: reduce)').matches, and an explicit true/false overrides the browser in either direction (undefined restores detection). generate() gains a third argument, { reducedMotion?: boolean } (default true), and emits @media (prefers-reduced-motion: reduce) overrides so pre-rendered CSS behaves the same before JS loads — animations that run once collapse to 1ms and land on their end state, perpetual and scroll-driven animations are turned off, transitions are dropped. Shipped in @wix/interact 2.6.0 / @wix/motion 2.2.0. |
§3.13, §3.14, §11 (pointerMove, viewProgress, what are effects?, Named Effects, keyframe Effects, understanding conditions), new Appendix A A13-A14 |
Documentation that depends on a decision above must not ship before the release that implements it.
These must be resolved; everything else can be triaged.
| # | Issue | Location |
|---|---|---|
| B1 | State-effect styleProperties documented as camelCase-only — both casings are accepted and kebab-case is the house style (§3.1; wording ships with the dual-casing release, §1.1) |
L2341, L2352-2353, L3096-3098, L3113-3127, L3146-3148 |
| B2 | Owner/Reviewer attribution lines with internal Wix email addresses | 27 lines (see §5.1) |
| B3 | Link to an internal Google Docs document | L1093 |
| B4 | 25 http://ADDLINK / ADDLINK placeholder links, plus 3 google.com/search?q=ADDLINK links |
§5.3 |
| B5 | "The final result + examples links" page is an unwritten outline | L827-842 |
| B6 | "Using lists" code samples rendered as single-cell Markdown tables | L4473-4505 |
| B7 | data-interact-initial="true" — an attribute that does not exist |
L2812 |
| B8 | Vanilla generate(config) example contradicts the page's own useFirstChild table |
L699, L4481 |
| B9 | Empty stub sections: "Combining triggers", several bare ## / # headings |
L977, L1000, L2067, L2589, L3070, L3998 |
| B10 | pageVisible listed as a trigger — no such trigger exists |
L3218 |
| B11 | Element-resolution rules contradict each other across three pages and are wrong on two counts | L3601-3638, L3621, L4356-4358, L4374-4382 |
| B12 | Unwritten placeholders in body copy: "TO DO ADD VISUAL DEMONSTRATION", [Explain that…], [Add link to…], [ A very cool visual example… ] |
L13, L29, L452, L3337, L3341, L4298 |
3.1 State-effect style property names — both casings accepted, kebab-case is the house style — BLOCKING
Where: L2341 (What are effects? → State Effects), L2352-2353 (same page, example), L3096-3098, L3113-3127, L3146-3148 (transition Effects page, all three examples).
What the docs say: "CSS property names use camelCase (backgroundColor, borderRadius)."
What is true today (pre-dual-casing): styleProperties[].name and transitionProperties[].name are written verbatim into the generated stylesheet — both into the state rule and into the transition: shorthand. camelToKebabCase() is applied only to the trigger name (src/core/css.ts:458), never to style property names (src/core/cssUtils.ts:148, src/utils.ts:77-93), so a camelCase name is silently discarded by the browser.
[verified by execution] Running generate() on a config containing both spellings emits:
[data-interact-key='btn'] > :first-child {
--transition-0-…: backgroundColor 200ms ease, background-color 200ms ease;
}
[data-interact-key='btn']:is(:state(…), :--…, [data-interact-effect~='…']) > :first-child {
backgroundcolor: #111; /* invalid — dropped by the browser */
background-color: #222; /* works */
}What will be true (decision of 2026-07-28 — see §1.1): both casings are accepted on every CSS-property input and normalised internally — camelCase for WAAPI keyframes, kebab-case for generated CSS; --* custom properties verbatim. kebab-case remains the house style for state effects because it is literally what the browser receives; packages/interact/README.md:326 (box-shadow) and L1450-1457 (click & hover) already follow it.
Fix:
- L2341 — replace with: "State-effect property names end up in CSS, so write them as standard kebab-case CSS properties (
background-color,border-radius). camelCase (backgroundColor) is accepted too — Interact normalises either form.keyframeEffectkeyframes are the mirror image: camelCase is idiomatic there (it is what WAAPI uses), and kebab-case is likewise accepted." - Rewrite all five examples to kebab-case, so every state-effect example matches the CSS it produces.
- Add one short callout on both the
What are effects?and thetransition Effectspage: the two surfaces have opposite idiomatic forms, and both accept either. Do not frame it as "get this wrong and your CSS breaks" — that stops being true with the dual-casing release. - L1450-1457 (
click & hoverpage) already uses kebab-case correctly — keep it, and make it the reference example. - Sequencing: until the release that ships §1.1 lands, the camelCase examples in this file are genuinely broken, so the rewrite to kebab-case can go out immediately; the "either casing works" sentence must wait for that release.
Where: L3601-3618 (source and target resolving), L3634-3635 (recap step 2), L4358 (what is a list?), L4366-4369 (example).
What the docs say: "listItemSelector is an optional filter. Use it only when a subset of the container's children should participate… only .active children become sources/targets."
What is true: listItemSelector is never consulted during element resolution. _getElementsFromData() (src/core/add.ts:43-77) branches only on listContainer and selector; with listContainer alone it returns Array.from(container.children) — all immediate children. The MutationObserver path (InteractionController._childListChangeHandler) likewise processes every added/removed HTMLElement child with no filter.
listItemSelector is used in exactly three places:
- CSS selector generation —
getSelector(…, { addItemFilter: true })emits${listContainer} > ${listItemSelector}(src/core/Interact.ts:340); - the
closest()lookup for state effects on lists (src/handlers/effectHandlers.ts:112); - the element-identity hash (
src/core/utilities.ts:31-33).
Fix: Replace the "filters which children participate" framing everywhere with the accurate description — which the Using lists page (L4462) already has: "Narrows which direct children count as list items when Interact generates CSS (for transition / state effects). Use it when the container also holds elements that are not items." Add an explicit warning: listItemSelector does not restrict which children receive JS-driven triggers or animations — all immediate children of listContainer are bound. Delete or rewrite the .active example at L3603-3618, which teaches behaviour the runtime does not implement.
Where: L3637 (source and target resolving, recap step 3) says: "use querySelector within the root to select first matching descendant."
What is true: src/core/add.ts:64-72 — root.querySelectorAll(data.selector) returns every match, and each becomes a source/target.
This directly contradicts L4374-4382 on the what is a list? page ("a selector alone matches via querySelectorAll"), which is correct.
Fix: Correct recap step 3. Reconcile the two pages so they state the same rule. Note the practical consequence: selector: '.card' on an interaction attaches the trigger to every .card in the root, not just the first.
Where: L3621 and L3635 (source and target resolving): "Interact runs querySelector inside each direct child of the container."
What is true: at bind time, src/core/add.ts:57-59 runs container.querySelectorAll(selector) — a single query scoped to the container, matching any depth. The per-child element.querySelector(selector) form (_queryItemElement, src/core/add.ts:79-85) is used only for items discovered later by the MutationObserver.
This is a real behavioural difference: listContainer: '.grid', selector: 'img' binds to all images inside .grid, including two images inside the same card, whereas a dynamically appended card contributes only its first img.
Fix: State the container-scoped querySelectorAll rule as the primary behaviour. Either document the per-item difference for dynamically-added children, or (preferred) file it as a runtime inconsistency and document only the stable rule — see Appendix A. The Using lists page (L4461) is already correct.
Where: L2812 (Named Effects → Entrance).
What the docs say: "pre-render the CSS with generate(config) and mark the element with data-interact-initial="true"."
What is true: the attribute is data-interact-enter, and it is written by the runtime, never by the author. generate() emits two guarded rules (src/core/css.ts:246-263):
…:not([data-interact-enter])→ appliesDEFAULT_INITIAL(visibility: hidden; transform: none; translate: none; scale: none; rotate: none);…:not([data-interact-enter="done"])→ applies the animation custom properties.
The runtime sets data-interact-enter="start" when the animation plays and "done" when it finishes/aborts (src/handlers/viewEnter.ts:216-237).
Fix: Remove the instruction entirely. Replace with: "Entrance FOUC prevention is fully automatic — generate() emits the initial-state rules and the runtime manages the data-interact-enter attribute. You do not add any attribute yourself." Document data-interact-enter (read-only, for debugging) on the FOUC page.
Where: L699 (HTML integration → Vanilla JS), L4481 (Using lists).
What the docs say: the table at L767 correctly states useFirstChild is true for web and false for vanilla and React. But the Vanilla JS code sample calls generate(config) with no second argument.
What is true: generate(config, useFirstChild = true) (src/core/css.ts:547) — the default is true. Calling generate(config) in a vanilla integration emits > :first-child selectors that will not match, so no CSS applies.
Fix: L699 → generate(config, false). L4481 → generate(config, false) (or make the surrounding example explicitly a web integration). Audit every generate( call in the file and make the second argument explicit everywhere, including the Named Effects and Using lists pages. Add a note to the argument table: "The default is true; always pass it explicitly."
Where: L3218 (custom Effects): "For viewEnter, pageVisible, hover, click, activate, interest, and animationEnd…"
What is true: TriggerType (src/types/triggers.ts:7-15) is exactly hover | click | viewEnter | animationEnd | viewProgress | pointerMove | activate | interest. There is no pageVisible anywhere in the codebase.
Fix: Delete pageVisible from the list.
Where: L3021 (keyframe Effects → Advanced pointer properties): "hitArea chooses which element's pointer events drive the effect ('self' the target element itself, 'root' its nearest positioned ancestor)."
What is true: src/handlers/pointerMove.ts:39 — root: options.hitArea === 'self' ? source : undefined. 'self' is the source element (not the target); 'root' (the default, since undefined root falls through to kuliso's viewport default) tracks the viewport, not any ancestor.
The pointerMove page (L1896-1898) states this correctly.
Fix: Correct L3021 to match the pointerMove page. Better: delete the duplicated pointer reference from the keyframe Effects page and cross-link to the pointerMove chapter (see §6.5).
Where: L4155-4158 (responsive animation design) presents { type: 'container', predicate: '(min-width: 600px)' } as a working feature, and the page body advertises "container conditions" (L4142).
What is true: type: 'container' is in the TypeScript type (src/types/config.ts:5) and in the validator schema (interact-validate/src/schema/primitives.ts:41), but nothing consumes it:
generate()only ever callsgetFullPredicateByType(…, 'media')— no@containerrule is ever emitted;- runtime gating uses
getMediaQuery(), which is also media-only (src/utils.ts:170-178).
A container condition is therefore silently dropped — the gated interaction runs unconditionally. The L4165 example (conditions: ['desktop', 'motion-ok', 'wide-container']) does not do what the surrounding prose claims.
Fix (pick one, then apply consistently):
- Recommended: remove container conditions from the docs entirely. Rewrite the L4145-4197 example using only
mediaconditions, and drop "container sizes" from L4046 and L4142. - Alternative: keep them but add an explicit "not yet implemented — reserved" note. This is worse for a public docs launch.
Either way, resolve the inconsistency with the understanding conditions page (L3918), whose type table lists only media and selector.
Where: L4031 (understanding conditions → Validation).
What is true: UNUSED_CONDITION is emitted as a warning (interact-validate/src/structural.ts:75 — warnings default to severity: 'warning'), so validateInteractConfig() still returns valid: true and assertValidInteractConfig() does not throw. Only INVALID_MEDIA_QUERY (a schema issue) is an error.
Fix: "Invalid media predicates are reported as errors; unreferenced condition definitions are reported as warnings. Use strict: true to promote warnings to errors, or severityOverrides to tune them."
Where: L4723 (using sequences §5): "This catches dangling sequenceId/effectId references, negative offset/delay values, and unused sequence definitions (UNUSED_SEQUENCE) before they reach the runtime."
What is true: assertValidInteractConfig throws only when result.valid === false, i.e. only on errors (interact-validate/src/structural.ts:83-88). Of the four items listed, only SEQUENCE_ID_NOT_FOUND and the negative offset/delay checks are errors. EFFECT_ID_NOT_FOUND and UNUSED_SEQUENCE are warnings and will not throw.
Fix: Split the sentence by severity, and show validateInteractConfig(config) (which returns everything) alongside assertValidInteractConfig(config) (which throws on errors only).
Where: L4534-4539 (what is a sequence?).
What is true (src/types/config.ts:9-20):
type SequenceConfig = {
effects: (Effect | EffectRef)[]; // REQUIRED
delay?: number; // ms before the whole sequence starts
offset?: number; // ms between consecutive participants
offsetEasing?: string | ((p: number) => number); // distributes the offsets
sequenceId?: string; // id for referencing / caching
conditions?: string[]; // gate the whole sequence
triggerType?: TimeAnimationTriggerType; // playback behaviour for the sequence
};The doc's snippet omits delay, sequenceId, conditions and triggerType, and types offsetEasing as string only. triggerType in particular is important — it is documented on the other sequences page (L4686) but missing from the type on this one.
Fix: Replace the snippet with the full type and annotate each field. Defaults from resolveSequenceForCSS (src/core/resolvers.ts:118-129): delay = 0, offset = 0, offsetEasing = 'linear', triggerType falls back to the trigger's default (once for viewEnter/animationEnd, alternate for hover/click/interest/activate).
3.13 Reduced motion — the claims are now true, but the mechanism must be documented — SUPERSEDED by §1.1 (2026-07-28)
Where: L3023 (keyframe Effects): "The library skips pointer-driven effects entirely when reduced motion is preferred." L1867 (pointerMove): "pointerMove effects are skipped when reduced-motion mode is enabled." Plus the reduced-motion sections at L2550-2585, L2868-2891 and L4000-4027.
What was true when audited: the runtime flag was only Interact.forceReducedMotion, a static boolean = false. Nothing read prefers-reduced-motion, so both statements promised behaviour the library did not have.
What is true as of @wix/interact 2.6.0: both statements are correct. Interact.forceReducedMotion reports the browser's prefers-reduced-motion setting unless it was assigned explicitly (src/core/Interact.ts, prefersReducedMotion() in src/utils.ts), and generate() emits @media (prefers-reduced-motion: reduce) overrides so pre-rendered CSS matches before JS loads (src/core/css.ts).
Fix: Keep both sentences, and document the mechanism once — in the reduced-motion section at L2550-2585 — then link to it from L1867, L2891, L3023 and L4000-4027:
Interact.forceReducedMotionfollows the browser by default. Set it totrueto force reduced motion for everyone,falseto ignore the browser setting,undefinedto go back to following it. Set it beforeInteract.create().- What reduced motion does: an animation that runs once collapses to
1msand lands on its end state; a perpetual animation (iterations: Infinity) does not run;viewProgressandpointerMoveeffects are skipped; transitions are dropped so state effects apply instantly. generate()emits the matching CSS by default; pass{ reducedMotion: false }(third argument) alongsideInteract.forceReducedMotion = falsewhen a surface deliberately ignores the browser setting.- Conditions are still the tool for a gentler alternative rather than no motion — that is what the L2557-2577, L2875-2884 and L4007-4020 examples teach, and they stay as they are.
Remove: the hand-rolled Interact.forceReducedMotion = matchMedia('(prefers-reduced-motion: reduce)').matches at L2585 and the "you can also force this globally" framing at L4023-4026 — that assignment is now the default behaviour, and repeating it in docs teaches users to re-implement what the library does.
Where: the viewProgress chapter never mentions it.
What is true: src/handlers/viewProgress.ts:25-27 returns early when reducedMotion is set — identical to pointerMove. Since 2.6.0 that flag is on by default for users who asked for reduced motion, and generate() also emits animation: none + animation-timeline: auto for scroll-driven effects so the CSS-only path is skipped too (see §3.13).
Fix: Add a "skipped under reduced motion" note to the viewProgress chapter, mirroring L1867 so the two continuous-trigger chapters are parallel, and point both at the reduced-motion section.
Where: L1621: "contain — While the element is fully contained in the viewport (great with sticky)."
What is true: per the CSS spec (and rules/full-lean.md:429), contain covers the period during which the subject is fully contained by or fully contains the scrollport. The second half matters precisely for the sticky/scrolly-telling pattern the same page teaches, where the subject is taller than the viewport.
Fix: "While the element is fully inside the viewport — or, for elements taller than the viewport, while it fully covers it. This is the phase a position: sticky child stays pinned, which is why it pairs with scrolly-telling."
Where: L4678 (using sequences): "Sequences work with the same triggers as single effects: viewEnter, hover, click, interest, activate." L2071 (animationEnd): "animationEnd starts an effect or sequence after another animation finishes."
What is true: _attachSequenceTriggers (src/core/add.ts:376-399) dispatches through TRIGGER_TO_HANDLER_MODULE_MAP[interaction.trigger] and passes the sequence as animation. The animationEnd handler accepts preCreatedAnimation (src/handlers/animationEnd.ts:24), so sequences do work with animationEnd. viewProgress and pointerMove ignore the pre-created animation, so sequences are meaningless there — which the viewProgress chapter (L1751) already explains correctly.
Fix: Add animationEnd to the L4678 list, and state explicitly that sequences are not supported on viewProgress / pointerMove (with the L1751 rationale), so the two pages agree.
Where: L2161: "Unrelated animations finishing on the same element do not activate the chain."
What is true: the handler filters on sourceAnimationOptions (src/handlers/animationEnd.ts:45-60). When the source's preceding effect resolves to a CSS animation, the filter checks hasAnimationName / hasAnimationId and the claim holds. When sourceAnimationOptions is absent (e.g. the awaited effect is not a CSS animation on that element), sourceAnimationGroup is null and the handler plays on any animationend from the source.
Fix: Soften to: "When the awaited effect resolves to a CSS animation on the source, unrelated animations finishing on that element are filtered out. Keep the awaited effectId on a CSS-backed effect (keyframeEffect or namedEffect) for reliable chaining."
Where: L2633 (Named Effects category table): Ongoing → "any trigger, with iterations: Infinity". L2833: "Start one with any trigger and set iterations: Infinity."
What is true: iterations on a scrub effect must be finite (rules/full-lean.md:403 — "NOT Infinity"); an infinite iteration count has no meaning on a scrubbed timeline.
Fix: "any time-based trigger (viewEnter, hover, click, animationEnd), with iterations: Infinity."
Where: L2319-2331 (What are effects?) presents them as "OR"; L3108 (transition Effects) explains when to reach for each. Neither states what happens if both are set.
What is true: src/utils.ts:62-95 — if transition?.styleProperties is present, transitionProperties is ignored entirely. (rules/full-lean.md:452 claims per-property entries take precedence — that is wrong; see Appendix A.)
Fix: Add: "Set one or the other. If both are present, transition wins and transitionProperties is ignored."
Where: the transition type is shown with duration?: number (optional) at L2326 and L3091, with no note.
What is true: transitionEffectToTransitionsList (src/utils.ts:65, :89) emits nothing when duration is falsy — for transition the whole block is skipped, and transitionProperties entries without duration are filtered out. The state still applies, but instantly, with no animation.
Fix: Annotate duration in both type snippets: "Optional in the type, but required in practice — without it the state change is applied with no transition."
Where: L266-290, L352-378, L411-440 (My first interaction) — a hover trigger scaling the hovered element itself (keyframes: [{ scale: 2 }], no selector, no separate target key).
Why this is a problem: the same documentation set marks this CRITICAL elsewhere — L1262-1263 ("Keep the hover hit area stable"), L2440, L3497 — and @wix/interact-validate has a dedicated HIT_AREA_SHIFT rule for it. A scale(2) on the hovered element is the textbook case: the element grows out from under the pointer, mouseleave fires, the animation reverses, the element shrinks back under the pointer, and it flickers.
(Note: the validator's checkHitAreaShift only inspects transform strings for translate|scale|matrix (interact-validate/src/semantic/fouc.ts:59-64), so the bare scale: 2 keyframe property used here escapes detection — the example is unsafe and invisible to the linter.)
Fix: Change the first tutorial to a safe pattern — either a modest, non-geometry effect on the element itself (e.g. opacity / filter / boxShadow), or keep the scale but move it to a child via selector. Also reduce scale: 2 to something realistic (1.05). This is the first code a reader ever runs; it should model the house rules.
Where: L59-89 and L119-148 (About Interact) — configs reference effectId: "headline-reveal", "hero-image-animate", "card-reveal", "image-animate" with no top-level effects registry.
Why this matters: these are the first configs a reader sees, they are typed as InteractConfig, and they would produce EFFECT_ID_NOT_FOUND warnings and animate nothing.
Fix: Either add a minimal effects registry to make them runnable, or replace effectId with inline namedEffect payloads. Also add the missing listContainer/listItemSelector consistency between the two examples (L74-86 has them, L135-144 does not, for the same described behaviour).
Ordered by impact.
| # | Missing | Where it belongs |
|---|---|---|
| M1 | FOUC chapter does not exist. L1093 points at a Google Doc instead. FOUC is referenced from 6 pages (L515, L771-777, L1091, L2812, L3490-3492, L4481) but never explained in one canonical place. | New/expanded page; the HTML integration §"Generating CSS & preventing FOUC" (L754-777) is the best existing draft and should be promoted. |
| M2 | The named-easing catalogue is never listed. Examples use backOut (L2607, L4085) with no reference. @wix/motion accepts sineIn/Out/InOut, quadIn/Out/InOut, cubicIn/Out/InOut, quartIn/…, quintIn/…, expoIn/…, circIn/…, backIn/Out/InOut, plus all CSS easings and linear(…). |
What are effects? → Timing & easing reference (L2591-2607). |
| M3 | FOUC guidance for non-once effects. The rules state: for repeat/alternate/state, apply the starting keyframe manually (inline styles or stylesheet) and use fill: 'both'. Only hinted at (L1350, L3528). |
FOUC chapter + viewEnter chapter. |
| M4 | data-interact-enter — the attribute that drives FOUC (start / done) is undocumented, yet users will see it in DevTools. |
FOUC chapter. |
| M5 | CSS embedding options. rules/full-lean.md:634-658 documents three placements including <style blocking="render">, which is the strongest FOUC guard. Not in the docs. |
HTML integration. |
| M6 | Interact.create(config, options) — options.useCustomElement is undocumented (L787 shows options? but never explains it). |
HTML integration → Static API reference. |
| M7 | Interact.setup() replaces rather than merges viewEnter options across calls (src/handlers/viewEnter.ts:60-62 assigns). |
HTML integration → Interact.setup. |
| M8 | Interact.forceReducedMotion must be set before Interact.create() — it is sampled as each interaction is bound, and existing animations are not rebuilt when it (or the OS preference) changes afterwards. Media conditions are the live-switching mechanism. Since 2.6.0 the value defaults to the browser setting, so this note is about overriding it (§1.1, §3.13). |
Reduced-motion sections (L2550-2585, L4000-4027). |
| M9 | transitionDelay is a valid scrub/pointer smoothing option (src/types/effects.ts:53) — omitted from L1908-1911 and L2278. |
pointerMove, What are effects?. |
| M10 | Scrub effects also accept iterations, alternate, reversed (src/types/effects.ts:42-55) — omitted from the scrub snippet at L2271-2282. |
What are effects?. |
| M11 | iterations: 0 is treated as Infinity (per rules). |
What are effects? → Time Effects. |
| M12 | Selector conditions support & — the predicate may contain &, replaced by the base selector (src/utils.ts:41-46). Without it, the predicate is appended. Documented in the rules, missing here. |
understanding conditions → Selector conditions. |
| M13 | Perspective guidance — prefer transform: perspective(…) inside keyframes; use the CSS perspective property only when multiple children share a perspective-origin (rules/full-lean.md:42). Relevant to the 3D tilt examples (L1934, L3815). |
What are effects? → Performance, and pointerMove. |
| M14 | interest and activate have no chapter, although the trigger overview (L994-996) promises "Each trigger has its own chapter". |
Either add a short chapter, or change the overview to say they are covered inside click & hover. |
| M15 | hover/click/interest/activate take no params — stated only obliquely (L1029). |
Trigger overview table. |
| M16 | customEffect makes a config non-JSON-serializable — worth stating given the "LLM-friendly / JSON config" positioning on the About page. |
custom Effects + About. |
| M17 | @wix/motion-presets also exports experimental Bg* background-scroll presets (marked "NOT PRODUCTION READY" in source) which import * as presets will pull in. |
Named Effects. |
| M18 | CustomMouse is exported from the mouse category (12 exports, 11 documented). Either document it or state that it is excluded. |
Named Effects → Mouse. |
| M19 | generate() must be re-run after registerEffects() for each config — and presets must be registered in both the build/SSR process and the client bundle. |
HTML integration → Named effects. |
| M20 | Inline state effects get a generated id — L1476 explains this well, but the consequence (pre-generated CSS from a separate build will not match the client's generated ids) deserves promotion to a callout rather than a paragraph. | click & hover, transition Effects. |
| M21 | remove(key) tears down the whole controller for that key — all interactions bound to that element, not a single interaction. L719 is right but terse; add that re-binding requires add() again. |
HTML integration → Vanilla JS API. |
| M22 | useSafeViewEnter is mentioned only in passing (L1075) and is missing from the params list at L1070-1073 and from the Interact.setup({ viewEnter }) description. |
viewEnter. |
| M23 | Browser support — no statement anywhere. ViewTimeline is feature-detected with a fizban fallback (src/handlers/viewProgress.ts:36-53); :state() / :-- state selectors have a [data-interact-effect~=…] fallback; custom elements are required (Interact.init bails when window.customElements is absent). |
New short section on the HTML integration page. |
| M24 | SSR caveat — Interact.init() returns immediately when typeof window === 'undefined' (src/core/Interact.ts:61-63). Worth stating explicitly next to the React useEffect guidance. |
HTML integration → React. |
L3, L184, L250, L458, L829, L847, L959, L1004, L1184, L1560, L1859, L2065, L2171, L2611, L2895, L3066, L3169, L3335, L3391, L3644, L3766, L3881, L4042, L4304, L4448, L4514, L4622.
All contain internal Wix email addresses. Delete every one. If attribution is wanted, move it to repository metadata, not the published page.
Every page heading is prefixed with 🧑🌾 or 🧑💻 (apparently marking content-writer vs. developer authorship). Strip all of them from the published titles.
| Type | Count | Lines |
|---|---|---|
http://ADDLINK / (ADDLINK) |
22 | L174-176, L295, L388, L462 (×3), L470, L478, L514, L822-825, L1918-1919 area, L2811, L3946, L4031, L4035-4038 |
https://www.google.com/search?q=ADDLINK |
3 | L4616-4618 |
Empty () links |
5 | L865, L874, L878, L882, L1000, L1203, L1865, L3243 |
Fake internal URLs http://Configuration/... |
8 | L1736, L1749, L3438, L3623, L3697, L3714, L3762, L3770, L3859, L3875 |
| Internal Google Docs link | 1 | L1093 |
| In-page anchors to sections that may not survive the site build | 2 | L3482 (#when-source-and-target-differ-fouc-and-refined-targets, #lists-listcontainer-and-listitemselector), L514 (#named-effects-registereffects) |
Action: build a link map (page slug ↔ target) and resolve all of them. Nothing with ADDLINK, google.com/search, docs.google.com, or http://Configuration/ may ship.
| Line | Content |
|---|---|
| L13 | ## [ A very cool visual example should be added here for a capabilities showoff] |
| L29 | [Add link to Rules and/or Skills] |
| L452 | TO DO ADD VISUAL DEMONSTRATION |
| L994 | *(In the live page, each trigger name links to its own chapter — a ↗ icon appears to its left and it underlines on hover.)* — a note to the site builder |
| L1180 | "Because of the current threshold limitation above, do not describe threshold: 0.5 as a guaranteed 50%-visible gate yet." — reviewer note; also a dangling reference (no threshold limitation is described above) |
| L1730 | (maybe next to it an 'out' animation for visual comparison) |
| L3337 | [Explain that interaction is the connection between a trigger and effects.] |
| L3341 | *[Visual example: A scroll-based interaction…]* |
| L4298 | (see reduced motion <add link to understanding condition - reduced motion>) |
| L4612 | "Overshooting durations: If your sequence contains structural loops or heavy offsets…" — "structural loops" is not a concept in this library; the sentence is not actionable |
| Artifact | Instances | Example |
|---|---|---|
Backslash-escaped Markdown (\-, \+, \[, \*\*, \`, \<, \#) |
~60 | L23, L94, L454, L833-839, L1207-1212, L2183, L2232, L2674, L2790-2793, L2812, L2831, L2864, L3078-3080, L3159-3161, L4304 |
Smart quotes (', ") — including inside code-like text |
18 | L1863-1872, L2921, L2971, L3002-3003, L3021-3023, L3078-3080, L3108, L3161 |
| Callout/admonition text merged into a heading | 4 | L454, L2674, L2812, L2831 |
| YAML frontmatter rendered as a heading | 3 | L2899, L2973, L3173 |
| Code samples rendered as single-cell tables | 5 | L4473-4477, L4487-4491, L4504-4505 |
Stray label lines (TypeScript, HTML) outside fences |
4 | L4532, L4555, L4597 |
| Duplicated nav markers | 2 | L178-180 (# Getting Started / # Getting Started Tab) |
| Orphan MDX components inside plain fences | 2 blocks | L260-322 (<Steps> / <Step>), L345 & L408 (stray </Step>) |
| Empty headings | 6 | L252, L853, L977, L2067, L2589, L3070, L3998 |
| Empty blockquote / stray bullet | 2 | L2263 (>), L3315 (*) |
| Bare URL as page content | 1 | L5 |
| Duplicate | Lines | Recommendation |
|---|---|---|
| Install instructions appear twice, with different wording | L188-206 and L472-484 | Keep the Installation and Entry points version as canonical; on HTML integration, replace with a one-line pointer. |
| Entry-point table appears twice, with different "use when" columns | L212-216 and L465-468 | Merge into one table; use it on the Installation page and link from Integration. |
registerEffects setup appears three times |
L496, L728-750, L2650-2674 | Canonical: Named Effects page. Others link to it. |
triggerType table appears four times with different wording |
L1031-1036, L1248-1253, L1356-1361, L2256-2261 | Keep the per-trigger tables (they legitimately differ per trigger) but make the wording of each row identical across pages. Add the viewEnter column to the effects-page table only. |
stateAction table appears twice |
L1465-1470, L2334-2339 | Same treatment. |
composite explanation appears three times |
L2228-2232, L3863-3873, L1023 | Canonical: multi-interaction compositions. Others summarise in one line + link. |
| Reduced motion appears four times | L2550-2585, L2868-2891, L4000-4027, L4296-4298 | Canonical: understanding conditions → Reduced motion. Others link. |
overflow: hidden caveat appears three times |
L1581-1601, L2311, L2831 | Canonical: viewProgress. Others summarise + link. |
Scroll-preset range requirement appears twice |
L1704-1732, L2831 | Canonical: viewProgress. |
| "Don't guess preset options" appears three times | L1732, L2546, L2790 | Canonical: Named Effects. |
| Hit-area-shift warning appears four times | L1262-1263, L2440, L3497, L2060 | Canonical: source and target resolving. |
- L288, L374, L434, L1318, L1407, L1798 —
effects: {}empty registries. Noise; delete. - L507 — pinned version
@wix/interact@2.5.1is stale (current is 2.5.4). Either bump or use a neutral@x.y.zplaceholder that will not rot. - L15 —
## This experience was built with Interact.— an orphan H2 with no body. - L2263 — a
>line with no content.
Almost every page opens with the site-page marker (# 🧑💻 viewEnter) followed by an in-page H1 (# Entrance Animations (\viewEnter`)). Pick one: the page marker becomes the site nav title (frontmatter title`), and the body starts at H2. Applies to all 27 pages.
Current mix: About Interact, Installation and Entry points, My first interaction, HTML integration, the config object, what is a trigger?, viewEnter, click & hover, keyframe Effects, transition Effects, custom Effects, Named Effects, what is an interaction?, source and target resolving, effects array & cascading logic, multi-interaction compositions, understanding conditions, responsive animation design, what is a list?, using lists, what is a sequence?, using sequences.
Recommendation: sentence case for all page titles, keeping API identifiers in code font: "What is a trigger?", "Keyframe effects", "Transition effects", "Custom effects", "Named effects", "Source and target resolving", "Effects array and cascading logic", "Using lists".
keyframe Effects(L2893) actually covers time effects (named + keyframe) and scrub effects including pointer properties. Either rename the page to "Time and scrub effects" or split the scrub half out and move the pointer content into thepointerMovechapter (see §6.5).transition Effects(L3064) and theWhat are effects?page's "State Effects" (L2315) are the same concept under two names. Pick one — see §8.
L994-996 says each of the eight triggers has its own chapter. Only six chapters exist (viewEnter, click & hover, viewProgress, pointerMove, animationEnd). interest and activate have none. Either add them or amend the promise.
pointerMove (L1857-2061), keyframe Effects → "Advanced pointer properties" (L3019-3023), and custom Effects → pointerMove (L3261-3303) all describe the same parameters — and the middle one is wrong (§3.8).
Recommendation: pointerMove owns the parameter reference. The other two keep only their payload-specific angle (how a keyframeEffect maps a single axis; what the progress object looks like in a customEffect) and link out.
The five trigger chapters do not follow a common order:
| Page | Order |
|---|---|
viewEnter |
intro → how it works → triggerType → params → caveats → FOUC → examples |
click & hover |
intro → payload families → a11y → conditions → hover → click → state effects → presets |
viewProgress |
intro → how it works → caveat → params → examples → presets → advanced pattern |
pointerMove |
intro → progress model → params → smoothing → payloads → examples → FOUC |
animationEnd |
intro → params → examples → chaining → rules |
Recommendation — canonical trigger-chapter order:
- What it is / when to use it
- How it works (underlying platform API)
- Trigger
params - Effect-level options (
triggerType/stateAction/range*) - Caveats and pitfalls
- Examples (config + HTML + CSS + Result)
- Working with presets
- See also
The same applies to the two effect-page pairs (what is a list? / using lists; what is a sequence? / using sequences) — the "what is" pages should be conceptual with one illustrative example; the "using" pages should be task-oriented with a consistent step structure.
Only 4 of 27 pages have one (L172-176, L820-825, L4033-4038, L4614-4618, L4725-4729), and two of those have unresolved links. Recommendation: every page ends with a "See also" list of 3-5 links, or none do. Given the site's cross-referential nature, add them everywhere.
Installation and Entry points → My first interaction → HTML integration → The final result. But HTML integration (L456-825) is by far the most complete integration reference and largely supersedes Installation and Entry points. Consider merging them, or clearly scoping the first page to "install + choose an entry point" and the second to "wire it up" (see §5.6).
L260-448 present three integrations (React, Web Components, Vanilla) sequentially, with the React one still wrapped in raw <Steps>/<Step> MDX inside a code fence, an orphan </Step> at L345 and L408, and a prose sentence sitting inside a bare code fence at L348-350. The three variants also have no headings identifying which is which.
Fix: rebuild as proper tabs (or three clearly-headed subsections), one per entry point, each with identical step structure: 1. Add the markup → 2. Define the config → 3. Create the runtime → 4. Clean up.
| # | Issue | Line |
|---|---|---|
| a | "every integration follows the same three steps" — followed by a four-item list | L511-516 |
| b | ## **Set up an Interaction** Types of triggers / Types of effects — three headings collapsed into one line plus an orphan |
L252-256 |
| c | ## **Combining triggers** → body is See [here]() |
L998-1000 |
| d | The final result + examples links page is an unwritten bullet outline |
L827-842 |
| e | Empty ## / # headings |
L252, L853, L977, L2067, L2589, L3070, L3998 |
| f | Named Effects §"3. Reference the preset by name" is buried inside a merged heading, so the numbered setup sequence reads 1 → 2 → (nothing) |
L2674 |
Three distinct voices are present:
Style A — narrative reference (detailed). Concept framing → mechanism → caveats → complete worked example (config + HTML + CSS) → Result: paragraph → "Key remarks". Used by: HTML integration, viewEnter, click & hover, viewProgress, source and target resolving, effects array & cascading logic, multi-interaction compositions, understanding conditions, what is a list?.
Style B — terse spec. Type snippet → bullet list of fields → minimal fragment example, no HTML, no result. Used by: pointerMove, animationEnd, keyframe Effects, transition Effects, custom Effects, using sequences.
Style C — marketing/conceptual. No code, or code without context; diagram-style pseudo-blocks. Used by: About Interact, what is an interaction?, what is a trigger?.
Decision (per the brief — prefer the clearer, more detailed version): adopt Style A as the house style for every reference and how-to page. Keep Style C only for About Interact and the two "what is…" conceptual openers, and even there add one runnable example.
# <Page title> ← sentence case, no emoji, no owner line
<1-2 paragraph intro: what this is and when you reach for it>
## How it works
<the underlying mechanism — platform API, where it runs, what it maps to>
## <Parameters / Options>
<table: name | type | default | description — always include a Default column>
## <Behaviour tables> ← triggerType / stateAction / range names
<identical row wording across pages>
## Caveats
<callouts, using a single consistent admonition set — see 7.3>
## Example: <specific, named scenario>
<config> <html> <css>
**Result:** <one paragraph describing what the user sees>
## See also
- 3-5 links
| Page | Missing relative to Style A |
|---|---|
pointerMove |
No HTML markup with any example; no Result: paragraphs (only one, at L2054, and it is not bolded like the others); no "See also". |
animationEnd |
No HTML; examples are config fragments only; "Important rules" is a flat bullet list where other pages use prose + callouts. |
keyframe Effects |
Leftover frontmatter headings; two examples are unlabelled fragments; the accessibility paragraph (L2971) is a wall of prose where other pages use a code example. |
transition Effects |
Three long unbroken prose paragraphs (L3078-3080, L3108, L3159-3161) where the parallel click & hover state section uses tables + examples; no HTML; no Result:. |
custom Effects |
No Result: paragraphs; the cancellation example (L3317-3329) has broken indentation and a TS cast in a JS fence. |
using sequences |
Numbered-step format is fine, but examples are fragments with no HTML, no Result:, and two unlabelled fences. |
using lists |
Examples are tables (B6); needs a full rebuild. |
what is an interaction? |
Purely conceptual with pseudo-code blocks; should carry at least one real config. |
responsive animation design |
Good examples, but no HTML, no Result:, and the "Best practices" section is a bullet list where other pages use prose. |
Currently in use: > **Info:**, > **Tip:**, > **Note:**, > **Important:**, > **CRITICAL:**, > **Pitfall:**, > **Reminder:**, > **Don't guess…**, ### **⚠️ Pitfalls**, and plain bold paragraphs ("Key remarks", "Notes", "Two rules that trip people up", "Two things to keep in mind").
Standardise on four: Note, Tip, Warning, Critical. Map: Info/Note/Reminder → Note; Tip → Tip; Pitfall/Important → Warning; CRITICAL → Critical. Convert "Key remarks" / "Notes" / "Two rules that trip people up" / "Two things to keep in mind" into a consistent ## Key points section or into individual admonitions.
Currently: ### Example: a feature card reveal, ### Example: a counter that plays on every visit, ### **Example: a product card that responds in layers**, ### Real-world example: card entrance, ### Real-world example: theme switcher, ### Basic transition effect, ### Example: named effect, ### **Example: Staggering Card Entrances**, ## Example: composing two keyframe effects.
Standardise on: ### Example: <lowercase scenario description> — drop "Real-world", drop Title Case, drop the bold wrapper.
Used on some pages as **Result:** (L1698, L1853, L3436, L3691, L3855), on others as plain Result: (L1138, L1180, L1348, L1434, L1532, L2054), and omitted entirely on five pages. Standardise on bolded **Result:** after every complete example, and add one to every example that lacks it.
Pick one term per row and apply it throughout.
| Concept | Currently used | Recommended |
|---|---|---|
| The state/transition effect kind | "State Effect" (L2189, L2315), "Transition effect" (L3072), "CSS style toggle", "state effects" | "State effect" everywhere; on the dedicated page, open with "State effects (also called transition effects, after the transition field)". |
| The scrub effect kind | "Scrub Effect" (L2188), "scroll-driven", "continuous trigger", "progress-based" | "Scrub effect" for the effect kind; "continuous trigger" for viewProgress/pointerMove. |
| The time effect kind | "Time Effect", "Time effects", "time-based effect", "time-based animation payload", "event trigger" | "Time effect" for the effect; "event trigger" for the trigger. |
| Presets | "named effect", "namedEffect", "preset", "Named Effects", "ready-made effects" | "named effect" in prose, namedEffect for the field, "preset" only when referring to the @wix/motion-presets package contents. |
| The library | "Interact", "@wix/interact", "the library", "@wix/interact" |
"Interact" in prose; @wix/interact when naming the package. |
| The flash problem | "flash of un-animated content (FOUC)" (L515), "Flash of Unstyled Content (FOUC)" (L2058), "Flash Of Un-styled Content (FOUC)" (L3490), "entrance flash" (L1091, L1350) | "flash of unstyled content (FOUC)", defined once, abbreviated thereafter. |
| The keyed element | "keyed element", "keyed root", "root element", "the <interact-element>", "the element registered for the key" |
"keyed element" for the bound element; "root" only inside resolution descriptions where it is defined. |
| Concept capitalisation | "Interaction"/"interaction", "Effect"/"effect", "Sequence"/"sequence", "Condition"/"condition", "Trigger"/"trigger" — inconsistent within single pages (e.g. L855-882 vs L2177) | Lowercase in prose; capitalise only when naming the TypeScript type (Interaction, Effect) or the React component (<Interaction>). |
| Source/target | SOURCE/TARGET uppercase comments used in ~half of the examples |
Use them in every example where source ≠ target; omit where they are the same. |
key terminology |
"interaction key", "element key", "data-interact-key", "interactKey" |
"key" generically; name the binding mechanism per integration once, in the resolution chapter. |
| Effect-registry entries | "the top-level effects map", "the effects registry", "EffectRef" |
"the effects registry"; use EffectRef only when naming the type. |
| # | Issue | Instances | Recommendation |
|---|---|---|---|
| C1 | Fence languages: ts (80), javascript (27), html (21), css (7), shell (6), typescript (2), tsx (2), java (1), none (~18) |
throughout | ts for all TypeScript/config, tsx for JSX, js only where the sample is deliberately plain JS, html, css, bash for shell. Fix java at L4557. Label all 18 unlabelled fences (L599, L656, L2678, L4680, L4700 and others). |
| C2 | Indentation: 2-space on most pages, 4-space on keyframe Effects and transition Effects (L2909-2938, L3089-3129) |
2 pages | 2 spaces everywhere. |
| C3 | Quotes: single in most ts samples, double in the About page and using lists samples |
~6 samples | Single quotes. |
| C4 | Keyframe values: opacity: 0 (number) vs opacity: '0' (string) — both valid, mixed within the same page |
throughout | Pick numbers for numeric properties, strings for anything with a unit or function. |
| C5 | Config fragments vs. complete configs — most examples are bare object fragments that will not compile if pasted | ~40 samples | Wrap every example a reader might copy in const config: InteractConfig = { interactions: [ … ] };. Keep fragments only for field-level illustration, and prefix them with // inside interactions[]. |
| C6 | Interact.create(config) shown in some examples, omitted in most |
mixed | Include it only in "getting started"/integration examples; omit consistently in reference examples. |
| C7 | Broken indentation | L2950-2951 (params: { threshold: 0.3 } split across lines before a comma), L3813-3816 (keyframeEffect: { / name:), L3319-3328 (custom-effect cancellation) |
Reformat. |
| C8 | TypeScript syntax inside javascript fences |
L3266 (type PointerProgress = …), L3319 (element as HTMLCanvasElement) |
Change fence to ts. |
| C9 | Nested/doubled fences (``inside`) | L260-322, L326-346, L390-409, L2907-2919, L2925-2940 | Flatten. |
| C10 | Literal ellipses inside otherwise-valid code | L4504 (keyframes: [...]), L2308 (/* ... */), L3708-3709 |
Use /* … */ consistently, or complete the sample. |
| C11 | Smart quotes inside code-adjacent text | L3002-3003, L3021-3023 (‘percentage’, ‘self’, ‘root’, ‘both’) |
Convert to straight quotes and wrap in backticks. |
| C12 | Type snippets styled as css |
L2286-2288 (animation-range: { name: … } in a css fence — that shape is TypeScript, not CSS) |
Change to ts and remove the invented animation-range: prefix. |
| # | Defect | Line | Fix |
|---|---|---|---|
| F1 | Broken table — a 3-column table row contains an unescaped | inside a type union, producing a 4-cell row |
L3918 (| \type` | `'media'` | `'selector'` | How the condition is evaluated. |`) |
Escape as `'media'` | `'selector'` or move the union into the description cell. |
| F2 | Bold-wrapped headings (## **Title**) |
~90 headings | Remove the **; heading level already conveys emphasis. |
| F3 | Table alignment markers inconsistent: :---- on most tables, ----- on others |
L1248-1253, L1356-1361, L1465-1470 | Use :---- everywhere. |
| F4 | Trailing double-space line breaks (Google Docs soft wraps) creating unintended <br> |
throughout bullet lists, e.g. L1038-1041, L2195-2198, L3399-3400 | Strip. |
| F5 | Non-breaking / stray whitespace at line ends | L15, L380, L442, L1885, L2864, L4569 | Strip. |
| F6 | Escaped backticks inside headings and prose, rendering as literal \` |
L454, L2674, L2812, L2831, L2864, L2905, L2921 | Unescape. |
| F7 | Table cells containing multi-line code (the using lists samples) |
L4473-4505 | Convert to fenced code blocks. |
| F8 | Heading levels skip (H1 → H3 with no H2) | L2652 (### 1. Install the package under an H2 that is itself inside a merged heading), L4630 |
Normalise. |
| F9 | Inconsistent list markers (* vs -) — * on the 🧑🌾-authored pages, - on the 🧑💻 pages |
throughout | Use - everywhere. |
| F10 | ✅ / ⚠️ / ❌ emoji used as semantic markers in some places only |
L2536-2537, L1590-1596, L3540, L3551, L3702, L4609 | Keep ❌/✅ for do/don't code pairs (they read well); replace bare ⚠️ headings with the standard Warning admonition. |
- Remove owner line (L3), bare URL (L5), visual placeholder (L13),
[Add link…](L29), orphan H2 (L15). - Fix dangling
effectIdreferences in both configs (§3.22); add theeffectsregistry or inline the payloads. - Make the two configs (L59-89, L119-148) consistent with each other — same keys, same list fields.
- Label the three unlabelled fences (L41, L49, L57); the first two are conceptual pseudo-blocks and should be prose or a diagram, not code fences.
- Resolve the three
See alsolinks (L174-176). - Add a sentence noting that configs are JSON-serializable except
customEffect(M16).
- Delete the duplicated marker lines.
- Remove owner line (L184).
- Reconcile the entry-point table with L465-468 (§5.6); keep one canonical version here.
- Add a note that
generate()is exported from all three entry points.
- Remove owner line (L250); fix the mangled headings (L252-256).
- Rebuild as three labelled tabs/subsections with identical step structure (§6.9); remove the raw
<Steps>/<Step>MDX and orphan</Step>tags (L345, L408); move the prose at L348-350 out of its code fence. - Change the example away from the hit-area-shift anti-pattern (§3.21).
- Remove
effects: {}(L288, L374, L434). - Replace "TO DO ADD VISUAL DEMONSTRATION" (L452) with the demo or delete the line.
- Un-merge the callout at L454 into a proper Note.
- Remove owner line (L458).
- Fix "three steps" → four (L511-516).
- Fix
generate(config)→generate(config, false)for vanilla (L699). - Fix the
Interact.forceReducedMotionrow in the static API table (L792): it is detected from the browser'sprefers-reduced-motionsetting, notfalseby default (§3.13). Addgenerate()'s thirdoptionsargument to the same table. - Update or neutralise the pinned CDN version (L507).
- Reword the "comes bundled" callout (L470) —
@wix/motionis a dependency, not bundled. - Add: browser support (M23), SSR caveat (M24),
useCustomElementoption (M6),setup()replace-not-merge (M7), CSS embedding options incl.blocking="render"(M5),remove()scope (M21). - Promote the FOUC section (L754-777) into the canonical FOUC reference, and add non-
onceguidance (M3) anddata-interact-enter(M4). - Fix the React example:
generate()is called on every render — hoist it oruseMemo. Fix thecreateInteractRefexample: it is called during render, producing a new callback each time and churning add/remove; showuseRef/useMemo(or state that<Interaction>is preferred). - Resolve 11
ADDLINKs.
- Write the page. It is currently a six-bullet outline. It should be the capstone: one complete, runnable page (HTML + generated CSS + config +
create()) that exercisesviewEnter+ FOUC, a hover state effect, and aviewProgressscrub, with the output shown.
- Remove owner line (L847), empty heading (L853).
- Add
containerto (or remove it from) theConditiondescription in line with the §3.9 decision. - Resolve the four empty
()links (L865, L874, L878, L882). - The example (L899-953) is good — keep it as the canonical "everything together" config and cross-link it from the sequences and lists pages.
- Remove owner line (L959), empty heading (L977), site-builder note (L994).
- Add the "no params for hover/click/interest/activate" note (M15).
- Either write "Combining triggers" (L998-1000) or delete the section and link to
multi-interaction compositions. - Resolve the promise that every trigger has a chapter (§6.4).
- Remove owner line (L1004).
- Replace the Google Docs link (L1093) with the internal FOUC chapter link.
- Remove the reviewer note at L1180 and its dangling "threshold limitation" reference — or write the limitation it refers to.
- Add
useSafeViewEnterto theparamslist (L1070-1073) and to theInteract.setupmention (M22). - Add non-
onceFOUC guidance (M3). - Otherwise this page is the strongest in the set — use it as the Style A exemplar.
- Remove owner line (L1184).
- Keep the kebab-case
stylePropertiesexample (L1450-1457) — it stays the reference example and the house style even though camelCase is also accepted (§3.1); cross-reference it from the effects pages. - Promote the inline-state-identity paragraph (L1476) to a Warning (M20).
- Add
interest/activatecoverage explicitly if no separate chapters are added (§6.4).
- Remove owner line (L1560).
- Fix the
containdefinition (§3.15). - Add the reduced-motion skip note (§3.14).
- Remove the editorial aside at L1730.
- Fix the two
http://Configuration/...links (L1736, L1749).
- Remove owner line (L1859).
- Keep the reduced-motion claim at L1867 and link it to the reduced-motion section (§3.13).
- Add
transitionDelay(M9). - Fix the sentence fragment at L1885.
- Convert smart quotes to straight quotes (L1863-1872 and following).
- Bring up to Style A: add HTML markup to at least one example, add Result: paragraphs, add "See also" (§7.2).
- Resolve the empty
[viewProgress]()link (L1865).
- Remove owner line (L2065), empty
#heading (L2067). - Soften the chain-isolation claim (§3.17).
- Bring up to Style A: add HTML, add Result: to both examples, convert "Important rules" into prose + Warning admonitions (§7.2).
- Remove owner line (L2171), empty blockquote (L2263), empty heading (L2589).
- Fix the camelCase-only claim (L2341) and the example (L2352-2353) — kebab-case example plus the "either casing works" note (§3.1).
- Fix
'Both'→'both'(L2225). - Fix the
css-fenced type snippet (L2286-2288) (§9 C12). - Add missing scrub fields:
iterations,alternate,reversed,transitionDelay(M9, M10). - Add the named-easing catalogue to the easing table (M2).
- Add
iterations: 0→ Infinity (M11). - Add the
transitionvstransitionPropertiesprecedence rule (§3.19) and thedurationgotcha (§3.20). - Add
interest/activateto the State Effect trigger row (L2189). - Add perspective guidance to Performance (M13).
- Rewrite the reduced-motion section (L2550-2585) as the single reference for the behaviour, and drop the hand-rolled
matchMediaassignment at L2585 (§3.13).
- Remove owner line (L2611).
- Remove
data-interact-initial="true"(L2812) (§3.5). - Un-merge the three callouts that became headings (L2674, L2812, L2831) and restore the numbered setup sequence 1 → 2 → 3.
- Fix "Ongoing … any trigger" → time-based triggers only (§3.18, L2633, L2833).
- Decide on
CustomMouse(M18) and note the experimentalBg*presets (M17). - Reconsider "A flat string (
'120px') also works" (L2793): it works at runtime (parseLengthaccepts strings) but is not in the public TypeScript type, so it will fail type-checking. Recommend documenting only the{ value, unit }object form. - Fix the escaped-backtick prose at L2864.
- Frame the preset-swap guidance (L2870-2891) as the gentler alternative on top of the built-in reduced-motion handling, not as the only protection (§3.13).
- Make all
generate(calls explicit aboutuseFirstChild.
- Remove owner line (L2895); remove the three frontmatter-as-heading lines (L2899, L2973); rename the page (§6.3).
- Fix the
hitAreadescription (L3021) (§3.8) — or delete the pointer section and link topointerMove(§6.5). - Keep the reduced-motion claim at L3023 and link it to the reduced-motion section (§3.13).
- Convert smart quotes throughout (L2921, L2971, L3002-3003, L3021-3023).
- Break the three prose walls (L2921, L2971, L3002, L3021-3023) into the standard structure.
- Fix indentation (4→2 spaces) and the split
paramsat L2950-2951. - Flatten nested fences (L2907-2919, L2925-2940).
- Add HTML + Result: to the two "real-world" examples.
- Remove owner line (L3066), empty heading (L3070), frontmatter heading (L3173 belongs to the next page but check L3072 area).
- Rewrite the camelCase
styleProperties/transitionPropertiesexamples to kebab-case (L3096-3098, L3113-3127, L3146-3148) and add the "either casing works" note (§3.1). - Break the three prose walls (L3078-3080, L3108, L3159-3161) into tables + examples, mirroring the
click & hoverstate section. - Unescape the
@propertyexample at L3161 and put it in acssfence. - Add the
transition-wins precedence rule (§3.19) and thedurationgotcha (§3.20). - Add HTML + Result: to the theme-switcher example.
- Remove owner line (L3169), frontmatter heading (L3173), stray bullet (L3315).
- Remove
pageVisible(L3218) (§3.7). - Fix the TS-in-JS fences (L3266, L3319) and the broken indentation in the cancellation example (L3317-3329).
- Add the JSON-serializability note (M16).
- Resolve the empty
[viewprogress chapter]()link (L3243). - Add Result: paragraphs.
- Remove owner line (L3335), editorial instruction (L3337), visual placeholder (L3341).
- Add at least one real config so the page is not purely conceptual (§7.2).
- Remove owner line (L3391).
- Fix recap steps 2 and 3 (L3633-3638) —
querySelectorAllnotquerySelector;listItemSelectordoes not filter (§3.2, §3.3, §3.4). - Fix the
listItemSelectorfilter claim and example (L3601-3618) (§3.2). - Fix the
listContainer+selectorclaim (L3621) (§3.4). - Fix the four
http://Configuration/...links (L3438, L3623) and the two in-page anchors (L3482). - This page and
what is a list?/using listsmust state identical resolution rules — reconcile all three.
- Remove owner line (L3644).
- Fix three
http://Configuration/...links (L3697, L3714, L3762). - Content is accurate — verified against the per-interaction custom-property mechanism in
src/core/css.ts:440-513.
- Remove owner line (L3766).
- Fix the broken indentation at L3813-3816.
- Fix three
http://Configuration/...links (L3770, L3859, L3875). - Make this the canonical
compositereference (§5.6) and trim the duplicate explanations elsewhere.
- Remove owner line (L3881), empty heading (L3998).
- Fix the broken table at L3918 (§10 F1).
- Fix "reported as errors" (L4031) (§3.10).
- Decide on
containerconditions (§3.9) and make the type table agree with theresponsive animation designpage. - Add
&support in selector predicates (M12). - Rewrite the reduced-motion section (L4000-4027) to link to the canonical one on
what are effects?and drop "you can also force reduced-motion behavior globally, regardless of the OS setting" (L4023-4026) — the OS setting is now the default (§3.13). - Add the "
forceReducedMotionmust be set beforecreate()" note (M8). - Resolve five
ADDLINKs (L3946, L4031, L4035-4038).
- Remove owner line (L4042).
- Resolve the
containercondition example (L4145-4197) (§3.9). - Add HTML + Result: to the three examples.
- Resolve the inline
<add link to…>note (L4298). - The cascade explanation (L4050-4058) duplicates
effects array & cascading logic— trim to a summary + link.
- Remove owner line (L4304).
- Fix the
listItemSelectorclaim (L4358) (§3.2). - The
selector-only row of the comparison table (L4380) is correct — keep, and align thesource and target resolvingrecap to it. - The comparison table at L4376-4382 has lost its ✓/✗ markers. Five cells now begin with a bare leading space (
| dynamic tracking + stagger |,| a filtered subset of children |,| (filtered) |,| \querySelectorAll` matches |`), so both "Targets multiple elements?" and "A managed list?" columns read as unanswered. Restore the markers (or convert the two yes/no columns to explicit "Yes/No" text, which survives copy-paste better).
- Remove owner line (L4448).
- Rebuild all five code samples out of Markdown tables into fenced blocks (L4473-4505) (B6).
- Fix
generate(config)→generate(config, false)(L4481). - Complete the truncated sample at L4504 (
keyframes: [...]). - The three-property table (L4458-4462) is the most accurate description of
listItemSelectorin the whole document — promote its wording to the other two pages.
- Remove owner line (L4514).
- Complete the
SequenceConfigtype (L4534-4539) (§3.12). - Fix the
javafence (L4557) and the strayTypeScript/HTMLlabels (L4532, L4555, L4597). - Replace the three
google.com/search?q=ADDLINKlinks (L4616-4618). - Rewrite the "Overshooting durations" pitfall (L4612) into something actionable, or delete it.
- The example duplicates the
the config objectexample (L899-953) verbatim — keep one and cross-link.
- Remove owner line (L4622).
- Fix the
assertValidInteractConfigclaim (L4723) (§3.11). - Add
animationEndto the supported-trigger list (L4678) and state thatviewProgress/pointerMoveare unsupported (§3.16). - Label the two unlabelled fences (L4680, L4700).
- Fix the missing comma in the config at L4663-4664 — the
sequences: { … }block is not followed by a comma beforeinteractions:, so the sample is a syntax error. - Add HTML + Result: to the examples.
- Resolve the four unlinked "See also" entries (L4727-4729).
These are defects in the shipped agent rules and package metadata, discovered while auditing. They are out of scope for the documentation file but should be tracked, because agents and humans will otherwise get contradictory guidance.
| # | File | Issue |
|---|---|---|
| A1 | packages/interact/rules/full-lean.md:471, :507, rules/click.md:70, :133, rules/hover.md:72, :134, rules/viewenter.md:152, rules/viewprogress.md:58 |
Casing documented as a single required form (camelCase for both state properties and keyframes). Per §1.1 both casings are accepted everywhere; the rules must say so and name the idiomatic form per surface — kebab-case for state properties (as packages/interact/README.md:326 already does), camelCase for keyframes, --* verbatim. |
| A2 | rules/full-lean.md:239 |
hitArea default documented as 'self'. Runtime default is effectively 'root' (src/handlers/pointerMove.ts:39). Note that interact-validate/src/semantic/fouc.ts:55-57 assumes the 'self' default when deciding whether to raise HIT_AREA_SHIFT — so either the runtime default or the rule/validator needs to change. |
| A3 | rules/full-lean.md:493-496 |
Mouse preset list has 9 entries; 12 are exported (BounceMouse, SpinMouse, CustomMouse missing). |
| A4 | rules/full-lean.md:452 |
Claims per-property transitionProperties take precedence when both are set. Wrong — src/utils.ts:62 ignores transitionProperties entirely when transition is present. |
| A5 | rules/full-lean.md:691 |
Source resolution claims listItemSelector filters which children become sources. Wrong (§3.2). |
| A6 | rules/full-lean.md:284-290 |
ViewEnterParams omits useSafeViewEnter, which exists in the type and the validator schema. |
| A7 | packages/interact/llms.txt:7 |
"Five trigger types: hover, click, viewEnter, viewProgress, pointerMove" — there are eight (animationEnd, activate, interest missing). |
| A8 | packages/interact/llms.txt:24 |
Links to rules/plugins.md describing Interact.use() and $-prefixed config fields. Neither the file nor the API exists in this repo. |
| A9 | interact-validate/src/semantic/fouc.ts:59-64 |
HIT_AREA_SHIFT only inspects transform strings; bare scale / translate / rotate keyframe properties (the individual CSS transform properties) escape detection — which is exactly what the My first interaction example uses. |
| A10 | src/core/add.ts:57-59 vs :79-85 |
listContainer + selector resolves differently at initial bind (container.querySelectorAll) than for MutationObserver-added items (child.querySelector). Likely a bug; documenting it as-is would be documenting an inconsistency (§3.4). |
| A11 | src/types/config.ts:5 |
Condition.type accepts 'container' but nothing implements it — conditions of that type are silently dropped (§3.9). Either implement @container emission or remove the type. |
| A12 | interact-validate/src/semantic/cssSyntax.ts, src/errors.ts:33, interact-validate/README.md:150, :214, rules/validate.md:59, :143, :237 |
KEYFRAME_PROP_NOT_CAMEL_CASE (rule category KEYFRAME_STYLE) warns on kebab-case keyframe properties, which §1.1 makes valid. Replace it with a check for names that are neither valid camelCase nor valid kebab-case, covering styleProperties / transitionProperties names too, and update both code tables (see dual-casing-support-plan.md §5). |
| A13 | rules/full-lean.md:41, :712, :716, rules/integration.md:349, :353, skills/interactor/references/config-schema.md:344, :396, :403, docs/api/interact-class.md, docs/api/functions.md, docs/api/README.md, docs/api/types.md:285 |
Reduced-motion and generate() signature text predating §1.1 (2026-07-28): forceReducedMotion described as "force … regardless of OS setting, default false", the hand-rolled matchMedia assignment recommended, and generate() documented with two parameters. Fixed alongside the 2.6.0 implementation — listed here because the same wording may have been copied into other surfaces (llms.txt and the docs site itself). |
| A14 | packages/motion/src/motion.ts:198-214 |
getAnimation() returns the CSS-backed animation from getElementCSSAnimation() before the reducedMotion option is consulted, so Interact.forceReducedMotion = true cannot shorten an animation that the generated CSS already declared. The 2.6.0 @media (prefers-reduced-motion: reduce) rules cover the detected case, but forcing reduced motion on a device whose browser reports no preference still affects JS-driven effects only. Closing the gap needs a root-attribute gate in the generated CSS that JS can toggle. Documented as a known limitation in docs/api/functions.md. |
| Claim group | Source of truth |
|---|---|
| Trigger list (8) | src/types/triggers.ts:7-15 |
Default triggerType per trigger |
src/core/resolvers.ts:19-26 (viewEnter/animationEnd → once; hover/click/activate/interest → alternate) |
| hover/click playback semantics | src/handlers/effectHandlers.ts:34-88 |
viewEnter playback semantics, threshold 0.2, inset negation, useSafeViewEnter, exit observer |
src/handlers/viewEnter.ts:11-51, 91-153, 198-291 |
a11y upgrade (hover→interest, click→activate) |
src/handlers/index.ts:9-29, src/handlers/constants.ts |
Keyboard handling (Enter/Space, preventDefault on Space, tabIndex = 0 on focusin) |
src/handlers/eventTrigger.ts:36-46, 172-177 |
stateAction semantics incl. clear |
src/core/InteractionController.ts:107-142, src/handlers/effectHandlers.ts:91-130 |
FOUC: data-interact-enter, DEFAULT_INITIAL, shouldUseInitial |
src/core/css.ts:26-32, 246-263, src/core/utilities.ts:19-28, src/handlers/viewEnter.ts:216-237 |
generate(config, useFirstChild = true, options?) |
src/core/css.ts (generate) — third argument added post-§1.1 |
| State-property CSS emission | src/core/cssUtils.ts:148, src/utils.ts:58-98 — [verified by execution] (pre-dual-casing behaviour; see §1.1) |
| Element resolution | src/core/add.ts:43-105, src/core/Interact.ts:331-353 |
listItemSelector usage sites |
src/core/Interact.ts:340, src/handlers/effectHandlers.ts:112, src/core/utilities.ts:31-33 (exhaustive grep) |
| Reduced motion | src/core/add.ts (all reducedMotion: call sites pass Interact.forceReducedMotion), src/handlers/pointerMove.ts:24-26, src/handlers/viewProgress.ts:25-27. Post-§1.1: detection in src/core/Interact.ts + src/utils.ts (prefersReducedMotion), CSS overrides in src/core/css.ts (buildReducedAnimationDeclarations), shared rule in @wix/motion's src/api/common.ts (getReducedMotionOptions) |
Conditions: media merge, selector :is() + &, container unimplemented |
src/utils.ts:41-46, 153-192, src/core/css.ts (only 'media' is ever requested) |
| Cascade / coexistence mechanics | src/core/css.ts:429-513 (per-interaction custom property per target; per-sequence-index custom properties; buildListsRule concatenation) |
| Sequence resolution and defaults | src/core/resolvers.ts:103-165, src/utils.ts:20-34 |
| Sequence trigger dispatch | src/core/add.ts:376-399 |
| Validator codes and severities | interact-validate/src/errors.ts:14-35, structural.ts:42-88, schema/interactions.ts:250-345, schema/primitives.ts:39-60 |
| Preset inventory | packages/motion-presets/src/library/{entrance,scroll,ongoing,mouse,backgroundScroll}/index.ts — 19 / 19 / 13 / 12 / 12 (bg marked not production ready) |
Preset option shapes (direction, range, distance, iterationDelay) |
packages/motion-presets/src/types.ts, consts.ts, utils.ts:395-445, library/entrance/*.ts, packages/motion/src/types.ts:1-50, 133-136 |