Commit Graph
100 Commits
Author SHA1 Message Date
BFlat 80c246d843 Merge pull request #92 from storytold/m3-delete-confirm
M3.16: deleting Project items in use asks first (After Effects parity)
2026-10-06 11:36:27 +07:00
bflatasticandClaude Opus 5.5 9624024dc1 M3.16: deleting Project items that compositions use asks first
As in After Effects, the Delete key and the Project panel's trash button ask before deleting
items that layers in other compositions use, saying how many layers in how many compositions go
with them (Delete / Cancel; Escape cancels). Unused items are deleted at once; agents running
project.delete directly are not asked. project.usage {items?} reports the count, sharing the
deletion's item and usage helpers. Follow-up to #72.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 11:35:57 +07:00
BFlat b46be83ab4 Merge pull request #84 from fortesnow/jp-font-fallback
text: script-aware system font fallback; accept PostScript / full font names
2026-10-06 10:57:36 +07:00
BFlat 86ae2fa218 Merge pull request #72 from jonassneu/fix/project-delete-shortcuts
Project: delete selected items with Delete and Backspace
2026-10-06 10:56:43 +07:00
bflatastic f0578a5940 Merge branch 'main' into pr72-land
# Conflicts:
#	docs/gaps.md
2026-10-06 10:55:43 +07:00
BFlat 42401e7672 Merge pull request #70 from jonassneu/fix/viewer-visible-layer-selection
Viewer: respect solo and visibility in layer selection
2026-10-06 10:54:56 +07:00
BFlat 877afeb25a Merge pull request #83 from storytold/pr/release-gate
Release gate: ui_agent_audit builds in release mode
2026-10-06 08:37:17 +07:00
bflatasticandClaude Opus 5.5 f25b75486b Release gate: the agent-audit test builds in release mode
`cargo xtask ci` runs clippy and the tests with --release, where egui has no Style::debug (it
exists only with debug_assertions), so ui_agent_audit.rs failed to compile since M13.14. The
debug option is set only in debug builds, and the test, which needs the widget types egui records
with it, is ignored in release builds.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 08:37:01 +07:00
BFlat 857340036b Merge pull request #82 from storytold/docs/issue-status
Docs: user issues #41–#47 and #63–#68 in gaps.md
2026-10-06 00:39:08 +07:00
bflatasticandClaude Opus 5.5 25f353fb2c Docs: gaps.md records the user issues of 5 October and what became of them
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 00:38:43 +07:00
BFlat dd56ae5c00 Merge pull request #81 from storytold/fix/44-project-import
Fix #44: double-click the Project panel's empty area to import
2026-10-06 00:37:36 +07:00
bflatasticandClaude Opus 5.5 df12664b8e Fix #44: double-click the Project panel's empty area to import
As in After Effects, a double-click below the items in the Project panel opens File ▸ Import ▸
File... (rows and the scroll bar keep their own clicks). Dropping files on the window already
imported them; the test now covers both. (On Wayland, winit delivers no file drops at all, so
dragging files in works on X11, macOS and Windows.)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 00:37:05 +07:00
BFlat fdfbca5faf Merge pull request #80 from storytold/fix/43-wayland-icon
Fix #43: the AppImage's window shows the EffectCraft icon on Wayland
2026-10-06 00:31:29 +07:00
bflatasticandClaude Opus 5.5 b424c321dc Fix #43: the AppImage's window shows the EffectCraft icon on Wayland
Wayland compositors take a window's icon from the desktop entry named by its app ID; the .deb,
.rpm and Flatpak install it, an AppImage installs nothing, so its window got the generic icon.
Started from an AppImage ($APPIMAGE), the app now writes a hidden (NoDisplay) desktop entry
pointing at it, and its 256 px icon, under $XDG_DATA_HOME (~/.local/share) before the window
opens. Nothing is written when an installed package or the user's own entry provides the ID;
the entry follows the AppImage when it moves; failures are only logged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 00:31:06 +07:00
BFlat 72e60dc6fa Merge pull request #79 from storytold/fix/45-project-details
Fix #45: the Project panel's details stay inside the panel
2026-10-06 00:26:17 +07:00
bflatasticandClaude Opus 5.5 e6d4a64775 Fix #45: the Project panel's details stay inside the panel
The details header (item name, size, duration, codec) and the "Select an item" hint were painted
with no width limit, so in a narrow Project panel they ran past its right edge. They now stop
10 px short of it and end in "…" when they don't fit (widgets::text_fit, a shared helper), and
are registered for automation (project.details.*).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 00:25:57 +07:00
BFlat 83cd5d0f45 Merge pull request #78 from storytold/fix/46-docking
Test #46: docking by the panel menu button and resizing by the gutters
2026-10-06 00:18:37 +07:00
bflatasticandClaude Opus 5.5 efcbc2e412 Test #46: panels dock by a drag from the panel menu button; gutters resize
Covers what issue #46 reports (v0.1.1 on Linux): a panel dragged by its hamburger onto another
group's edge docks there as its own group, and dragging the gap between panels resizes them.
Both work on main (tab dragging was reworked in M3.15 for v0.2.0); this keeps them working.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 00:18:11 +07:00
BFlat bb7b5f51de Merge pull request #77 from storytold/fix/65-drag-refresh
Fix #65: the viewer keeps up while a layer is dragged
2026-10-06 00:16:25 +07:00
bflatasticandClaude Opus 5.5 1b8780f6da Fix #65: the viewer keeps up while a layer is dragged
While paused, the app prefetched the frames around the current one as soon as the viewer's
frame finished. During a drag every step changes the project, so those frames were stale at the
next step, and they held the CPU and GPU the next viewer frame needed: on the demo project a
drag step took 102 ms on average to show (up to 413 ms) though a frame renders in 20 ms. Paused
prefetch now waits until edits pause for 0.3 s: 16 ms a step (11 ms render).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 00:16:04 +07:00
BFlat 75640044b8 Merge pull request #76 from storytold/fix/64-double-axes
Fix #64: one 3D compass in the viewer
2026-10-05 23:49:03 +07:00
bflatasticandClaude Opus 5.5 77c056a8db Fix #64: one 3D compass in the viewer
Two implementations of Show 3D Reference Axes had landed (M13.5 and M14.5) and both drew,
offset by a few pixels: lines, and arrows. Keep the arrowed one (perspective-correct, shown
while a 3D layer is active) and test that only one is drawn.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 23:48:41 +07:00
BFlat 0dcbe325ab Merge pull request #74 from storytold/fix/63-viewer-scale
Fix #63: scaling a layer by its handles follows the pointer
2026-10-05 23:44:30 +07:00
bflatasticandClaude Opus 5.5 782a67f228 Fix #63: scaling a layer by its handles follows the pointer
The handle drag rescaled incrementally from the current scale every frame: errors built up,
edge handles scaled both axes, and once a factor reached ~0 the drag could never bring it
back. It also tested the handle at the point where egui recognised the drag (past the drag
threshold), so a quick grab often missed the handle and moved the layer instead.

