Commit Graph
225 Commits
Author SHA1 Message Date
Brandon Thomas 98872a4690 Merge PR #95: Improve AE-style editing, easing fidelity and Windows GPU rendering 2026-10-06 08:41:33 -04:00
Dexter 39e6907b5f G2: improve editing reliability and Windows rendering fidelity 2026-10-06 00:00:31 -05: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 <noreply@anthropic.com>
2026-10-06 11:35:57 +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
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 <noreply@anthropic.com>
2026-10-06 00:37:05 +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 <noreply@anthropic.com>
2026-10-06 00:25:57 +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 <noreply@anthropic.com>
2026-10-06 00:16:04 +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 <noreply@anthropic.com>
2026-10-05 23:48:41 +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 <noreply@anthropic.com>
2026-10-05 23:43:50 +07:00
Jonas Neubauer 41f5ee3aec G2 (#67): delete Project items with Delete and Backspace 2026-10-05 18:24:14 +02:00
Jonas Neubauer a0c1b60d61 G2 (#66): respect solo and visibility in viewer selection 2026-10-05 18:01:41 +02: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 <noreply@anthropic.com>
2026-10-05 20:39:45 +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 <noreply@anthropic.com>
2026-10-05 17:55:02 +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 <noreply@anthropic.com>
2026-10-05 15:18:54 +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 <noreply@anthropic.com>
2026-10-05 15:12:06 +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 <noreply@anthropic.com>
2026-10-05 15:00:11 +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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
2026-10-05 14:33:44 +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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
2026-10-05 12:49:47 +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 <noreply@anthropic.com>
2026-10-05 11:58:19 +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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
2026-10-05 10:36:55 +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 <noreply@anthropic.com>
2026-10-05 08:05:03 +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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
2026-10-05 07:14:02 +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 <noreply@anthropic.com>
2026-10-05 06:30:46 +07:00
Brandon Thomas c6b8f5412f Merge pull request #36 from storytold/pr/never-crash-ui
Never crash: frame-pool panic handler and ui-egui without expect()
2026-10-04 15:33:38 -04:00
Brandon ThomasandClaude Opus 5.5 543942d085 Never crash: frame-pool panic handler and ui-egui without expect()
ui-egui without expect()/unreachable!(): the frame pool is optional (rayon's global pool if the
OS refuses threads) and gets a panic_handler, because a panic in a rayon spawn() job aborts the
whole process by default; render-queue progress, the text-edit anchor and the GPU viewer
texture are bound where they're checked; the settings panel's Bool arm no longer panics.
Denies the panic lints in the crate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:52:28 -04: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 <noreply@anthropic.com>
2026-10-05 01:33:14 +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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
2026-10-04 23:22:09 +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 <noreply@anthropic.com>
2026-10-04 23:02:14 +07:00
storybored379andClaude Opus 5.5 18fcc48046 Shortcuts: don't bind macOS-only Ctrl+Cmd shortcuts off macOS
Off macOS `Cmd` maps to Ctrl, so Enter Full Screen's Ctrl+Cmd+F parsed
to the same keys as Find's Cmd+F and both commands were bound to Ctrl+F
(ui_and_menu_shortcuts_do_not_collide fails on Linux and Windows).
Shortcuts that use both Ctrl and Cmd can't be typed distinctly there,
so they are skipped when the key bindings are built off macOS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 01:17:34 -07:00
Brandon ThomasandClaude Opus 5.5 f3715aac32 M13.30: web depth and polish
- GPU in browser job workers: each job worker opens its own WebGPU device
  (deferred readbacks); the Render Queue export and the analyses' frame loops
  are futures (render::passes, offload::run_request_async) whose frames
  render in passes, awaiting the device between them. GPU particles and
  Advanced 3D read back under keys too. workers.jobs reports passes and
  readbacks; the smoke test measures them.
- Layer buffers in the browser's OPFS disk cache: frame workers back their
  layer caches with a PrefetchStore; the page plans what to fetch before a
  request from recent misses (LayerPrefetch over the shared DiskIndex).
- Templates embed footage (default on, size-capped with a warning) and
  creating from a template extracts it next to the new project / into
  browser storage.
- ICC LUT profiles (lut8/lut16/lutAtoB/lutBtoA, from the public ICC spec) in
  Simulate Output > My Custom RGB, baked into a 3D LUT for the viewer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 02:57:17 -04:00
Brandon ThomasandClaude Opus 5.5 b73028d999 Merge branch 'main' into worktree-agent-a9fea98fe9839069d (filmcraft rev 2c5ba7a: AAF and OMF interchange)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:45:00 -04:00
Brandon Thomas 05b614990b Merge branch 'main' into worktree-agent-a9fea98fe9839069d 2026-10-04 00:25:43 -04:00
Brandon ThomasandClaude Opus 5.5 955422819c M13.26: Premiere Pro interop commands (file.importTimeline, file.exportTimeline with ProRes 4444 pre-renders), File menu entries, export dialog, docs and pixel round-trip tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:25:43 -04:00
Brandon Thomas 7d9f369a5e Merge branch 'main' into worktree-agent-acd022ffe72ec3783
# Conflicts:
#	docs/parity.md
2026-10-03 18:35:29 -04:00
Brandon ThomasandClaude Opus 5.5 1b726349ab M13.24: web app depth (WEB-1) — GPU effects in frame workers, OPFS disk cache, storage manager
- GPU in the browser: each frame worker opens its own WebGPU device (Gpu::request_deferred) and
  renders the GPU compositor and GPU effects with deferred readbacks (effectcraft-gpu deferred):
  readbacks keyed by content start with map_async and miss, the pass returns placeholders, the
  layer cache's gate drops inserts, and FrameServer::resume renders again once the device
  delivered. Backend Auto times whole frames per comp in the workers. Frames with effects go to
  GPU workers instead of the page. CPU fallback without WebGPU in workers (?nogpuworkers).
- Fix: a WGSL constant (3.40282347e38) that Chrome rejects invalidated every GPU kernel in Chrome
  (the page's compositor too); a test checks every float literal fits f32.
- Disk cache in the Origin Private File System: workers write sealed frame entries with sync
  access handles (temp file + move), the page keeps the DiskIndex (LRU under the settings' limit,
  at most half the quota) and reads hits instead of rendering; survives reloads; blue cache bar.
- Storage manager: Settings ▸ Disk ▸ Browser Storage (usage/quota, persistent storage, Clear
  buttons, automation ids) and storage.info / storage.persist / storage.clear (StorageHost).
- Docs: web.md (GPU workers, deferred passes, disk cache, storage manager, threading design:
  one engine instance per worker), architecture, preferences, parity (WEB-1), roadmap.
- Tests: deferred passes vs CPU on a real device, placeholder gating, frame server passes and
  Auto, GPU worker routing + disk cache across a reload, storage commands and widgets, store
  usage by kind; smoke test: GPU effect frames in workers with pixel parity, disk hits after a
  reload, storage manager.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 18:35:03 -04:00