Skip to content

Free keyframes: keys per property, ◆ in the Inspector, key rows and autokey in the timeline - #23

Merged
srivtx merged 6 commits into
mainfrom
free-keyframes
Oct 24, 2026
Merged

srivtx merged 6 commits into
mainfrom
free-keyframes

Conversation

@srivtx

@srivtx srivtx commented Oct 24, 2026

Copy link
Copy Markdown
Owner

Free keyframes: any number of keys per property, each at its own time with its own value and its own curve, like Figma Motion's timeline. "Rotate 0° at 0 s → 45° at 1 s → 30° at 2.5 s" is now three clicks, or one morpho set. Presets (entrance / Then / loop) stay the main path; keys are the "more control" layer, and both act at once (a layer can float and follow keys).

Model (ui/src/state/keys.ts, evaluation in motion.ts)

  • Key { t, v, ease? }, where ease is the curve into the key: an easing or time spring stretched over the gap from the key before, or a physics spring at its own pace. The default is ease in out.
  • Where keys live:
    • node.keys holds the transform (x, y, rotation, scale, opacity) and geometry (the same mod: / sym: / morph:t / line: names as animate).
    • style.keys holds material settings, numbers and colours.
  • Old files load unchanged. migrate() sorts keys and drops malformed ones.

Evaluation

  • Transform keys are absolute values; a group's are offsets from rest. buildScene hands them to the renderer as their own MotionLink, after the node's own motion and posed about the layer's origin, so a keyed rotation is exactly the layer with that rotation.
  • Opacity keys on a layer set its material opacity directly. On a plain group they fade its children.
  • Setting keys use the same paths as cycles: effectUniforms for materials, and geoDrivers in geoMotion, so moving geometry rebuilds as it does for cycles.
  • Keys win over cycles. The Inspector also keeps the two exclusive: a key drops the cycle, and ↻ drops the keys.
  • Colours blend in RGB, like cycles.
  • Before and after: a property holds its first key's value before it and its last key's after it. A looping timeline jumps back at the seam, and morpho timing says so.
  • Video export and exact frames honour keys. Both paths go through placement, effectUniforms and movingDocAt.

Inspector

  • ‹ ◆ › next to X, Y, rotation, scale and opacity. Every animatable material, modifier and shape setting gets ‹ ◆ › plus ↻ (the existing cycle).
  • ◆ keys the value at the playhead and pauses there. ⌥-click removes the key at the playhead. The arrows jump to the previous or next key.
  • Keyed fields show the value at the playhead. Editing one writes a key there.

Timeline

  • A row per keyed property, folding under its layer.
  • Diamonds over the value curve, or over the colours they pass through.
  • Drag a diamond to retime its key. It snaps to frames, keys, clip edges and the playhead.
  • Click a diamond for its editor: value, time, curve in (Advanced: the full transition editor), previous/next, delete.
  • ⌫ deletes the focused key. ← → nudge it a frame (⇧: ten frames).
  • Autokey: a record switch in the header. With it on, any change (canvas drag or Inspector) starts keys: the old value at 0 s, the new one at the playhead.
  • Edits to keyed properties: an edit to a property that already has keys always keys it at the playhead. A canvas drag moves the key by as much as it moved the layer.

morpho CLI (kept small)

  • edit.ts: Star.keys.rotation=[[0,0],[1,45],[2.5,30,"linear"]], and likewise keys.<material param> and geometry keys. null removes them. Values and curves are checked, and the timeline grows to hold the last key.
  • describe.ts lists keys.
  • timing.ts draws ◆ rows and notes keys past the end or jumping at the loop seam.
  • catalog keyframes documents keys.
  • page.ts: one-line change so keyed designs count as moving.

Tests

  • Unit tests: state/keys.test.ts covers evaluation, transform and group poses, composition with motion, geometry keys winning over cycles, edit/autokey semantics, parsing and migration. agent/keys.test.ts covers set, describe, timing and catalog. tracks.test.ts covers key rows.
  • E2E: a new section keys a star through 4 times in the Inspector, then checks the canvas angles, drags, clicks and deletes diamonds, undoes, uses autokey, and checks the exported video frames. All 9 of its checks pass.
  • npm test and npm run typecheck pass.
  • The full e2e suite didn't run to completion here, because this machine was heavily loaded (load average 13–20). Under SwiftShader a single page screenshot took 18–34 s against Playwright's 30 s timeout, so the suite crashed on a screenshot. Unmodified origin/main crashed the same way.
    • With a 120 s default timeout (an uncommitted local tweak), every existing check passed: 117 ✓. The only failures were 4 checks in the new section, all from one Playwright fill() race; the test now types like a person.
    • After that fix, the new section passes on its own. Worth a full npm run build && npm run test:e2e on a quieter machine before merging.
  • In the app (dev server): made a star rotate through 4 keys from the Inspector, retimed, edited and deleted keys in the timeline, autokeyed a drag, keyed a fill colour and keyed a group's rotation and fade. Also exported npm run morpho -- video of a star with 4 rotation keys plus fill keys; frames 0, 30, 75 and 119 show 0°, 45°, 30° and ≈−90°.

Known limits / follow-ups

  • Selection outlines and hit testing stay at the layer's own values, as they do for motion. Outside a drag, a key edit also sets the layer's own transform to the key's value, so the outline matches what's drawn right after an edit. After scrubbing it shows that last value.
  • morpho video still prints "(loops seamlessly)" for any looping timeline. That line is in scripts/morpho.mjs, which another session is editing, so it's left alone here.
  • Groups get transform keys from the CLI and the timeline. The Inspector's group box moves the children (which autokey keys).

🤖 Generated with Claude Code

srivtx and others added 4 commits October 24, 2026 02:39
Keys per property (transform, material, modifier and shape settings), each
with its own time, value and curve. Transform keys ride as a pose link
outside a node's motion; setting keys win over cycles in the renderer and
geoMotion; edits to keyed properties key at the playhead (autokey starts
keys). Files without keys load unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
‹ ◆ › next to the transform fields, opacity and every animatable setting
(↻ keeps the cycle); a row per keyed property in the timeline with
draggable diamonds, a key editor and Backspace to delete; a record switch
for autokey. morpho describe and timing show keys, set writes them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
◆ pauses on the frame it keys and keys the value there; ‹ › look at click
time while playing; key edits outside a drag settle the layer's own
transform onto the key, so its outline sits where it's drawn; timeline rows
use the layer's label; timing notes keys that jump at the loop seam.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
morphotype Ready Ready Preview Oct 24, 2026 10:37am UTC

srivtx and others added 2 commits October 24, 2026 15:42
A number box selected its text a frame after taking focus, so keys typed
in that frame were selected and the next one replaced them (45 became 5 in
CI). It now selects only while its text is untouched. The free-keys e2e
waits for the box to settle, polls for each key and says where it typed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@srivtx
srivtx merged commit 340d793 into main Oct 24, 2026
7 checks passed
@srivtx srivtx mentioned this pull request Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant