Skip to content

Feature/schemadoc#25

Open
ronnorthrip wants to merge 219 commits into
mainfrom
feature/schemadoc
Open

Feature/schemadoc#25
ronnorthrip wants to merge 219 commits into
mainfrom
feature/schemadoc

Conversation

@ronnorthrip
Copy link
Copy Markdown
Collaborator

Schema doc examples added

zachiler and others added 30 commits April 12, 2026 00:01
Add onPostTick(callback) to AnimationEngine. Post-tick callbacks fire
after all regular tick callbacks complete each frame, before the next
rAF is scheduled. Returns an EngineHandle with stop() to unregister.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ng a private one

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eout

The test asserts register was called then stops the timeline cleanly
instead of awaiting the play promise (which would hang under fake timers
without advancing the injected engine).
…erformance.now()

Replace wall-clock pause tracking (performance.now()) with engine-elapsed values
so pause/resume works correctly with fake schedulers and virtual replay engines.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mer, track position internally

Replace performance.now() with engine-provided elapsed parameter for particle
timing, ensuring correct behavior when rAF is throttled in background tabs.
Remove wall-clock setTimeout safety timer in favor of engine-based 2x-duration
check. Track particle position on the object itself instead of reading DOM
attributes, decoupling getCurrentPosition() from the SVG renderer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The `elapsed > ms * 2` branch was mathematically unreachable since
`progress >= 1` always fires first. Removed it, leaving only the
`progress >= 1 || !parentNode` check. Also removed the never-read
`done` field from ActiveParticle and all construction sites/mocks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add LRU cache to svgPathToFunction to reuse parsed functions for identical path strings
- Cache size limited to 64 entries; evicts oldest entry when limit reached
- Export _clearPathCache() for test isolation
- Add memoization tests verifying same path returns cached instance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ON roundtrip

BREAKING: node.data containing non-cloneable values (functions, Symbols)
will now throw DataCloneError during snapshot instead of silently
dropping them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…text

Adds a generic type parameter to FlowTimeline, TimelineStep, StepContext,
and StepEntry so consumers can type their step context. Default is
Record<string, any> for full backwards compatibility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split the ~500-line method into _validateStepTargets, _isEmptyStep,
_captureNodeFromValues, _captureEdgeFromValues, _resolveFollowPath,
_createGuidePath, _executeInstantStep, _prepareAnimatedEdges,
_executeFollowPathStep, _executeAnimatedStep, _buildAnimateTargets,
_executeEdgeLifecycleOnly, _interpolateFollowPathTick,
_tickEdgeTransitions, and _cleanupEdgeTransitions.
No behavior changes — existing 87 timeline tests are the guard.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…to canvas-particles module

Moves sendParticle, _tickParticles, and particle lifecycle into a
focused module. Prepares for the Tier C particle renderer system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds destroy() to the animation mixin that stops all in-flight animations,
particles, and active timelines. Wires it to flow-canvas.ts destroy() so
the engine is fully cleaned up when the canvas element leaves the DOM.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ve engine idle

PostTick callbacks with keepAlive: true now keep the rAF loop running
even when no regular tick callbacks are registered. This ensures
recorders (Tier E) receive every frame, including idle gaps between
animation bursts. Default behavior (keepAlive: false) is unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ter methods

Introduces a StepExecutionContext interface that bundles all computed
step values into a single object. Refactors _executeFollowPathStep
(18 params), _executeAnimatedStep (12 params), _executeInstantStep
(6 params), and _executeEdgeLifecycleOnly (9 params) to accept the
context object instead of positional parameters. All 87 timeline tests
pass unchanged, confirming no behavioral regression.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- M-1: add comment to EASING_MAP documenting easeCirc*/easeExpo* abbreviations vs d3-ease names
- M-2: narrow isReducedMotion config param from `any` to typed shape
- M-3: remove duplicate animator/timelines/particles cleanup block in flow-canvas destroy()
- M-4: extract checkReducedMotion() to easing.ts; replace divergent implementations in FlowTimeline and canvas-animation mixin

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e() entry

Adds _snapshot and _target readonly getters to AnimationHandle. Both Maps are
populated at animate() call time (capturing the blend/compose from-value for
in-flight keys) and persist for the handle's lifetime after completion, forming
the foundation for rollback stop mode and the reverse direction state machine.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ze, superseded)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, play/restart, startAt, ping-pong

BREAKING: handle.reversed replaced by handle.direction. loop: 'reverse'
kept as alias for 'ping-pong'. Finished handles now survive — reverse()
plays backward instead of being a no-op.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nish

Introduces HandleRegistry for tag-based animation lifecycle control.
Adds tag/tags options to AnimateOptions and AnimateInternalOptions,
registers handles on the Animator.registry, and auto-deregisters via
queueMicrotask on natural completion or explicit stop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…roup()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e auto-cancellation

