- Clipart store holds an array of root folders instead of a single
handle, plus an activeHandleIndex for the in-folder view. Getters:
activeHandle, inOverview, hasAnyFolder, canGoBack, folderName.
- Actions: addFolder (appends a root), enterFolder, exitFolder,
removeFolder. Single-folder installs auto-drill into the sole root
and behave exactly like the pre-refactor drawer; adding a 2nd+
folder pops back to the root overview.
- IDB persistence migrated from the 'clipartDir' single-handle key to
a 'clipartDirs' array. Legacy records are migrated on first load.
saveClipartFolders unwraps Pinia proxies (toRaw per element) so
IDB's structured clone accepts the payload.
- ClipartItem union gained a 'folder' kind so root tiles render as
first-class media items. listClipart emits nested directories too,
but the drawer keeps drill-down one level deep for now.
- ClipartDrawer header: three equally-sized .header-btn chips —
back (only when canGoBack), refresh (when viewing a folder), and a
new `+` (when any folder is attached). No more big 'Choose folder'
button — empty state is a centred 96px dashed + with a wrapped hint.
- Folder tiles carry a hover `×` to remove a root from the library.
- DocumentSettings media tab lists all configured folders with a per-
folder remove action; primary button is now 'Add folder'.
- Pinia store now calls acceptHMRUpdate so Vite HMR actually swaps
the store definition between reloads instead of leaving the first-
load state shape behind.
- Tests: store actions (addFolder / enterFolder / exitFolder / remove
/ IDB round-trip), drawer UI (centred + empty state, overview with
folder tiles + click-to-enter, back visibility, single-root hides
back, header + button triggers addFolder). Tests isolated with
try/finally so a failing store assertion can't leak error state
into subsequent drawer tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Document drawer slides out from under the topbar (no tab, no hover
peek); opens via project-name button, closes on outside-click or ESC.
Layout: inputs row + divider + preset row, presets user-managed via
popup and persisted in localStorage.
- PWA manifest + minimal service worker so Chrome installs RUWIO as a
standalone app — suppresses the native fullscreen exit overlay.
- Clips and audio clips get right-click context menus (Duplicate /
Delete) via new `duplicateClipInstance` / `duplicateAudioClip` store
actions.
- Clip moves cascade non-overlap in both directions; `setAudioClipPosition`
now pushes backward as well as forward. New `setClipInstancePosition`
gives clip instances the same behaviour.
- Library drags carry timeline-shaped ghost previews (audio pill, clip
pill, button thumb) instead of the library row; clip lane renders a
dashed drop-target ghost and rejects window-overlap drops.
- Scene / Clipart drawer tabs disappear in fullscreen; the 24 px they
occupied is reclaimed for the stage. Play button sits on the exact
horizontal center via a 3-column grid in the transport cluster.
- Tests: context menu + push-cascade + duplicate action coverage for
both clip kinds; DocumentDrawer suite rewritten for the slide-out
model.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tooltips
- New v-tooltip directive + TooltipHost floating panel replace the
native title attribute across 95+ sites (topbar, timeline, sidebar,
properties, stage overlays, fullscreen preview, export dialog,
clipart drawer, modals). Styled dark card with a configurable
label + optional help line for inline documentation; screen
readers still get an aria-label when the panel is disabled.
- Settings → Interface toggle (`ruwio:tooltipsEnabled`) hides the
visual panel for users who know the app. IconButton now routes
its title prop through v-tooltip so every existing call site is
tooltip-aware without churn.
Editor-hide + export filter
- New `Layer.editorHidden` flag, eye toggle per layer row, skipped
by the Konva stage and the mask-preview canvas path but honoured
by exports and fullscreen preview (runtime untouched).
- Export dialog gets a "Skip hidden tracks" switch that strips
editor-hidden layers from every format (scene JS, runtime,
example HTML, MP4, WebM) on opt-in. Disabled when the scene has
no hidden layers.
Button editor
- `+` buttons appear on empty state slots in button-timeline rows
and duplicate the nearest keyframe (prev by preference, next
otherwise) onto that slot. New `duplicateKeyframeAt` store
action carries props, easing, hold, and bezier handles across.
Stage / interaction
- Plain left-click on the empty content view pans the viewport,
but only when the pointer isn't over a Konva shape — transformer
handles, vertex dots, and selection boxes that extend past the
canvas edge keep their drag.
Settings reshuffle
- Timeline tab merged into Document (Duration + FPS sit under a
new "Timing" sub-heading). New Interface tab gathers three
global prefs: Show tooltips, Show locked tracks, Asset
thumbnails — driven by shared composables so the existing
toolbar toggles stay in sync. Interface-tab CSS defensively
resets the scoped label flex-column + uppercase rule that was
making the toggle row look like an accident.
Project identity + UX polish
- Favicon wired up (moved to public/favicon.ico, linked from
index.html); scripts/pack.sh packs the minimum Vue project
(src, index.html, configs, public) into a tarball with the
right version in the filename, and `npm run pack` drives it.
- About dialog now credits Torsten Hoeltge.
Test infra
- Progress bar turns red mid-run on the first failure: runAll
gets an onResult callback that TestOverlay uses to push each
result into the reactive array incrementally.
- mountComponent in the test harness registers v-tooltip on the
isolated app instance so IconButton's aria-label contract
actually fires under test.
Regression tests
- duplicateKeyframeAt: copy fidelity (props, easing, hold),
collision refusal, unknown-id guards, separated-track targeting.
- Shared composables: useTooltipPref and useAssetThumbsPref both
return the same ref across calls and persist to localStorage.
- Tooltip contract: IconButton ships `aria-label` (not title);
live-DOM smoke tests match on aria-label selectors now.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
- 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>