Now, as in After Effects, the drag scales about the anchor point relative to the layer as the
drag began, so the grabbed point stays under the pointer: corners scale both axes, edges one,
Shift keeps the proportions, and dragging past the anchor flips. Selection-tool gestures start
at the press point.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 23:43:50 +07:00
BFlat 4373c24962 Merge pull request #73 from storytold/fix/68-open-links
Fix #68: Discord buttons and other links open the browser again
2026-10-05 23:27:45 +07:00
bflatasticandClaude Opus 5.5 0286a49f0a Fix #68: open links in the system browser (Discord buttons, Help menu, Settings)
The desktop app built eframe without its `links` feature, so egui's open_url was a no-op
on native: the header and Home Discord buttons, Help menu links and Settings links did nothing.
Enabling it links the `webbrowser` crate (MIT OR Apache-2.0), which opens URLs with the
system browser on macOS, Windows and Linux.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 23:26:29 +07:00
BFlat 24fcbdf3d2 Merge pull request #62 from storytold/pr/release-0.3.0
Release: EffectCraft v0.3.0
2026-10-05 20:42:35 +07:00
bflatasticandClaude Opus 5.5 044fc32a9c Release: EffectCraft v0.3.0
Bump the workspace version 0.2.0 -> 0.3.0 (cargo xtask version set 0.3.0).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 20:42:11 +07:00
BFlat b9ffd889bc Merge pull request #61 from storytold/m13-face-ml
M13.36: face tracking trained model (MediaPipe Face Landmarker, pure Rust)
2026-10-05 20:40:31 +07:00
bflatasticandClaude Opus 5.5 e43bf66775 M13.36: face tracking trained model — MediaPipe Face Landmarker on a pure-Rust TFLite interpreter
- effectcraft-segment: a FaceModel interface (find a face in a region, follow it; a Topology
  maps the model's points to the tracker's landmarks and outline), a TensorFlow Lite reader and
  interpreter (float ops, float16 weights folded; matches ai-edge-litert to ~1e-6), and
  MediaPipe Face Landmarker (Google, Apache-2.0: BlazeFace + Face Mesh V2, 3.8 MB, pinned
  SHA-256). The registry gains a task and authors per model; installed weights get a NOTICE.
- effectcraft-track: FaceTracker::new_with uses the model, falls back to the classical fitter
  when it finds no face; chin and jaw come from the outline either way so measurements agree.
- Engine: roto_models becomes models (both tasks); face.models / face.model.* commands,
  Settings ▸ Face Tracking, `wait` on select/install/download for scripts; track.mask reports
  faceModel.
- Credits: ATTRIBUTION.md lists the optional models and the Apache-2.0 specs the code follows.
- Docs: README, ROADMAP (G7), gaps, parity (TRK-3 score unchanged pending G1), agents,
  preferences, architecture, segment README.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 20:39:45 +07:00
BFlat 4624c00308 Merge pull request #58 from storytold/m13-roto-ml
M13.35: Roto Brush trained model — swappable segment module, MobileSAM in pure Rust
2026-10-05 17:57:36 +07:00
bflatasticandClaude Opus 5.5 174ab2b6af Merge main (gap assessment) into M13.35: keep parity numbers, record G7's decision and evidence
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 17:57:09 +07:00
bflatasticandClaude Opus 5.5 7579de21c6 M13.35: Roto Brush trained model — swappable segment module, MobileSAM in pure Rust
- New L1 crate effectcraft-segment: the MaskModel interface (prompts → foreground probability),
  a registry of open-licensed models (licence must be MIT/Apache/BSD/…; URL, size, SHA-256),
  a PyTorch checkpoint reader (zip + pickle subset), dependency-free SHA-256, and MobileSAM
  (TinyViT-5M + SAM prompt encoder / two-way transformer / mask decoder) on rayon and ndarray's
  safe GEMM. Loads the official mobile_sam.pt as published; checked against candle
  (embedding 0.3% RMS, 99.9% equal mask pixels); ~0.7 s/frame encoder at 32 threads, cached
  per frame, ~0.1 s decoder.
- track::roto: segment_with / propagate_with prompt a model with the strokes or the flow-warped
  matte (box, core points, mask); its probability is a strong, unblurred prior for the same
  graph cut (strokes stay hard constraints); a failing model or one disagreeing with the flow
  (IoU < 0.5) is ignored. Prior gets a weight.
- effects::roto: Version 2.0 / 3.0 use the model chosen in Settings (set_model / model_for);
  the model id is part of the chain seed.
- engine: roto_models (roto.models, roto.model.select / install / download / remove), weights in
  the models folder (host::config_dir()/models, shared by the app, CLI and MCP), background
  loading, downloads through the system curl, verified; Settings ▸ Roto Brush page (prefs
  roto.model). host::config_dir moved from the desktop app.
- Tests: segment (kernels vs direct sums, checkpoint reader, SHA-256 vectors, registry), track
  (fake model: prior shapes the cut, failing / disagreeing models ignored; real weights with
  EFFECTCRAFT_MOBILESAM: base IoU 0.989, propagated ≥ 0.980 vs classic 0.973), engine (registry,
  verification, selection; real install + load), MCP.
- Docs: README, ROADMAP, parity (MSK-4 done: 90/2/0), preferences, agents, architecture,
  crates/segment/README.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 17:55:02 +07:00