Adds `while` predicate support to AnimateInternalOptions and ActiveGroup so animations
auto-cancel each frame when the predicate returns false, using the configurable
`whileStopMode` ('jump-end' | 'rollback' | 'freeze', default 'jump-end'). Also exposes
`while`, `boundTo`, and `whileStopMode` on the public AnimateOptions type in types.ts —
`boundTo` is defined for the canvas integration layer (Task 8) to compile into a `while`
predicate; the Animator itself only handles `while`. Five new tests cover all stop modes
and the paused-animation safety invariant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…hot, boundTo on $flow

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rection timing, group tag merge

- I-1: _revive() now re-registers handle in HandleRegistry; microtask
  unregister is guarded to skip if group was revived before flush
- I-2: _playDirection() applies the same startTime adjustment as
  _reverse() for seamless mid-flight direction changes
- I-3: FlowGroup.animate()/update() preserve user-provided tag in the
  tags array instead of silently overwriting it
- I-4: ActiveGroup.loop type narrowed from 'boolean | reverse | ping-pong'
  to 'boolean | reverse' to match normalized runtime value

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… and timeout safety

Timeline steps can now include an `await` field that blocks execution until
a promise resolves. Supports raw Promises, objects with `.finished` (animation
handles), and thunks evaluated lazily at step activation. Optional `timeout`
prevents hanging by emitting `step-timeout` and advancing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… }) with pause/stop propagation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…... })

Adds FlowTimeline#timeline(builder, options?) as thin sugar over step({ timeline: sub }). The method creates a new sub-timeline, passes it to the builder callback, pushes it as a step entry, and returns the sub for individual targeting. Includes three passing tests covering sequential execution, return type assertion, and the independent option.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
zachiler and others added 30 commits April 22, 2026 10:21
8 issue codes: dangling-edge, duplicate-node-id, missing-condition,
condition-missing-branch, unhandled-source-handle, wait-missing-duration
(errors); unreachable-node, cycle (warnings). Mirrors validateSchema —
pure helper, no mutation, attached onto canvas via the workflow addon
setup callback. 14 vitest tests covering each code + happy path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tted duration

Renders .flow-wait-node host with header (optional icon, label, formatted
duration) plus top target and bottom source handles. Reads node.data
(durationMs, label, icon) reactively via Alpine effect. textContent only,
never innerHTML, for XSS safety. Structural CSS in css/structural.css and
theme defaults in css/theme-default.css using existing tokens — no new
CSS variables introduced. 12 vitest tests covering class/attribute
stamping, default and custom labels, duration formatting (ms/s/m), handle
positions, icon rendering, and missing-data fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ndations

feat(workflow): addon foundations — validate + wait
Adds the FlowRunHandle tracking surface every workflow UI primitive needs:
- canvas._currentRunHandle: FlowRunHandle | null — set by run.ts during a
  run, cleared in the finally cleanup path.
- canvas.runState: 'idle' | 'running' | 'paused' | 'stopped' getter
  derived from the handle's flags.
- canvas.stopRun(): forwards stop() to the active handle.
- New WorkflowRunState type exported.
- Setup callback also wraps any pre-existing resetStates() so condition
  nodes get _branchTaken cleared (Task 3 lands the run/replay write side).

