Draggable nool: captured-geometry drag rewrites, plus navigation, sound, undo, and persistence - #6
Merged
Conversation
- Animate.tsx: dynamic view-transition-name assignment/cleanup instead of pregenerated CSS rules for ids 0-99 - App.tsx: defer setHover actions during active view transitions, replay after finish; reassign transition names post-DOM-update - index.css: drop dead static view-transition rules, tweak light-theme transform-view shadow colors Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Living record of the capture-vs-internalize investigation: dragology engine findings, decision log (morph over floating, flat clone overlay over in-place nested FLIP, capture-first/SVG-never), substrate contract, nool prerequisites, plan, open questions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- src/motion/Motion.tsx: flat overlay of shallow per-id clones tweening measured boxes (left/top/size/font/opacity), depth-stacked, with jank-free retargeting (origin = current blend) and enter/exit fades; no-change guard skips non-geometric actions - remove View Transitions API entirely (Animate.tsx, inject branching, hover deferral, ::view-transition CSS, action-name-class rules) - componentize views for DOM stability: Seed/StageView/ToolsView/ ExpView/PreView are real components; expression kids id-keyed via <For>; toolbox rows keyed by transform index reading reactively; AdjacentPossible early-return reactivity fixed; hover mask memoized - verified via CDP: transform morphs run and tear down cleanly, 0px retarget jump, hover updates stage classes with zero DOM replacement See design/captured-geometry.md (Status / Step-1 findings). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Context-scoped selectors (#stage .node.comp, .node-container projection, #seed symbols, theme rules) did not match clones mounted under #main, so nodes lost most styling mid-morph. The overlay now lives inside the hidden .node-container and layers punch through with visibility:visible, keeping the genuine cascade context. Also tag layers with data-motion-id. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes mid-morph style loss: black operators, lost mask tints, head glyph jump-and-pop. Three mechanisms: - subtrees moving rigidly (uniform scale+translate, no internal enter/exit) stay whole as one full-clone layer, so filters/shadows/ descendant selectors compose exactly as live (28 layers -> 7 on a root commute) - split layers get display:contents context shells carrying real ancestor classes (selector matching + inheritance, zero geometry) - ancestor filter values are copied inline onto split layers Also freeze pulse/hover animations during measurement, group exit ghosts (survivors stripped), and reconstruct member boxes from group-relative coords on retarget (measured 0.001px jump). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Profiling showed morphs were smooth per-frame (8.3ms median) but paid two ~120-150ms paint stalls: first raster of overlay layers, and a full stage repaint at teardown. Fixes: - dim real content (opacity 0.001 on container children, overlay excluded) instead of visibility:hidden, keeping its texture warm so teardown is compositor-only (end stall eliminated) - drive translation-only layers with the compositable translate property + will-change (raster once); translate not transform so clone animations composing on transform are preserved - keep animations on clones (only transitions are suppressed): fixes the selected-head pulse glow vanishing during morphs - reinstate the selection-outline morph as a synthetic glow layer tweening between old/new selected boxes over the live stage - raise no-change guard to 1.5px (selection border jiggle was triggering full morphs and shadowing the selection path) - revert toolbox shadow to the deployed soft variant (the harder one was from the unpushed exolivelit css-tweaks commit) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- exit ghosts sort at old depth minus 0.25 so an eliminated wrapper (often a large mask-tinted box) recedes UNDER the surviving subtree that takes its place, instead of covering the stage (the blue-flash bug on identity-elimination) - tween clock now starts on the second rAF tick, after the first painted frame has paid layer rasterization, so the animation opening is no longer skipped - Shift+S toggles body.noshadows (box/text shadows off) for perf comparison; measured: ~170ms raster stall per morph with shadows, zero stalls without - shadows are the entire press-time hitch Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GPU tracing showed the morph hitch was texture re-raster, from three sources, each treated differently: - overlay clones: always-on box-shadow:none for .motion-layer (moving nodes are shadowless in flight; selection glow layer excluded). Always-on matters: toggling a class on the real stage per-morph invalidates the whole tree (~76ms) and was self-defeating - stage content permanently promoted (will-change: opacity) so dimming is an opacity flip on a warm texture, not a re-raster - toolbox rows repaint their 10-layer pat shadows whenever match classes flip (even keyboard transforms): will-change on .source/ .result halves it (65->33ms, no visual change); Shift+D toggles a calibrated 4-layer pat shadow (max pixel delta 26/255) that takes it to zero stalls, for aesthetic judgment Also: TransformView memoizes its transform by reference so unflipped rows keep their DOM on tool use. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- settings.dragging with a corner-icon toggle (below the sound icon, mirroring the top-right cluster) so drag and noolbox interaction cannot interfere; in drag mode stage pointerdown grabs instead of selecting - src/drag/Drag.tsx: enumerate transforms applicable at the grabbed node (both directions, ids preserved + freshen, structurally deduped); render each candidate into a hidden probe inside #stage (px font-size replicating stage depth scale, center-aligned) and batch-measure per-id boxes; ~10ms for 5 candidates - debug overlay: dashed box on the grabbed node, colored anchor dot per candidate (hollow when the grab vanishes), cleared on release; coincident anchors make rule conflicts visible Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Motion gains a pointer-driven manual mode sharing all layer machinery with the clock driver (capture_before/build_layers extracted): manual_start builds layers from current display toward probe-measured candidate boxes, manual_set drives t, manual_release springs back, and committing simply injects the action - animate() captures the manual blend as origin, so the handoff is seamless. Drag = closest-of-betweens: pointer projects onto each grab->anchor segment (rel-offset mapped boxes), nearest wins, t past 0.5 on release commits at the candidate's site. Key discovery: rules applied AT the grabbed node often give zero anchor travel (commuting your own children does not move you), so enumeration now covers ancestor sites too - the grab becomes a moving subterm (drag the operand to commute the parent; verified end-to-end). MIN_TRAVEL filters self-static candidates. ~14 candidates in ~18ms. Also: :active wobble suppressed in drag mode (#seed.dragging). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- DRAG_TOOL_IDXS temporarily narrows drag candidates to commutativity (+,x) and distributivity while the interaction is tuned - track visualizer: one segment per candidate from the grab point to its anchor (dashed when inactive, faint when unreachable), a perpendicular tick at the commit threshold, the active track drawn solid with its dot enlarged, and a projection-foot marker carrying a live t readout that turns green/checked past commit - note: chaining was never enabled; a drag commits exactly one rewrite Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nearest-segment dispatch let nearly-collinear tracks steal the drag mid-flight (grab the die, watch it become an ancestor commute). The active track now keeps the drag unless a challenger is 24px closer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A candidate is offered at grab-node X only if X itself moves in it: when X's box maps rigidly through its parent's displacement (same relative position and scale), the rule relocated an enclosing subtree and X merely rode along - that drag belongs to the ancestor. Derived from measured geometry (live vs probe boxes), no pattern annotations; reproduces the animate-algebra demo's trigger discipline for wildcard interiors. Verified: grabbing a factored sum inside a product offers exactly commute-times and distribute; ancestor commutes that would carry the whole product are excluded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The drag state is no longer a per-frame nearest-candidate classification (discontinuous at decision boundaries, patched by stickiness) but the position of a knob on a star-shaped rail network joined at the grab point. The pointer pulls; the knob chases its projection along the current rail at bounded speed and may change rails only within a 10px hub radius (checked before chasing - a diagonal pull still projects positively onto the wrong rail). Continuity is by construction; commitment away from the hub is topology, not a tuned threshold. STICKINESS is deleted. Debug state exposed as window.__knob. Also: candidates where the grabbed node vanishes are no longer offered (the demo's triggers always survive); this removes the misplaced track caused by the candidate-root fallback anchor, whose position varied wildly with the within-node click position. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- settings.dragMechanic with an 8th corner icon (beside sound, top left): Rails = knob-on-rails; Closest = the original memoryless nearest-segment dispatch, kept for feel comparison - grabs are now a singleton: a new grab force-ends any previous one (a crashed test script demonstrated how a leaked drag loop can outlive its pointerup) - design doc: dragology placement (d.rails as a dispatch combinator sibling of closest), the factor-affordance case where geometry is stricter than the demo's trigger annotations, and the merge-rule id survival wrinkle Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- settings.dragDebug (default on) gates the rails/anchors/t-readout overlay and the console table; 9th icon completes the top-left 2x2 - every settings icon now carries a title tooltip describing what it toggles and its current state Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
actions/cache@v2 is hard-failed by GitHub now; the exolivelit branch already bumped checkout and cache to v4 and its deploys worked. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mixing a reactive class attribute with classList lets any class re-evaluation (projection or theme toggle) rewrite className and drop the classList-managed names until their own values change. This is why drag-mode shake suppression (#seed.dragging) died after switching projection modes; #main could likewise lose .selected on theme toggle. Both elements now build one class string. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Small circles left of each rule row (drag mode only, centered in line with the arrow) toggle whether that rule generates drag candidates, both directions together. State lives in tools.dragActive (default: commutativity + distributivity), driving Drag.enumerate directly; the DRAG_TOOL_IDXS stopgap is gone. Tooltips show on/off state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
During a drag the scene morphs toward the candidate exp, whose created nodes carry enumeration-time fresh ids. Committing by re-deriving the rewrite minted DIFFERENT ids for the same nodes, so the handoff saw the almost-faded-in nodes as exits plus identical enters - a double pop. The commit now injects f = () => candidate exp (reference-guarded: falls back to re-derivation if the stage exp changed mid-drag), giving id continuity across the drag-commit boundary; verified that in-flight enter ids survive into the committed DOM. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the animate-algebra emergeFrom treatment to the captured substrate: entering nodes start AT a source box, fully visible, and pull out; exiting subtree ghosts converge into the box of whatever replaces the rewrite site while fading. Motion gains EmergeOpts (emerge/converge id maps) on manual_start/build_layers; Drag computes v1 provenance geometrically (enters emerge from the grab - the demo's expanding-rewrite trigger rule; exits converge into the site's replacement root). Verified: identity-intro's +/moon grow out of the grabbed mushroom at opacity 1; identity-elim's wrap ghost is absorbed into the survivor's destination. Pat-level provenance (clone-from- original, prefer-grabbed-copy) recorded as the follow-up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…enance Created nodes that structurally equal an existing site-scoped node are CLONES: they emerge full-size at their original (sym and head mapped, pixel-coincident at t=0 - fixes the displaced clone dice). Nodes with no counterpart GROW from 15% at their source, opacity 1 (the moon scales up from nothing; no fades). Exits mirror: subtrees with a surviving structural twin MERGE (coincide, no fade); otherwise they ABSORB (shrink into the site's replacement while fading - the wrap ghost is sucked into the survivor's destination). Structural equality is provenance-lite; rewrite-recorded provenance remains the endgame. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Exits are now exactly dual to enters: per-node shallow layers (whole- subtree ghosts removed) with merge (coincide with the surviving twin, kind-matched sym/head, opacity 1) or absorb (shrink to 15% at the site's replacement, opacity 1 - the dual of grow). Nothing fades in provenance-carrying morphs; the click-path fallback fade remains. Emerging layers get z-0.4 so creation comes from behind. And prefer- the-grabbed-copy: when a merge rule consumes the grab but keeps a structural twin, the grabbed subtree is spliced over the twin at the candidate level - both dice now afford factoring, and the other copy merges into the grabbed one. Verified: factor from either die, losing die converging at opacity 1; identity-elim collapsing at opacity 1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- tap-highlight/long-press-callout/user-select suppressed on the play surface (the misshapen focus box on tap); touch-action:none on #seed so drags are not stolen by scroll/pull-to-refresh - 100dvh (URL-bar-safe); font clamp on narrow OR short viewports (landscape phones are short, not narrow); portrait stacks stage above noolbox; coarse pointers get finger-sized icons/toggles with spread corner clusters - settings.dragging defaults on for coarse pointers - desktop (>820px wide and >520px tall) is pixel-identical Verified in device emulation: portrait+landscape fit with no overflow, zero icon overlaps, touch-pointer drag grabs/morphs/releases cleanly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unselecting expands the glow to #main's box (the window-as-root-node) instead of fading in place; selecting shrinks it back down. Holding a background press creeps the window corners in via #main.selected:active, reviving the border-radius transition orphaned when view transitions were replaced (93d2e89). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three real cuts were ending the glow abruptly: (1) the commit flash kept mix-blend-mode: plus-lighter on for its full duration — additively brightening the row even after its keyframes hit zero — then snapped to normal compositing when the class dropped; the blend mode is gone and the blown-out peak comes from brightness instead. (2) A single fade slot meant any new fade (rail change) hard-cleared the previous row; fades are now per-row. (3) A constant half-life reads as a uniform wipe (brightness perception is ~logarithmic): the half-life now grows from 40ms toward a 200ms cap, giving a rapid falloff that levels into an afterglow clearing in about a second. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The commit flash's additive blowout returns — but on an ::after overlay whose OPACITY animates to zero, instead of blending the row itself: an additive layer at opacity 0 contributes nothing, so the class removal at the end stays visually a no-op and the continuous exit is preserved. White-hot radial core over the row, fading on the same rapid-falloff- into-tail curve as the rest of the glow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The overlay approximation traded too much of the blowout for exit continuity; Andrew prefers the real thing. The row itself composites plus-lighter for the flash (the rule-press :active look, brightness back to 1.5), accepting the compositing snap when the class drops at the end. The filter still fades on the falloff-into-tail keyframe curve. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The drag commit action (transformNode) had no case in the sound dispatch, so drag rewrites were silent while button rewrites weren't. Reversed candidates play the reversed sample, mirroring the button path; gated by the sound setting like everything else. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The genuinely exciting experiment: each rewrite's commit sample gets a GrainPlayer looping a ~120ms grain window whose position is driven by the live rail parameter — dragging literally scrubs the rewrite's sound, unfolding as you pull, retreating as you back off, mirrored for reversed candidates. Volume swells toward the commit point (-24dB → -10dB); rail changes switch samples; release fades the grains out over 80ms before the commit sample (or silence, on revert) takes over. Transforms now carry their sfx bank name (Tools' mk records it) so the drag layer can find the sample; scrub players own their buffers so the commit Players' reverse-toggling can't cross-talk. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The scrub read as stuck tape, and it was also double-speaking (scrub during + full sample after). Rather than patch one variant, all the candidate designs are now live behind a debug cycle (Shift+A, logged to console): scrub (as before), scrub-only (no commit sample — the scrub IS the gesture; default, to evaluate next), detents (mechanical ticks on rail acquisition and commit-threshold arm/disarm), tension (filtered noise rising with t), plucks (quantized notes at quarter points, descending when receding), and commit-only (the pre-experiment baseline). All but scrub-only still play the transform's sample on commit; continuous layers fade out on release. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verdict from the Shift+A bake-off: drags speak in plucks — quantized notes at the rail's quarter points, lower octave when receding — and the rewrite samples are reserved for button-mode application. Deleted: the granular scrub (players and all), detents, tension layer, the mode cycle + Shift+A binding, the commit-sample suppression hook, and the Transform.sfx plumbing the scrub needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same peak and falloff shape, compressed; the class drops at 330ms. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
History lives in the model as past/future stacks of stage exps; every successful rewrite (button, drag — both funnel through update_stage) and restart pushes the pre-state and clears the future. Undo/redo swap exps through Stage.put_exp (statics rebuilt), unset the selection (old paths may not exist), and reset hover. Restart is undoable: Escape can't destroy work. Restored exps keep their old node ids, so the motion layer animates undo as a full morph for free. Ctrl works as the modifier too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Concept 1 from the sound-mapping menu: the pluck triad is derived from the rule's own patterns — isomorphic rearrangements (commute, associate) pluck open fifths, growing rewrites (identity/inverse intro, double-neg, distribute) a major triad, shrinking ones (their reverses, factoring) minor. size(result) - size(source) decides; nothing is hand-assigned, new rules get sounds automatically, and since flip swaps the patterns a reversed rule is automatically its forward form's harmonic opposite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Identity-intro on a composite operand grew its new 0 and + from the operand's geometric center — a dot materializing amid the children, increasingly wrong as operands get big. Grow-mode emerges whose source is a comp now anchor at the comp's HEAD box (the operator icon), and dually, fallback absorbs into a comp target converge into its head. Atom sources/targets are unchanged (they are their own head). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Documents the drag-era sound design: one meaning per audio channel (register=depth, contour=direction, quality=structural effect landed; key/content and pan open), the six-way bake-off verdict, and the non-arbitrariness ladder (size delta → operator multiset → pattern melody) with the current toolbox's operator multisets tabulated. TODO.md indexes sound.md and drag-target-decomposition.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ladder rung 2 (supersedes the quality triads): each operator symbol has a pitch class — the sound alphabet, the one axiomatic choice (➕→C, ✖️→G, ➖→Eb, other→D, empty→A3) — and a rule's quarter-point plucks walk its content from source to result: ¼ = source multiset, ½ = the invariant core (∩), ¾ = result. Multiplicity stacks octaves, so associativity is literally the plus-note doubled while commutativity is it alone; created content audibly arrives near commit and consumed content departs; flip swaps the walk. Advancing plucks crescendo, receding replays an octave down. sound.md updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Default drag loadout gains associativity (+ and ×) and additive identity alongside the commutes and distribute. The reported hard-to-reproduce stuck-mid-tween state resisted three scripted chaos campaigns (rail flapping through the hub, extreme holds, commits with depth rescale, regrabs mid-commit-morph — all clean, no errors). Two real holes closed on inspection regardless: undo/redo now no-ops during an active drag (it would yank the world out from under the drag's probes and tween — a brand-new hazard since Cmd+Z landed), and manual_start's silent failure paths (empty before-capture, overlay mount failure) now warn, since either would freeze the previous overlay exactly as described. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Toggle between 'content' (multiset chords as landed) and 'content+harmony': each pluck chord gains one note, a third above its root — major when the rewrite grows (operator count delta), minor when it shrinks, none when isomorphic. The content letters are untouched; the third colors the mood, restoring the retired quality channel as a layer under the content skeleton rather than a competitor to it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drag mode: clicking anywhere on a rule row toggles whether it generates drag candidates (the pattern-press handlers step aside and let the press bubble). The side dot indicator is gone; the equals sign carries the state instead, glowing white when the rule is on. Wheel scrolling: the old handler constructed a fresh throttle closure per event, so it throttled nothing — every trackpad micro-delta stepped a full rule. Replaced with distance accumulation: one rule per ~100px of wheel travel, remainder carried, so a notchy mouse wheel still steps 1:1 per click and a two-finger sweep moves about one rule. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ring-buffer window is gone: all rules render and the noolbox is a real scroll container — continuous native scrolling (momentum, rubber band) with CSS scroll-snap settling on a rule when the gesture ends, replacing per-notch discrete stepping that felt stiff against thick rows. tools.size still sets the box height (shift+wheel resizes, now reading deltaX too for macOS's shifted-trackpad quirk); the wheelTools action and ToolBox.offset are deleted. Bonus: every rule row now always exists in the DOM, so the drag rule-glow reaches previously off-window rules (distribute). The equals glow is now the retired dot's look on the glyph itself: brightness(0) invert(1) renders the emoji as solid white, with a tight double drop-shadow halo instead of the diffuse text-shadow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Snap goes proximity -> mandatory: still fully smooth mid-gesture, but every rest position is row-aligned at the top — the middle ground between smooth and discrete, and no truncated top row at rest. The bottom partial row (unavoidable with variable row heights and a fixed box height, short of JS-driven resizing) now fades out via a mask, so clipping reads as the rules receding into a slot rather than being cut. Also fixes the left-edge hard line: becoming a scroll container forced horizontal clipping (a non-visible overflow-y drags overflow-x along), slicing the rule shadows that used to bleed past the box; 0.7em of left padding gives them room to fall off. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-stop The mask-fade made the box look LESS recessed — the hard clip lines at top and bottom were doing the work of the slot's lip. Reverted. And scroll-snap-stop: always goes in as a trial: every flick advances at most one rule, no sailing past snap points; remove if too notchy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The model's own state — world exp, selection, settings, drag loadout, box size, and the full undo history — persists to localStorage (a few KB of plain JSON; synchronous storage keeps boot simple, IndexedDB would buy nothing at this size), debounced 250ms after every applied action. Boot loads it back through Stage.put_exp (statics rebuilt); the ID counter is persisted and bumped so restored exps' minted ids can't collide with fresh ones. Undo works across reloads. hardReset returns everything to factory and clears the stored session. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Corner reorganization (three per corner, no 2x2 squares): top-left sound + adjacent-possible preview + the new reset-everything button (confirm-guarded, wearing the previously-unused dead icon); top-right theme/projection/symbols unchanged; bottom-left takes the drag trio (mode, mechanic, debug); bottom-right is empty — the motion icon is retired, since motion is load-bearing now and Off is no longer a mode worth a corner. Mobile coarse-pointer offsets updated to match. Also: the adjacent-possible previews were permanently blurred — the rest-state filter carries !important and the hover (sharpen) rule didn't, so hover never won; fixed. The scroll-snap-stop trial is removed (imperceptible at 9 rules). Shift+wheel box resize is now continuous (fractional size, smooth max-height) instead of stepped. TODO records the recess-lip observations and the exact-fit-height verdict. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Button-applied rewrites (noolbox clicks, keyboard 1-4/space, preview clicks) animated their enters as in-place half-size fade-ins and exits as in-place fade-outs, while drags had full provenance (clone/grow from sources, merge/absorb into targets, head-origin anchoring). Now App.inject hands animate() a provenance closure for transform actions at the selection: the site is the selection and the selected node plays the grab's trigger role, so identity, inverse, double-neg, and distribute look identical clicked or dragged. Drag commits pass through untouched (selection is unselected in drag mode; their provenance resolved during the manual morph). Closes the design doc's open click-path-emerge item. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Double-neg elimination merged its dying negations onto the mushroom's minus — any same-headed comp surviving in the site qualified as a merge twin, and the dying ➖ glyphs independently twin-matched any structurally equal head atom. Intro had the dual flaw (new negations cloned out of the bystander minus). Two tightenings: a comp only clones from / merges into a same-headed comp that itself CHANGED in the rewrite (participants, not bystanders — factor's product merge and distribute's clone still qualify since the surviving twin's content changes), and comp heads never twin-match on their own — they follow their comp or grow/absorb with it. Verified by trajectory sampling: the dying negation head now absorbs toward the site replacement's head, away from the bystander. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clones bake in the post-state's .selected decoration, so a rewrite that relocates the selection (factoring: the surviving times lands in the plus's seat) flashed a fully-formed white outline + pulsing head on a node still mid-flight. Now full morphs suppress the in-scene decoration (selection-morphing extends to the head's pulse) and fly the synthetic glow frame from the old selected box to the new one — it lands exactly as the live decoration returns at teardown, so there's no pop either. Unselecting morphs (undo) send the glow out to the window, per the root-selection metaphor. Glow-layer construction and the #main box are factored into helpers shared with the pure-selection morph. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The traveling-glow treatment now applies only when the selection MOVES between nodes (pre !== post, or an interrupted glow needs to finish). A stationary selection — commute at the selected node: same node, only its children rearrange — keeps its baked outline and head pulse riding the clone, which is the truthful rendering; suppressing it read as the highlight flickering off mid-operation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
captured-geometry: bystander-twin tightening findings entry; the flat-layer backdrop-tint observation with its candidate fix. sound.md: the shrink-at-half pluck asymmetry and queued follow-ups (pan, commit accent). TODO: pending verdicts (harmony, previews, die tint), the stuck-tween watch instructions, and the Iconify icon-lab workflow idea. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…the first fold Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Margin animation re-laid-out and repainted both shadow-heavy neighbors every frame. #stage itself stays transform-free so the fixed-position motion overlay keeps its viewport anchoring; the stage side rides its already-layerized root node. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clicking the seed now sounds in both modes (it previously borrowed the background click's unselect pluck, inert in drag mode): membrane boom on C2 with the pluck alphabet's open fifth ringing above through a long tail. Still clears selections like a background click. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The complete
draggablebranch (64 commits): direct-manipulation rewriting driven by captured geometry, and the session-work that grew around it.Drag system (captured geometry)
Interaction & UI
Sound
Design docs
🤖 Generated with Claude Code