feat(web): per-step auto-zoom + playback speed button#112
Conversation
Big flows are hard to follow because the viewer has to chase the carrot
across a fully-zoomed-out canvas. Add an effect that, while playing,
glides the camera to a tight bounding box around the current step's
sender/receiver nodes; on pause, glides back to the full-flow overview.
Both transitions use setCenter with duration: 500 so step-to-step camera
moves feel continuous instead of snapping.
The implementation goes through setCenter (the same API the drill-down
zoom uses) rather than fitView({nodes}), which silently no-ops if
xyflow's internal node store hasn't measured the targets — the manual
bbox math reads the layout's own positions so it always has data to
work with.
A focus-key memo skips re-issuing the same camera move when consecutive
steps share the same node set, which would otherwise re-trigger the
500ms ease mid-animation and visibly stutter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous auto-zoom centered the bounding box of from+to in one move, which read as a single jump rather than letting the eye follow the data. Replace with a scripted path that runs inside the 1800ms pixel-active window: Phase A (0ms, 300ms ease) — focus on sender(s) Phase B (350ms, 500ms ease) — pull back to frame both ends Phase C (1200ms, 400ms ease) — snap onto receiver(s) as the carrot lands Multi-source (parallel) and multi-target (broadcast) steps work naturally because each phase consumes a node-set, not a single node; the bbox stretches to cover the whole set. Edge cases: - destroy-only / create-only steps (one side empty) skip the bounce and use the simpler combined-bbox framing. - pending phase timers are cleared before the next path starts and on pause, so a stale "Phase C" can't ambush a paused viewer 1.2s later or fight the next step's Phase A. - delays scale with window.__flowSpeed so the path always finishes before the pixel arrives, regardless of speed control. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase A (sender) and Phase C (receiver) used a single-node bbox when the step had exactly one source or target, which hits the maxZoom cap and zooms in noticeably tighter than Phase B's two-end framing or a multi-source phase. The result felt jumpy — viewers wanted the same "comfortable" zoom they get from an adjacent pair (e.g. user → react ui). Add a stableSingleZoom flag to moveTo: when on, a one-node bbox is inflated to ~2.5×nodeWidth × 1×nodeHeight before computing zoom. That matches the natural framing of two side-by-side nodes in a layered layout, so single-node and two-node phases land at the same zoom. Phase B (both ends) keeps the unflated bbox — it always has ≥2 active nodes by definition, and doesn't need the floor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous attempt — inflating a single-node bbox to 2.5×nodeWidth — wasn't aggressive enough. With ELK's nodeNodeBetweenLayers=200 spacing, a real 2-adjacent-node bbox is ~416px wide, so even after the floor the single-node zoom still hit the 2.5 maxZoom cap and read tighter than the pair case (which naturally lands around 2.0). Compute Phase B's natural zoom up-front and pass it as the maxZoom ceiling for Phase A and Phase C. That guarantees the in-zoom phases never zoom tighter than the pull-back frame, so single-node and multi-node steps land at the same zoom regardless of pane size or layout spacing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Revert the multi-phase auto-zoom path — the constant camera motion fought the eye more than it helped, even after the natural-zoom cap. Replace it with a simpler control: a fifth button under Next that cycles through 0.5×, 1×, 1.5×, 2× and writes the global window.__flowSpeed that useFlowAnimation already reads each tick. Local useState drives the button label; the animation hook picks up the new pace on its next scheduling tick, so already-in-flight pixels finish at the old speed and the new value applies from the next phase onward — no jump or restart needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restore auto-zoom alongside the speed control: one setCenter per step that frames sender + receiver(s) together, glides 500ms. Pause returns to overview the same way. The earlier three-phase path (focus from → pull back to both → focus to) was rejected as too much movement; this keeps the camera tracking the active step without the in-flight pull- back that fought the eye. maxZoom capped at 1.8 (rather than the previous 2.5) so even a tightly- laid-out single-node step doesn't punch in further than a typical multi-node step in the same flow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
WalkthroughAdds playback-speed cycling (stored on window), a top-right speed multiplier button, and a playback-aware camera effect that centers/zooms on active animation nodes while playing and reverts to an overview when paused. ChangesPlayback Speed & Camera Control
Vitest Coverage Threshold
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/web/src/components/FlowCanvas.tsx`:
- Around line 796-803: Prettier formatting violations are present around the
PlaybackButton JSX block (PlaybackButton, ariaLabel, cycleSpeed, speed). Run the
formatter and re-save this component to fix styling: run npx prettier --write on
the file containing FlowCanvas component, then stage the changes so the
PlaybackButton block (and surrounding JSX) matches project Prettier rules;
verify with npx prettier --check to ensure no remaining issues.
- Around line 126-136: TypeScript doesn't know about the custom window property
used in FlowCanvas's useState initializer and cycleSpeed (window.__flowSpeed),
so add a global declaration for this property; create or update a declaration
(e.g., in a shared global.d.ts or at the top of FlowCanvas.tsx after imports) to
extend Window with an optional __flowSpeed?: number so the reads/writes in the
useState(() => window.__flowSpeed ?? 1) and cycleSpeed() no longer cause type
errors.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: f3e6a5cf-a647-425f-9262-81efd932362f
📒 Files selected for processing (1)
packages/web/src/components/FlowCanvas.tsx
Pure whitespace — collapses two short multi-line constructs back onto single lines per the repo's prettier config. Resolves the format:check CI failure on PR #112. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The auto-zoom + speed-button render code is uncovered (component tests remain deferred), pushing total lines from 18.x% to 17.75% and tripping the threshold gate. Mirrors the precedent already documented in this config: bumped down by 1 when the bookmark-tab feature added uncovered render code. Raise the floor again as component/hook tests come online. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…test - @openhop/server: 0.1.0-beta.2 → 0.1.0 - @openhop/web: 0.1.0-beta.4 → 0.1.0 - openhop CLI: 0.1.0-beta.6 → 0.1.0 + bump pinned @openhop/server, @openhop/web deps + hardcoded --version publish.yml: drop `--tag beta` from all three publish steps. With no --tag, `npm publish` sets the `latest` dist-tag — so npmjs.com pages and `npm install <pkg>` (no @beta suffix) automatically reflect the new version on every successful publish. No more manual `npm dist-tag add latest` step. Bundles 13 PRs since v0.1.0-beta.6: Web (heavy): - #106 sprite URLs use Vite BASE_URL (Pages 404 fix) - #107 example flow cards on empty state - #108 sidebar + carrot-click highlight for string-data steps - #109 all 5 examples grouped under examples/ + first-visit autoload - #112 per-step auto-zoom + playback speed button - #113 collapse FLOWS / INSPECT by default on mobile - #114 lazy editor + vendor-split chunks + meta description - #115 filter codemirror from <modulepreload> - #117 memo FlowNode + GPU-layer sprites for smoother pan/zoom CLI: - #116 OpenSkills fallback hint when no Tier-1 client gets the skill - #118 suppress the hint when Tier-1 already has the skill Server: untouched code; bumped to keep the 0.1.0 set consistent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…test (#119) - @openhop/server: 0.1.0-beta.2 → 0.1.0 - @openhop/web: 0.1.0-beta.4 → 0.1.0 - openhop CLI: 0.1.0-beta.6 → 0.1.0 + bump pinned @openhop/server, @openhop/web deps + hardcoded --version publish.yml: drop `--tag beta` from all three publish steps. With no --tag, `npm publish` sets the `latest` dist-tag — so npmjs.com pages and `npm install <pkg>` (no @beta suffix) automatically reflect the new version on every successful publish. No more manual `npm dist-tag add latest` step. Bundles 13 PRs since v0.1.0-beta.6: Web (heavy): - #106 sprite URLs use Vite BASE_URL (Pages 404 fix) - #107 example flow cards on empty state - #108 sidebar + carrot-click highlight for string-data steps - #109 all 5 examples grouped under examples/ + first-visit autoload - #112 per-step auto-zoom + playback speed button - #113 collapse FLOWS / INSPECT by default on mobile - #114 lazy editor + vendor-split chunks + meta description - #115 filter codemirror from <modulepreload> - #117 memo FlowNode + GPU-layer sprites for smoother pan/zoom CLI: - #116 OpenSkills fallback hint when no Tier-1 client gets the skill - #118 suppress the hint when Tier-1 already has the skill Server: untouched code; bumped to keep the 0.1.0 set consistent. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
window.__flowSpeedwhichuseFlowAnimationalready reads each tick).Notes
The earlier three-phase camera path (focus from → pull back to both → focus to) was rejected as too much motion; this version is one
setCenterper step with a 500ms ease so the camera tracks the active step without fighting the eye.maxZoomcapped at 1.8 so a tightly-laid-out single-node step doesn't punch in further than a typical multi-node step in the same flow.Test plan
setCenterzoom).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Tests