5 vitest tests covering idle/running/paused/stopped transitions plus the
handle lifecycle. Module augmentation extended on FlowInstance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When run picks an outgoing edge from a flow-condition node — either via the
declarative resolveConditionBranch path or via a pickBranch handler that
returns an edge with a sourceHandle — write the chosen sourceHandle to
node.data._branchTaken. The directive (Task 5) reflects this via
data-flow-condition-branch-taken so the theme can highlight the chosen
branch and mute the other. resetStates() (wired in Task 1's setup) clears
the flag.

5 vitest tests covering both pick paths, the non-condition no-op, and the
resetStates clear behaviour.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When replaying an edge:taken event whose source node is a flow-condition,
mirror the edge's sourceHandle onto sourceNode.data._branchTaken so the
directive's branch decoration matches the original run. Combined with the
resetStates() wrap in workflow setup (Task 1), the flag is cleared on
re-entry. 2 vitest tests covering condition + non-condition edge sources.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure utility that turns a FlowCondition descriptor into a compact
human-readable string for the condition-node body. Mirrored on the PHP
side by FlowConditionNode::prettyPrintCondition() so the SSR fallback
matches the runtime render. 11 vitest tests covering every operator
(equals/notEquals, comparisons, in/notIn, exists, matches), nested
field paths, null values, and unknown-op fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…h-taken decoration

Renders the condition node: header (defaults to 'Condition'),
pretty-printed expression body (or evaluateLabel / '[custom evaluator]'
for evaluate-fn nodes), target handle, and labelled true/false source
handles. Direction defaults to horizontal, switchable via the directive
expression or node.data.direction. Reflects node.data._branchTaken via
data-flow-condition-branch-taken so themes can highlight the chosen
branch and mute the other.

textContent only — no innerHTML anywhere. 12 vitest tests covering
host-class stamping, direction resolution, body rendering for both
condition and evaluate paths, three handles with sourceHandle metadata,
and branch-taken attribute reflection. Registered in src/index.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ation

Structural CSS positions the target handle and the two source handles
(true/false) for both horizontal (default) and vertical directions —
target on the leading edge; true/false on the trailing edge at 35/75%
horizontally, 30/70% vertically. Theme defaults reuse existing tokens
(--flow-node-bg, --flow-border-subtle, --flow-text-body/-muted,
--flow-surface-alt, --flow-edge-stroke, --flow-edge-taken-stroke) — no
new CSS variables. Branch-taken decoration via attribute selectors:
chosen handle gets a soft glow, the other dims to 0.4 opacity. False
handle uses literal #ef4444 to match the existing failed-state node
border accent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Registered alongside Alpine.magic('workflowRun') so the factory ships
with the workflow addon (animate-extraction safety) without
re-registering on every canvas mount. Auto-binds to canvas.lastReplayHandle
or lazy-builds replayExecution from canvas.executionLog. Capability
detection: scrubber when handle.scrubTo exists, progress bar otherwise;
time readout via formatTime(ms) → m:ss. Polling on play/restart for
non-reactive handles, cleared on pause/stop. Scrubber drag wires
pointerdown/move/up via _applyScrub with bounded ratio.

Test stubs in run.test.ts and validate.test.ts updated to provide
data/ alongside magic.

Structural CSS for .flow-replay-controls + buttons + progress + scrubber
+ time + speed dropdown. Theme defaults reuse existing tokens (--flow-
node-bg, --flow-border-subtle, --flow-text-body/-muted, --flow-edge-stroke,
--flow-edge-taken-stroke). No new CSS variables.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the flowExecutionLog factory alongside flowReplayControls in the
workflow plugin entry. Resolves the canvas via the same resolveCanvas()
helper, reads either canvas.executionLog (default) or a named expression
from sourceExpr. Filter modes: 'all' / 'errors' / 'lifecycle'. Caps
visible events at maxEvents (FIFO display window — separate from the
addon's own logLimit storage cap). Auto-scrolls to the tail while events
arrive; the auto-scroll flag is reset by onUserScroll() based on whether
the body is at the bottom.

Click handler dispatches a flow:highlight-node CustomEvent with
{ detail: { nodeId } } and bubbles — consumers wire focus or animation
externally. iconFor / iconClassFor map event types to single characters
+ palette tokens.

Structural CSS for .flow-execution-log + header + body + row layout
grid + empty state. Theme defaults reuse existing tokens (--flow-node-bg,
--flow-surface-alt, --flow-border-subtle, --flow-text-body/-muted,
--flow-edge-entering-stroke, --flow-edge-taken-stroke). Error icon uses
literal #ef4444. No new CSS variables.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s + theming

Three Alpine.data factories, registered alongside the other workflow UI
factories so they ship with the addon. flowRunButton reads handlers
from the canvas DOM element via config.handlersKey (default
'runHandlers'); auto-disables while canvas.runState is running|paused.
flowStopButton hides itself when idle (unless config.alwaysVisible) and
calls canvas.stopRun(). flowResetButton calls canvas.resetStates() then
canvas.resetExecutionLog() so a fresh run can start from a clean slate.

Structural CSS sizes the trio uniformly (h32, rounded). Theme defaults
reuse existing tokens — primary uses --flow-edge-taken-stroke for the
run accent; stop/reset use --flow-border-subtle outlines with subtle
hover lift. No new CSS variables.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…factories

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Alpine injects $el, $watch, $nextTick, $refs onto the data scope at
runtime but they are not present on the factory return type, so strict
tsc fails the build. Casting via (this as any).$X (or a self alias)
silences the strict check without affecting runtime behaviour. Same
pattern used elsewhere in the codebase for Alpine magics on data
factories.

Bundled with the dist rebuild — workflow factories (flowReplayControls,
flowExecutionLog, flowRunButton, flowStopButton, flowResetButton) land in
alpineflow-workflow.esm.js and x-flow-condition + x-flow-wait register in
the core bundle. CSS classes emit in alpineflow.css and theme defaults in
alpineflow-theme.css. No new CSS variables.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…primitives

feat(workflow): UI primitives — condition directive + canvas runState + factories
Five Alpine.data factories (flowRunButton, flowStopButton, flowResetButton,
flowReplayControls, flowExecutionLog) cached `this._canvas` at init() time
via Alpine.$data(.flow-container). When a button is rendered as a
sibling/ancestor of the canvas in DOM order, button-init runs before the
canvas's flowCanvas init's _initAddons(). The cached _canvas was a stale
empty proxy missing run/stopRun/resetStates/runState/executionLog, so
click-time method calls threw TypeError.

Add an internal `ensureCanvas(scope, requiredMethod)` helper that lazy
re-resolves scope._canvas from document.querySelector('.flow-container')
if the cached reference doesn't have the required method. Called at the
top of every method that touches canvas internals. Idempotent — no-ops
when _canvas is fresh. Also rewrite flowExecutionLog.filteredEvents to
read source from the canvas at getter time rather than from a cached
_source array.

Adds 10 new vitest tests covering button-before-canvas DOM order across
all five factories. Pre-existing 2568 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tion

fix(workflow): lazy re-resolve canvas in factory methods
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.

2 participants