BFlat 11d4885764 Merge pull request #57 from storytold/m13-audio-scrub
M13.34: audio scrubbing (Ctrl/Cmd-drag the current time)
2026-10-05 15:19:12 +07:00
bflatasticandClaude Opus 5.5 5afc25d0d8 M13.34: audio scrubbing (Ctrl/Cmd-drag the current time)
- audio::AudioScrub: an open output that plays a faded snippet (one frame, 30–100 ms) of the
  comp's mix at each new frame, replacing what is still queued; holding still repeats nothing.
  The mixing is shared with the preview feeder (mix_block); AudioFeed::clear.
- The Timeline ruler: Ctrl/Cmd-drag scrubs audio (EffectcraftApp::scrub_audio opens the output
  through the audio device hook on first use). The Audio panel meters follow the snippets; an
  idle second closes the output.
- playback.scrubAudio {time?} for agents.
- Tests: ui_audio_scrub (snippet length, fades, same-frame no-op, replace not pile up, silent
  comps, Cmd-drag in the ruler, idle close) with a fake device. Docs: parity.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 15:18:54 +07:00
BFlat 9607be471b Merge pull request #56 from storytold/m13-nested-markers
M13.33: nested comp markers on the precomp layer bar
2026-10-05 15:12:22 +07:00
bflatasticandClaude Opus 5.5 f013bdd8d7 M13.33: nested comp markers on the precomp layer bar
- commands::markers::nested_markers: a precomp layer's comp markers at their times in the
  containing comp (start and stretch in closed form; with time remapping or a stretch over
  protected regions, the first frame whose source time reaches them), within In–Out.
- markers.nested {layer}: the same list for agents.
- Timeline: drawn on the precomp bar as outlined read-only markers (positions cached per
  revision), hover names them, double-click opens the nested comp at the marker; automation id
  timeline.layer.<id>.nestedMarker.<i>.
- Tests: engine (start, stretch, trim, time remap, non-precomp error), UI (draw, label, open).
  Docs: parity, control protocol.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 15:12:06 +07:00
BFlat 1ac55c33a3 Merge pull request #55 from storytold/m13-new-viewer
M13.32: View ▸ New Viewer — several Composition viewers
2026-10-05 15:00:30 +07:00
bflatasticandClaude Opus 5.5 97390f15fb M13.32: View ▸ New Viewer — several Composition viewers
- PanelKind::Viewer(n): more Composition viewers (panels::viewers). One is active (the full
  interactive viewer, showing the active comp); the others show their own comp's frame at that
  comp's current time (CPU or GPU frames, their own textures) and become active on a click or
  their tab, their comp becoming the active comp.
- view.newViewer (View menu, the Composition panel menu, Alt+Shift+N): a new viewer of the
  active comp next to the one in use, which is locked. Opening a comp while the active viewer
  is locked shows it in an unlocked viewer, or a new one when every viewer is locked. Closing
  the active viewer hands over to another with its comp. Tabs are named after their comp, with
  their own lock and swatch; Preview full screen maximizes the active viewer.
- UI state: viewers (comp per viewer) and active_viewer (serde).
- Tests: ui_viewers (lock on New Viewer, routing, click to activate, all locked → new viewer,
  close hand-over, tab titles; an ignored snapshot). The bounded-drag frames test asserts what
  holds when freed addresses are reused. Docs: parity, control protocol.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 15:00:11 +07:00
BFlat 6a5abad753 Merge pull request #54 from storytold/m4-content-keyed-frames
M4.14: content-keyed RAM preview frames (M4 done); proxies in the disk cache key
2026-10-05 14:39:33 +07:00
bflatasticandClaude Opus 5.5 26c714db7f M4.14: content-keyed RAM preview frames; proxies in the disk cache's content key
- FrameKey's identity is the comp's content (frames::comp_content), not the project revision:
  an edit keeps the cached frames of comps it doesn't touch and undo finds the frames of the
  state it returns to. The identity hashes the addresses of the comps a comp draws (~0.1 ms on
  the 1500-layer test project; the full content hash took ~117 ms), by value the footage, solids,
  proxies and project settings; with expressions, every item. Frames::content_of keeps a
  project snapshot per identity with frames (or in flight), so Arc::make_mut always copies an
  edited comp and its address can't be reused meanwhile; past 64 identities the least recently
  used go with their frames (long drags).
- drop_stale is gone (frames of other contents are evicted least-recently-shown first).
- disk_cache::comp_content_key covers items' proxies and Use Proxy (it served stale frames).
- Tests: frames (content keys, edits elsewhere and undo, bounded drags, in-place edit safety),
  ui_ram_preview (end to end), the disk cache proxy key. Docs: roadmap (M4 done), parity.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 14:39:17 +07:00
bflatasticandClaude Opus 5.5 4f7723a7b6 M13.31: Home size column test uses an unambiguous size (12.5 KB rounds to even)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 14:33:44 +07:00
BFlat 0f1cad8bbd Merge pull request #53 from storytold/ci-packaging-lint
CI: fix Packaging lint (SC2015 in release.yml)
2026-10-05 13:52:21 +07:00
bflatasticandClaude Opus 5.5 a48de85015 CI: packaging lint passes again (wasm-bindgen install step as an if, not A && B || C)
actionlint's shellcheck flagged SC2015 on release.yml's Install wasm-bindgen CLI step, failing
the Packaging lint workflow on main and release since 2 October.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 13:50:37 +07:00
BFlat fc0462a855 Merge pull request #51 from storytold/home-screen-ae
M13.30–M13.31: ArtCraft logo in the Tools bar; Home screen laid out like After Effects'
2026-10-05 12:52:18 +07:00
bflatasticandClaude Opus 5.5 d31094692e M13.31: Home screen laid out like After Effects'
- Home covers the whole workspace below the Tools bar (the panels wait behind it) instead of
  the Composition panel.
- Left rail: New Project (primary) and Open Project, the Home / Templates / Learn pages with
  icons, and the community links and ArtCraft apps at its foot.
- Home page: "Welcome to EffectCraft", quick-start tiles (New Composition, Open Demo Project,
  Import Footage, New from Template) and Recent as a table — thumbnail, name and folder,
  Opened (relative), Size, Kind — with a filter field, Clear list and an empty state.
