Headline:
- Group colour cascades to every non-overridden child (layer or clip
track). New `colorOverridden` flag on Layer / ClipTrack / ScriptTrack
/ TrackGroup; setTrackColor on a non-group pins the track, clearing
re-inherits from the parent group. moveTrackIntoGroup also inherits.
normalizeScene migrates legacy scenes (existing colour ⇒ overridden).
- Keyframes inside a button-asset timeline snap to the 4 state slots
(normal/hover/pressed/disabled). upsertKeyframe / setKeyframeTime /
duplicateKeyframe all enforce it; colliding drags refuse the move,
duplicates walk to the nearest free slot.
- Backspace / Delete on a selected layer removes it (respecting the
layer's own lock and any enclosing group lock).
- "Still frame" allowed on the last keyframe: the layer stays on stage
past its final kf. Timeline draws a solid dim tail line from the
last kf to the end, and single-keyframe tracks now show a full-span
line so on-stage/off-stage reads at a glance. Height stays 3px.
- Project name moved next to the logo, turns into a rename button on
hover (pencil fades in). Rename syncs the stored record when the
project is already persisted.
- Shadow + blur keyframes animate in the preview and exported runtime:
the runtime's lerpProps used to drop them, so every preview rendered
shadow=0 between keyframes. RUNTIME_VERSION 11 → 13 (covers the
button-mask + shadow fixes). Held-last-kf also taught to the runtime.
- Button masks render across every path: edit-mode mask-preview,
nested buttons on the main stage, sidebar thumbnails, fullscreen
preview + exports. drawButtonFrame new in render.ts.
Smaller fixes:
- Dragging layer A on the stage with a stale selection on layer B used
to move B and snap A back. Now the shift-selected branch only runs
when the dragged layer is itself in the selection.
- Clip-track row was missing its icon — PhFilmStrip was referenced but
never imported. Added, plus dropped the bespoke `gap` on clip / script
name rows so their icon+label spacing matches layer rows.
- Clip-art drawer pre-lays-out its 400px grid behind `overflow: hidden`
so slide-in reveals a finished grid instead of reflowing auto-fill
columns over 220ms.
- Timeline clip-track dropzone hides until the library has a clip; its
"drop a clip here" hint stays centred in the visible ruler area.
- Keyframe context menu: check-mark and icons share a single 12px column
so the ✓ on an easing option lines up with PhPauseCircle / PhCopy /
PhTrash on the rows below. "Still frame"'s active indicator moved to
the trailing-check slot.
- Button editor's mask-preview now shows the button's own stage instead
of leaking through to the main scene. drawButtonFrame drives it.
Regression tests cover all of the above: group-color cascade, pin,
clear-re-inherit, migration, button-kf snap and collision rules,
backspace-deletes-selected-layer (incl. lock guards + editable-target
guard), still-frame-past-end visibility, clipart drawer rigid width,
mask rendering in drawButtonFrame / drawSceneFrame, shadow + blur
interpolation in both canvas and runtime paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Button assets
- Re-introduce button-asset type with 4 content states (normal/hover/
pressed/disabled) stored as keyframes at BUTTON_STATE_TIMES, plus a
separate hit-area editor mode.
- useButtonRuntime composable for per-instance state/disabled flags on
the main stage.
- Sidebar library lists button assets with live 80×80 thumbnails;
renderButtonThumbnail now accepts an images map so image-kind layers
inside a button render instead of producing a fully-transparent PNG.
- Compact (no-thumbnail) mode aligns button rows to the library-row
height.
Scope footer
- Button-edit mode now uses the same sidebar scope-footer pattern as
clip-edit (back button to "Main timeline" + active scope chip);
removed the inline exit button from the timeline scrub-bar.
Timeline
- Track-name column default bumped to 260px to match the assets sidebar
above it (storage key bumped to :v2 so stale values reset).
- scrub-bar z-index raised above sticky track-name columns so rows
scroll under it instead of over.
- scrub-handle flips z-index based on horizontal scroll: above
name-spacer when the playhead is in view (no half-clip at t=0),
below when the playhead has scrolled off the left edge (so the
toggle row cleanly covers it).
- name-spacer extends ±3/1px to fully cover the obscured handle.
- scrub-handle head pokes 3px above the ruler with rounded top corners
via clip-path path().
Track context menu
- Added missing i18n keys (renameScriptTrack / deleteScriptTrack /
renameScriptTrackDialog.{title,confirm}).
- Rename items show a visible pencil icon; empty .check slot removed
so items line up flush-left; toggle items place their active ✓ on
the right (kf-menu-trailing-check).
Dev HUD
- Test progress bar flips to --danger as soon as the first failure is
recorded, instead of only at the end of the run.
Regression tests
- renderButtonThumbnail: null on empty button, renders shape layers,
accepts custom images map.
- i18n: renameScriptTrack / deleteScriptTrack / dialog keys resolve.
- scrub-handle-obscured logic (visible at t=0, hidden when playhead is
scrolled off-screen, still visible when playhead is far to the
right of the scroll offset).
- Updated button state-scrubber expectations to match
BUTTON_STATE_TIMES (normal=1, hover=2, pressed=3, disabled=4) and
that hit-mode parks at normal=1 rather than 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- background video layer (document-level), syncs to playhead in editor and fullscreen
- playback in/out brackets on the scrub ruler; i/o set them, r rewinds to in-point
- togglePlay, tick, fullscreen loop, WebM + server FFmpeg export all honor [in, out]
- top-level mask layers cascade over everything below until a group boundary
- fullscreen preview slaves localTime to the bg video so it stops stuttering
- welcome "new project" now actually loads a blank scene (was a no-op)
- default canvas background hardened to white via normalizeScene
- new keyframe button (+) in the name-spacer, backspace/delete removes selected kfs
- drag-end auto-selects the created/shifted keyframe so the path is visible
- animation path updates live during drag and spans every selected keyframe
- track rows show an icon per asset kind (rect/circle/image/text/polygon/freeform)
- default mask mode is negative, buttons read − then +
- sticky track-name columns raised above playhead/brackets; orange bracket styling
- tests for playback range, blank scene, mask defaults, cascading top-level masks
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Clipboard paste creates an image layer: a document-level paste
listener detects image items in the clipboard (Strg+V after a
screenshot, e.g.) and imports them via the shared helper. Skipped
when focus is in a real text input / textarea / contenteditable.
- Shared importImageFile() helper in fileLoader.ts replaces the
duplicated image-import logic in AssetSidebar and CanvasStage's
clipart-drop path. Optional position argument for drop-at-point.
- Clipart folder persistence hardened for reloads:
- initialize() is now safely re-runnable (no-ops only once a handle
is actually loaded, tolerates refresh failures so the folder name
survives even if permission isn't auto-granted)
- App.vue's onMounted kicks off initialize() so it runs regardless
of whether the drawer component has mounted
- DocumentSettings re-invokes initialize() each time the modal
opens — covers app-start races and gives the user gesture needed
for FS Access permission prompts
- Opening the drawer refreshes thumbnails when handle is set but
items are empty, so the user-gesture context restores permission
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Rename the app to RUWIO: title, APP_NAME, topbar logo (40px PNG),
welcome dialog header and about dialog switch from the PhSparkle
glyph to the real logo. Redundant name text next to the logo is
removed across the board.
- About dialog: logo spans the full dialog width, the bottom Close
button is gone (close via header X stays).
- Vertex bezier for polygons: each vertex can be Corner / Curve / Split
curve via right-click. Curve mirrors tangent handles symmetrically,
split curve keeps them independent. Tangents are fully animatable
per keyframe — lerp mirrored across all four render pipelines.
- Stage path overlay: selecting a keyframe draws the tween arc between
the previous and selected keyframe with draggable per-frame dots that
curve the path via quadratic bezier control points. Other segments
on the same track render as dashed ghost lines so the full curve is
visible. Path dots grow + show grab cursor on hover.
- Vertex box-select: click-drag on empty stage while a polygon is
selected boxes vertices. Shift-click toggles individual vertices
into/out of the selection. Multi-vertex drag moves all selected at
once; right-click on a selected vertex applies Corner/Curve/Split
to all. Tangent handles only render when exactly one vertex is
selected.
- Welcome dialog focus: opens with the most recent project focused.
Enter opens it; Tab cycles only through the dialog's real buttons
(delete icons pulled out of tab order, scrollable body removed from
tab order). Export-current removed from welcome (not useful on
startup).
- Modal focus trap: all modals now keep Tab inside the dialog.
- Fill / stroke transparency toggle restores the last real color when
flipped back on, with a layer-base / asset fallback chain so there's
always a visible color to restore to. A watcher keeps the restore
cache in sync with the sampled value.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Clipart library: new left-edge drawer (5px idle, expands on hover,
400px when open). User picks a local folder via the File System Access
API in settings; directory handle is persisted in IndexedDB so the
library survives reloads. Dragging a thumbnail onto the stage creates
an image asset + layer at the drop point.
- Animation paths on the stage: selecting a keyframe draws the tween
path from the previous keyframe to the selected one as a solid
polyline with per-frame draggable dots. All other segments on the
same position-track render as dashed ghost lines so the full curve
progression is visible.
- Path dots grow on hover (2.5 → 6 px), show a grab cursor, and have a
wider hit area so they're easier to grab.
- Bezier-curved position interpolation: keyframes gain optional
ctrlX/ctrlY fields. When set, x/y tween along a quadratic bezier
through the control point instead of a straight line. Dragging a
path dot solves for the control so the curve passes through the
drag point at its frame progress. Mirrored across all four render
pipelines.
- Tests: new suites for polygon actions, freeform drawing lifecycle,
polygon + stroke rendering (15 new tests). Fixed the mask-render
tests that relied on a default-transparent canvas background.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>