- Theme tokens throughout (light themes follow); web_time for "now" (works on the web).
- Tests: formatters (opened / size / kind) and the new automation ids. Docs: parity.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 12:51:26 +07:00
bflatasticandClaude Opus 5.5 2039688245 M13.30: the ArtCraft mark in the Tools bar, About and Home; VectorCraft sibling link
- paint_logo draws docs/brand/artcraft-mark.png (the README's / getartcraft.com's ArtCraft
  mark) as supplied, decoded once into a mipmapped texture, instead of a drawn placeholder.
- The "More ArtCraft apps" sibling and help.sibling named DrawCraft (storytold/drawcraft, which
  doesn't exist): it is VectorCraft (storytold/vectorcraft); provenance comments in the path
  crate follow.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 12:49:47 +07:00
BFlat 053c445465 Merge pull request #50 from storytold/m4-motion-blur-depth
M4.13: motion blur of collapsed precomps and of animated shape / text content
2026-10-05 12:36:07 +07:00
bflatasticandClaude Opus 5.5 05188d036a M4.13: motion blur of collapsed precomps and of animated shape / text content
- Collapsed precomps: the renderer records the precomp layers a nested comp is drawn through;
  motion-blur sub-samples re-evaluate their transforms (when their motion blur is on) and map
  times down through each one, so the precomp layer's own motion blurs its nested layers. The
  containing (outermost) comp's shutter applies to collapsed layers. The GPU compositor gets
  this through Renderer::placement.
- Shape and text layers whose content changes within the shutter (compared by its cache key)
  draw their source at Samples Per Frame times and average them; the cache key covers every
  sub-sample's content.
- Tests: tests_motion_blur_depth (collapsed vs flat, the containing shutter, shape content vs
  the same move by Position); each fails without the change.
- Docs: roadmap, parity.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 12:35:30 +07:00
BFlat 9e79271893 Merge pull request #10 from storybored379/pr/spatial-tangent-getter-names
Scripting: Property.keyInSpatialTangent / keyOutSpatialTangent under their AE names
2026-10-05 12:11:18 +07:00
BFlat 58dd94e212 Merge pull request #9 from storybored379/pr/hold-key-incoming-segment
Keyframes: a Hold key no longer eases the motion into it
2026-10-05 12:11:10 +07:00
BFlat 1e8bcb519a Merge pull request #7 from storybored379/pr/comp-zero-frame-rate
Compositions: reject zero and negative frame rates instead of panicking
2026-10-05 12:11:02 +07:00
BFlat 26f577cb1a Merge pull request #6 from storybored379/pr/expr-runtime-stack-leak
Expressions: keep the runtime usable after errors and property reads
2026-10-05 12:10:53 +07:00
BFlat 9b5652f686 Merge pull request #49 from storytold/timeline-comp-tabs
M3.15: Timeline tabs per open comp; Project double-click opens; tab drag insertion mark
2026-10-05 11:59:07 +07:00
bflatasticandClaude Opus 5.5 c7c90cfa74 M3.15: Timeline tabs per open comp; Project double-click opens; tab drag insertion mark
- The Timeline shows one tab per open comp (dock DocTab). Clicking a tab shows that comp; ×
  or middle-click closes that comp's Timeline (comp.close), not the panel. Opening a comp brings
  its viewer and Timeline tab to the front.
- Project panel: a double-click on an item's name opens it (a comp gets its own tab) instead of
  renaming; Enter and the context menu rename.
- Tab drags: over a tab strip an insertion mark shows where the tab lands, and it lands there
  (Layout::insert_tab), which also reorders a group's own tabs; side zones are unchanged.
- Tests: ui_tabs (double-click, tab switch / close, drag reorder and cross-group insert; an
  ignored mid-drag snapshot); the Timeline close test follows the new behaviour.
- Docs: parity, control protocol.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 11:58:19 +07:00
BFlat a008a904af Merge pull request #48 from storytold/graph-auto-visible-keys
M5.15–M5.16: Timeline-visible J/K and Select All, Graph Editor Auto-Select Graph Type, Key Light eyedropper
2026-10-05 11:05:02 +07:00
bflatasticandClaude Opus 5.5 73b2607c9a M5.16: Key Light: keyer eyedroppers pick from the effect input (effect.pickColor); "keylight" search
- effect.pickColor {effect, param|prop, x, y (layer px), average?}: the colour of the effect's
  input at a point, set on the parameter (one undo step). Keying effects' Effect Controls
  eyedroppers use it, since the shown frame is already keyed; Ctrl/Cmd+click averages 5x5.
- effects::aliases / name_matches: Effects & Presets and effect.list (MCP list_effects) find
  Key Light by After Effects' name ("Keylight (1.2)").
- End-to-end test: a green-screen plate with an uneven screen, spill and soft edges keyed via
  effect.apply "Keylight (1.2)", pick, Clip Black / White and a render; UI eyedropper test.
- Docs: parity, agents (green-screen recipe).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 11:04:10 +07:00
bflatasticandClaude Opus 5.5 47f2a5a3e6 M5.15: J / K and Select All Keyframes use what the Timeline shows; Graph Editor Auto-Select Graph Type
- J / K (Go to Previous / Next Keyframe) and Ctrl+Alt+A (Select All Keyframes) act on what the
  Timeline shows, as in After Effects: the keys of its revealed properties, layer and comp
  markers and the work area (they used every property of the selected or all layers, revealed
  or not). The commands take `visible: [{layer, prop}]`, which the Timeline fills in; agents
  calling them without it keep the previous behaviour.
- Graph Editor ▸ Auto-Select Graph Type (an "Auto" button, on by default as in After Effects):
  the speed graph when every property shown is spatial (Position, Anchor Point…), else the
  value graph; choosing Value or Speed turns it off.
- Docs: parity, agents.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 10:36:55 +07:00
BFlat c90fd35ff8 Merge pull request #40 from storytold/timeline-keyframes
Fix the periodic freeze on Windows; keyframe drag, copy/paste and Graph Editor behave like After Effects
2026-10-05 08:13:28 +07:00
bflatasticandClaude Opus 5.5 ef856d8f67 M5.14: docs: parity, agents and preferences cover the keyframe, clipboard and responsiveness fixes
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 08:06:32 +07:00
bflatasticandClaude Opus 5.5 dfb0227468 M5.14: J / K stop at the work area; the Info panel shows a selected keyframe
- Go to Next / Previous Keyframe (K / J) also stops at the work area's start and end, as in
  After Effects.
- With one keyframe selected, the Info panel shows its property, time and value instead of
  "1 keyframes selected".

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 08:05:03 +07:00
bflatasticandClaude Opus 5.5 43bf2906d3 M5.13: keyframe paste: several copied layers paste onto as many layers in order; Paste Reversed pastes like Paste
- Keys copied from several layers and pasted onto as many selected layers go first to first,
  second to second, as in After Effects; every target used to receive every copied property
  (the later ones overwriting the earlier).
- Edit ▸ Paste Reversed Keyframes mirrors the clipboard over its span and pastes it through
  `keys.paste`, so it now follows the same rules as Paste: the layer's time stretch, the
  selected target property, Hold-only properties, value conversion and roving keys (its own
  simplified paste ignored them and kept existing keys' easing on a time collision).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 08:02:16 +07:00
bflatasticandClaude Opus 5.5 9ea1f1325d M5.12: double-click a keyframe to edit its value; Select Equal / Previous / Following Keyframes; roving keys as dots
- Double-clicking a keyframe of a numeric property (or its context menu ▸ Edit Value…) opens
  the property's value dialog for that key, as in After Effects (it only moved the current
  time); other keys still move the current time to them.
- The keyframe context menu has Select Equal Keyframes, Select Previous Keyframes and Select
  Following Keyframes (`keys.selectEqual` / `selectPrevious` / `selectFollowing`).
- Roving keys draw as small dots in the Timeline, like After Effects.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 07:58:03 +07:00
bflatasticandClaude Opus 5.5 daed03305a M5.11: Graph Editor: drags move every selected key, Shift keeps an axis, Ctrl / Ctrl+Alt clicks; drags and the transform box never merge or lose keys
- `keys.transform {fromStart}`: each step of a merged drag gives the whole transform since the
  drag started and applies to the keys as they were then. Keys squeezed onto one frame on the
  way (Alt-drag scaling, the transform box) are no longer merged for good, and a slow
  transform-box move, whose per-frame offsets rounded to nothing, now moves the keys.
- Dragging a key in the Graph Editor moves every selected key in time and value (it moved only
  the grabbed one), by how far the pointer went from where the button went down (the key no
  longer jumps under the pointer); Shift keeps the drag to the time or the value axis.
- Shift+click toggles a key in or out of the selection; Ctrl+click switches Linear ↔ Auto
  Bezier and Ctrl+Alt+click toggles Hold, as in the Timeline.
- The transform box and the Timeline's Alt-drag scale send whole-gesture values.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 07:52:20 +07:00
bflatasticandClaude Opus 5.5 9929b5f9ac M5.10: keyframes behave like After Effects: drags never destroy keys, stretched layers, Shift/Ctrl/Ctrl+Alt clicks, locked layers, Hold off
- A drag (keys.move steps sharing a merge key) applies its total offset to the keys as they
  were before it (`Session::key_move`, `prop::shift_keys`): dragging over another key of the
  property no longer deletes it; only the drop position replaces a key, as in After Effects.
- Keys of time-stretched layers move with the pointer: offsets are applied in comp time and
  snapped to comp frames (drags and Alt+Arrow nudges; they moved by the layer-time distance).
- Shift+click toggles a key in or out of the selection (`keys.select {toggle}`); Ctrl+click
  switches Linear ↔ Auto Bezier and Ctrl+Alt+click toggles Hold.
- Keys of locked layers can't be selected (clicks, marquee, Select All Keyframes), so nothing
  edits them.
- Toggle Hold off returns an eased key to Bezier (it always restored Linear).
- Shift-dragging a key snaps to everything the current time snaps to — other keys, layer in /
  out points, layer and comp markers, the work area — and a tooltip shows the key's time and how
  many frames it moved.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 07:40:42 +07:00
bflatasticandClaude Opus 5.5 3b6b390f68 M12.8: read the system memory every 30 s instead of 10 s
Each reading starts PowerShell on Windows (in the background since the previous commit); the
cache budgets only need to follow memory pressure, not track it closely.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 07:25:01 +07:00
bflatasticandClaude Opus 5.5 e3778b1afd M3.15: Ctrl+C / Ctrl+X / Ctrl+V reach Edit ▸ Copy, Cut and Paste in the real app
The windowing layer delivers Ctrl+C, Ctrl+X and Ctrl+V as clipboard events instead of key
presses, and the shortcut dispatcher only read key presses: in the app, copying and pasting
keyframes, layers and effects with the keyboard did nothing (tests passed because they inject
key presses). Clipboard events now map back to their keys with the modifiers held (so Ctrl+Alt+C
and the other variants work too), except while text is edited in the viewer, which handles them
itself. Ctrl+V sends no event while the system clipboard is empty, so copying now also puts a
line naming what was copied on it ("EffectCraft: 3 keyframes").

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 07:15:22 +07:00
bflatasticandClaude Opus 5.5 f3db1ebe38 M5.9: keyframe drags in the Timeline follow the pointer to the end; Shift snaps
Dragging a keyframe moved it a frame or two and then let go: the key's widget id includes its
time, so after the first move it was a new widget and egui ended the drag. The Timeline now
follows the whole gesture itself (`KeyDrag`: from where the button went down, by whole frames,
every selected key, one undo step), like After Effects. Holding Shift snaps the grabbed key to
the current time, the work area and composition markers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 07:14:02 +07:00
bflatasticandClaude Opus 5.5 2d4a0902ea M12.8: the app no longer freezes every 10 s on Windows: the system memory is read in the background
Every 10 seconds the UI thread re-read the system's memory for the cache budgets, and on
Windows that starts PowerShell (`Get-CimInstance Win32_OperatingSystem`) and waits for it:
about a second, sometimes two, during which the whole app froze. The reading now runs on a
background thread (`sysinfo::MemoryWatch`); `Session::memory_tick` takes the last reading and
starts the next one, never waiting. Console programs started for it no longer open a console
window (`CREATE_NO_WINDOW`).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 07:05:17 +07:00
BFlat 4b37443481 Merge pull request #39 from storytold/docs-m3-m4
Docs after M3 and M4, plus three fixes the review found (never-crash, web restore, guard test)
2026-10-05 06:46:50 +07:00
bflatasticandClaude Opus 5.5 a965d8df49 M3.9: an edit that only allocates ids still counts as a change
`Project::same_content` ignored `next_id`, so an edit that only took an id was dropped as a
no-op and the id could be handed out again (an edit may keep it beyond the project).
`guard::tests::the_session_survives_a_panicking_edit`, merged after M3.9, failed on main for
this reason. Ids now count; setting a value to what it already is still records nothing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 06:43:10 +07:00
bflatasticandClaude Opus 5.5 cb892b5239 M4.12: docs: ROADMAP, parity, web, preferences and agents brought up to date after M3 and M4
- parity: the status note covers the M3.9–M4.12 passes (feature counts unchanged: they fixed
  behaviour inside features already counted done) and the GPU effect count matches EFF-5 (280);
  the Layers, Compositions, Interface, Project and Preview rows name what is left and what
  landed; landed items in "Highest-value gaps" are struck through with where they landed, the
  gaps the passes left are items 16–18, and the closing paragraph no longer says 69 menu entries
  are disabled.
- ROADMAP: what is left includes the preview gaps.
- web: a restored session with unsaved changes stays modified and asks before closing.
- preferences: Adaptive Resolution Limit applies while dragging, not to playing previews.
- agents: nested comp settings and New Comp from Selection for several items.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 06:35:00 +07:00
bflatasticandClaude Opus 5.5 a12d4ae1fa M3.9: a browser session restored with unsaved changes stays modified
The web app reopens the last visit's project and, when it had unsaved changes, marked it
modified with `bump()`. Since M3.9 the modified mark follows the saved snapshot, and a bump on
the very project just opened counts as "back at the saved state", so restored changes looked
saved (no `*`, no unsaved-changes prompt). `Session::mark_unsaved` forgets the saved snapshot
instead: the project stays modified until saved, whatever is undone. Regression test included.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 06:33:40 +07:00
bflatasticandClaude Opus 5.5 b21e04b301 M4.12: never crash: hostile input in New Comp from Selection, an empty work area in Cache Frames When Idle
Following AGENTS.md's Never crash rules for the M3 / M4 additions:
- Cache Frames When Idle panicked (`clamp` with min > max) when the work area was inverted
  (start set past the end); an empty work area now has nothing to cache.
- `file.newCompFromSelection`: the sequence overlap times the layer count overflowed for a huge
  `overlapDuration` (wrapping to a wrong comp length without overflow checks); negative or
  non-finite values fall back to 1 s and the product saturates. The lead item is read with
  `get()` instead of indexing.
- Layer Settings' enablement reads the first selected layer with `first()`.
- Regression tests for both (each failed before the fix).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 06:30:46 +07:00
BFlat 7ee6d6a301 Merge pull request #38 from storytold/m4-nested-comps
Previews (M4): Preserve frame rate / resolution when nested, nested comps past their end, one motion blur gate
2026-10-05 02:08:13 +07:00
bflatasticandClaude Opus 5.5 e295679b7f M4.11: docs: architecture, parity and ROADMAP cover nested comps and motion blur
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 01:56:17 +07:00
bflatasticandClaude Opus 5.5 995d99f938 M4.11: one motion blur gate for layers, masks, effects and Advanced 3D
`Renderer::mb_on` (render options × the comp's switch × the layer's switch as the precomp
layers above limit it) now decides everywhere:
- Mask motion blur checked only the comp's switch and the layer's own switch, so Render
  Settings ▸ Motion Blur ▸ Off for All Layers and a precomp layer with motion blur off still
  blurred masks; it now takes the gate (`masks::MaskBlur`) and the comp's Samples Per Frame
  (four in Draft) instead of a fixed eight.
- Effects that read the shutter (`EffectEnv::shutter`) get it only when the layer is motion
  blurred in this render.
- Advanced 3D layers follow the precomp layers' Motion Blur switches too.
- The layer cache key of a layer with masks or effects includes whether it is blurred and the
  comp's shutter and samples, so a buffer blurred for the viewer is never reused by a render
  with motion blur off.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 01:55:34 +07:00
bflatasticandClaude Opus 5.5 0b5cff1743 M4.10: a nested comp shows nothing before it starts or after it ends
A precomp layer trimmed out (or time-remapped) past its nested comp's end kept drawing the
nested layers whose bars ran on. Like After Effects, the nested comp now shows nothing outside
its own span (`Comp::covers`), flattened, through a proxy or with Collapse Transformations, on
the CPU and the GPU (both go through `Renderer::source` / `collapse_into`).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 01:47:54 +07:00
bflatasticandClaude Opus 5.5 0fe6db00e8 M4.9: Preserve frame rate when nested or in render queue, Preserve resolution when nested
Composition Settings ▸ Advanced has both options (they were missing from the model, the
renderer and the dialog):
- Preserve frame rate: nested in another comp, the comp shows only its own frames
  (`EvalCtx::nested_time` floors the precomp layer's source time to the nested comp's frame
  rate; used for the nested frame, its proxy, aux channels, collapsed transformations and the
  viewer's internal wireframes), and the Render Queue renders it at its own rate whatever the
  Frame Rate override (`RenderSettings::rate`).
- Preserve resolution: nested in a comp rendered at Half / Third / Quarter, it renders at full
  size and the buffer's scale places it.
- `comp.settings {preserveFrameRate, preserveResolution}`, `comp.info` reports them, scripts
  read and set `CompItem.preserveNestedFrameRate` / `preserveNestedResolution`, Pre-compose
  copies them, and the dialog registers `dialog.comp.preserveFrameRate` /
  `dialog.comp.preserveResolution`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 01:33:14 +07:00
BFlat 4e98b8c4bd Merge pull request #32 from storytold/m4-preview
Previews (M4): RAM preview keys and eviction, Auto resolution while playing, Cache Before Playback that fits, Cache Frames When Idle, purges
2026-10-05 01:13:37 +07:00
bflatasticandClaude Opus 5.5 da3177954f M4.8: docs: parity covers the cached playback fixes
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 01:04:21 +07:00
bflatasticandClaude Opus 5.5 fa27d621e0 M4.8: Edit ▸ Purge clears what it names: disk and snapshot purges keep the RAM preview, memory purges drop decoded footage
- Every purge emptied the viewers' RAM preview; Purge ▸ All Disk Cache and Snapshot now leave
  it (Image Cache Memory, All Memory, All Memory & Disk Cache, All and All 3D Cache still
  clear it).
- The memory purges also drop the decoded footage frames (`FootageSource::purge`, the media
  pool's `clear_frames`; decoders stay open), so footage reads decode again.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 01:03:47 +07:00
bflatasticandClaude Opus 5.5 5ce641c6b3 M4.7: Cache Frames When Idle renders the work area in the background
Composition ▸ Preview ▸ Cache Frames When Idle was only a saved checkbox. After a second
without input or edits it now renders the work area's frames (from the current time on, then
from the start) into the RAM preview, a few at a time behind the viewer's own frame, until the
work area is cached or the RAM preview budget is full.

Docs: preferences.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 00:56:49 +07:00
bflatasticandClaude Opus 5.5 6214aecdb9 M4.6: previews: Auto resolution keeps its scale while playing, Cache Before Playback plays what fits, achieved frame rate in the Info panel
- Viewer Resolution Auto rendered at half its scale while a preview played, so frames cached
  while scrubbing were not played and frames cached by the preview were re-rendered once it
  stopped. Auto now follows the zoom alike playing and paused (Fast Previews still lowers it
  while dragging).
- Cache Before Playback waited for every frame of the range: a range larger than the RAM
  preview budget evicted its first frames and never started. The range is now cut to the
  frames that fit (90 % of Settings ▸ Memory & CPU's budget) with a message, then cached and
  played.
- The Info panel shows the frame rate a playing preview achieves and whether it is real time
  (`Playback::achieved_fps` / `real_time`, automation id `info.previewFps`).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 00:52:50 +07:00
bflatasticandClaude Opus 5.5 5648fdb1c2 M4.5: RAM preview: render options in the frame key, least-recently-shown eviction, stale frames freed at once
- `FrameKey` carries a hash of the render options (`frames::opts_hash`): switching Fast
  Previews (Draft / Fast Draft) or Realtime Shadows no longer shows frames rendered the other
  way.
- Eviction drops the least recently shown frame (`Frames::get` touches it), not the oldest
  inserted: a looping preview a little over budget no longer evicts the frame it shows next.
- Frames of earlier project revisions are dropped on every change (`Frames::drop_stale`); they
  could never be shown again but held memory (and GPU textures) until evicted.
- The green cache bars (Timeline, Preview panel) count the frames of exactly what the viewer
  shows — scale, 3D view / region of interest and options (`EffectcraftApp::shown_series`) —
  instead of any frame at that scale.
- Playback loops compute the key's fixed part once (`EffectcraftApp::frame_series`).
- Docs: architecture.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 00:43:56 +07:00
BFlat 249cc145e3 Merge pull request #8 from storybored379/pr/fullscreen-shortcut-off-mac
Shortcuts: don't bind macOS-only Ctrl+Cmd shortcuts off macOS
2026-10-05 00:16:09 +07:00
BFlat 9ec7680291 Merge pull request #20 from storytold/m3-layer-settings
Project operations (M3): Layer Settings, lock-aware layer commands, New Comp from Selection, menu fixes, safe project files
2026-10-05 00:15:20 +07:00
bflatasticandClaude Opus 5.5 cb6e1beebc M3.14: docs: ROADMAP marks project operations done
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 00:08:29 +07:00
bflatasticandClaude Opus 5.5 948f06fbf2 M3.14: project files name the version that wrote them, never save unreadable, keep their format
- `Project::to_file_json` writes `savedBy` (the EffectCraft version) ahead of the project and
  checks that the text reads back before anything is written: JSON has no NaN or infinity, so
  such a value made a file that saved but could never be opened. Save now fails with a message
  and the file on disk is left as it was.
- Opening a project saved by a newer EffectCraft warns that settings this version doesn't know
  are lost on saving; `file.open` reports `savedBy`. A file that isn't a project names itself in
  the error.
- Increment and Save keeps the extension (`.ecprojx` stays XML); Save a Copy writes XML for
  `.ecprojx` / `.xml` paths, like Save As.
- Docs: architecture (document model), parity.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 00:08:07 +07:00
bflatasticandClaude Opus 5.5 6fc39eb133 M3.13: menu bar: Group/Ungroup Shapes in the Layer menu, Window ▸ Learn, flowchart shortcuts, Center In View shortcut, Reveal in Explorer on Windows
- Group Shapes (Ctrl+G) / Ungroup Shapes (Ctrl+Shift+G) sit in the Layer menu itself, as in
  After Effects (they were under Mask and Shape Path).
- Window ▸ Learn opens the Home screen's Learn tab.
- Composition ▸ Composition Flowchart is Ctrl+Shift+F11 and Window ▸ Flowchart Ctrl+F11 (the
  flowchart had the panel's shortcut and the panel none).
- Layer ▸ Transform ▸ Center In View shows Ctrl+Home.
- "Reveal in Finder" reads "Reveal in Explorer" on Windows and "Reveal in File Manager" on
  Linux (`menus::platform_label`).
- Docs: parity (View ▸ New Viewer noted as not yet).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 23:58:00 +07:00
bflatasticandClaude Opus 5.5 a34d0c421b M3.12: Pre-compose keeps the composition's settings; New Comp from Selection is one undo step with After Effects' options
- Pre-compose makes the new composition with the original's pixel aspect, background, motion
  blur and frame blending settings and 3D renderer (`Comp::nested_like`); it kept only size,
  rate and background.
- File ▸ New Comp from Selection records one undo step (two per item before) and takes each
  item's pixel aspect. Several items selected open the New Composition from Selection dialog:
  Single / Multiple Compositions, Use Dimensions From, Still Duration (default from Settings ▸
  Import ▸ Still Footage), Add to Render Queue, Sequence Layers with Overlap, Duration and
  Transition. `file.newCompFromSelection {single, dimensionsFrom, duration, addToRenderQueue,
  sequence, overlap, overlapDuration, transition}`; `layer.addItem {duration}` for stills.
- Docs: parity.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 23:48:54 +07:00
bflatasticandClaude Opus 5.5 89653f37de M3.11: layer commands respect locks, keep children in place on delete, arrange step by step, keep depth and separated dimensions, keep links on duplicate/paste
- Locked layers can't be selected (Timeline click, Select All, Ctrl+Up/Down; shy layers while
  hidden too) and are left alone by Clear/Delete, Arrange, Layer ▸ Transform and Center Anchor
  Point, which fail when every target is locked (`commands::unlocked`, `commands::selectable`).
- Deleting a parent unparents its children where they are, in the same undo step: the
  pick-whip compensation moved out of `layer.setParent` into `layer::parent_fixes` /
  `ParentFix::apply`, which now also handles separated X/Y Position.
- Bring Forward / Send Backward move each selected layer one step (a non-contiguous selection
  was squeezed into one block).
- Center In View / Fit to Comp keep a 3D layer's Z (and Fit keeps Scale/Anchor Z); Reset /
  Center / Fit / Center Anchor Point work with separated dimensions (`layer::update_position`).
- Duplicate and paste keep parents and track mattes, following the copies when both were
  copied; paste dropped them always.
- Docs: agents, parity.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 23:33:54 +07:00
bflatasticandClaude Opus 5.5 753de070a0 M3.10: Layer Settings opens Solid Settings with "Affect all layers that use this solid"; settings dialogs register every control
- Layer ▸ Layer Settings on a solid or adjustment layer opens Solid Settings on the layer's
  solid (it ran an empty command before): name (renames the layer and the solid), size, Pixel
  Aspect Ratio, colour, and "Affect all layers that use this solid". It starts off, as in After
  Effects, so changing a solid other layers share gives this layer its own copy.
  `layer.settings {pixelAspect?, affectAll?}` → `{layer, solid?}`; `layer.newSolid {pixelAspect?}`.
- Nulls ask for their name; text and shape layers have no settings (the entry is disabled).
- Composition Settings / New Composition and Solid Settings register an automation id for
  every control (`dialog.comp.*`, `dialog.solid.*`). Composition Settings takes any typed frame
  rate besides the list, and New Composition starts from the last settings used.
- Colour buttons in both dialogs show the stored sRGB colour (`widgets::srgb_color_button`);
  egui's linear picker showed it lighter.
- Docs: agents, parity.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 23:22:09 +07:00
BFlat 3861b6d043 Merge pull request #13 from storytold/m3-project-operations
Unsaved changes: ask before closing a modified project; the modified mark follows undo
2026-10-04 23:05:45 +07:00
bflatasticandClaude Opus 5.5 e0ed28f55d M3.9: closing a modified project asks to save it first; the modified mark follows undo
- Quit and the window's close button, File ▸ New Project, Open Project, Open Recent, Close
  Project, the demo project and a Home template show Save / Don't Save / Cancel when the
  project has unsaved changes (Save asks for a path when untitled; cancelling that keeps the
  project open). File ▸ Revert asks to discard. Before, all of them dropped unsaved work
  without asking.
- The window title shows the project's path and `*` while it is modified.
- The modified mark follows undo: the session keeps the snapshot it last opened or saved
  (`Session::saved_project`), so undoing, jumping in the History panel or rolling back a batch
  to the saved state clears it.
- An edit that changes nothing (a value set to what it already is) records no undo step and
  doesn't mark the project modified (`Project::same_content`, comparing untouched comps by
  pointer).
- Agents' `engine.execute` / MCP calls are never asked; `ui.menu.invoke` shows the prompt like a
  click (automation ids `dialog.unsaved.save` / `dontSave` / `revert` / `cancel`), and the
  control channel's `app.quit {force: true}` skips it.
- Docs: architecture, agents, control protocol, parity.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 23:02:14 +07:00
BFlat 58163a28fb Merge pull request #5 from storytold/docs-accuracy
Docs: accuracy pass (README, ROADMAP, parity, preferences)
2026-10-04 01:16:08 +07:00
BFlat 8a7142f9eb Merge pull request #4 from storytold/timeline-properties
Timeline: working rename and twirl arrows, every property shortcut (AA…UU, Alt+Shift keys, Ctrl+`)
2026-10-04 01:16:00 +07:00
bflatastic 2c056ae6cf Docs: accuracy pass — README (306 effects, the web build ships, HEVC/AV1 export, pure-Rust encoders are FilmCraft's plus our own, an accurate 'still to come' list instead of features that exist), ROADMAP (M11 done, M12 mostly done, M13 done, all effects in full), parity summary (no simplified effects left), preferences (web storage is OPFS/IndexedDB; the in-window macOS menu bar setting), CLAUDE.md (the Illustrator sibling is ../vectorcraft) 2026-10-04 01:13:21 +07:00
bflatastic f95eface83 M13.20: docs — README (Timeline shortcuts and Puppet tools), control protocol (reveal / keyAt / twirlSelected UI commands, timeline.layer.<id>.row and viewer.puppetPin.<uid> element ids), agents guide (Timeline: layer.arrange above, reveal shortcuts, keys.toggleTransform, rename), architecture (puppet selection, rigging and follow-through; per-thread expression context recovery), parity (M13.17–M13.20) 2026-10-04 01:10:36 +07:00
bflatastic f9990f7387 M13.20: every Timeline property shortcut — double presses AA (Material Options), PP (paint / Roto Brush / Puppet), SS (selected properties), RR (Time Remap), TT (Mask Opacity), MM (all mask properties; a single M now shows Mask Path), FF (missing effects), EE (expressions) next to LL and UU; Alt+Shift+A/P/S/R/T add or remove a keyframe at the current time on every selected layer in one undo step (keys.toggleTransform: separated X/Y/Z Position, Orientation and X/Y/Z Rotation on 3D layers); Ctrl/Cmd+` twirls the selected layers open or closed (from a reveal, their full tree) 2026-10-04 01:10:33 +07:00