Skip to content

release: merge beta into main (roadmaps 5-9 + mobile/ui/context-menu fixes) - #25

Merged
AlexZ005 merged 250 commits into
mainfrom
beta
Jul 18, 2026
Merged

release: merge beta into main (roadmaps 5-9 + mobile/ui/context-menu fixes)#25
AlexZ005 merged 250 commits into
mainfrom
beta

Conversation

@AlexZ005

Copy link
Copy Markdown
Collaborator

Merge the beta integration branch into main (262 commits, no divergence — main fast-forwards). This lands every roadmap and fix accumulated since d335d1a.

Highlights

Roadmap #9 — release runway

  • B1: packs-view .zip drop (non-zip rejected) + GLTF selection-only export with warnings
  • B2 (VR): 120Hz request, hands↔controllers switch fix, cuboid peer hands, pinch-hold radial opener
  • B3: .tpscene first-class scene format + export-settings cog (JSON demoted)
  • B4 (flow stage 1): NODES.md audit + edge-id divergence fix, typed socket colors, info/settings tabs, adjustable node params, custom-node input sockets + deterministic edge pruning, Map Range / Select nodes
  • B6: docs in the separate theprototype-docs repo

Roadmaps #5/#6/#7 — VR mesh-edit/face-edit, controller-handedness fixes, world-grab presence, Explorer window chrome (WindowShell), VR peer approve/deny, face polygon-select + multiselect, VR Tools radial + box-select, Explorer packs + 3D preview, articulated peer hands, per-peer network-quality dot.

UI / mobile pass (this session)

  • z-index tiers reworked (modal/toast/menu above ad-hoc chrome; backdrop-filter containing-block fixes)
  • mobile "+" HUD button + canvas long-press open the viewport menu; shared object context menu
  • top-bar responsive (Connect full-width bar on narrow); context menus cap + scroll + per-submenu flip
  • Flow/Explorer dock highlight coordination; Flow "+" tab menu + Flow Code (JSON) window; hide-tab
  • context menus + Connect bar stay fully on-screen at 360px; the "+" HUD shows on all widths with its menu anchored to it

Verification

  • npm run build green
  • npx svelte-check holds the baseline (502 errors / 77 warnings)
  • Playwright e2e suites pass (per-feature suites shipped with each phase)

Merge strategy: use a merge commit (not squash) so the per-phase commit history lands on main intact.

AlexZ005 and others added 30 commits July 7, 2026 21:41
- fix node inputs (slider/color/select) being reset by store round-trip:
  one-way data flow via setNodeData instead of bind:value
- fix peer handshake being dropped: send on connection open event
  instead of a fixed 500ms delay (channel can take longer to open)
- right-click context menu: create nodes from grouped submenus,
  disconnect/delete nodes, disconnect edges; Delete key also works
- node catalog with Input/Scene/Animation/Effects groups
- animation runtime (shake, spin, bounce, orbit, pulse, blink) driving
  scene objects via objectselector connections, runs with drawer closed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r toasts

- flow drawer and object list visibility moved from DOM classList hacks
  to stores (flowGraphClose, objectListClose) so they can be orchestrated
- hidePanels/restorePanels helper in appStore with a single snapshot slot
- settings modal hides all panels (incl. chat and flow) and restores on close
- spectate mode hides editing panels, keeps chat and flow, restores on exit
- peerjs error/disconnected handlers with user-facing toasts and up to
  3 reconnect attempts to the signaling server

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- central shortcut registry in src/lib/shortcuts.js binds the keys and
  renders the Settings -> Shortcuts section from the same source
- W/E/R switch transform modes, O/N/C toggle object list/node editor/chat
- guards: inert while typing in inputs, in play mode, or when settings is
  open (except Ctrl+/)
- Ctrl+/ opens settings with the shortcuts section expanded
- keycap hints in the bottom nav tooltips

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- shared selection logic extracted to src/lib/objectActions.js (lock-aware,
  gizmo attach, properties drawer refresh) and reused by the object list
- clicking an object in the viewport selects it (raycast against scene
  objects, resolves to the top-level group child); clicking empty space
  deselects and closes the properties drawers; drag gestures never select
- pointerup is captured on window because the Canvas wrapper swallows it
- VR: controller trigger press selects the pointed-at object
- F focuses the selected object: pans the orbit target and dollies to a
  framing distance over 400ms, keeping the view direction

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- shared ContextMenu component (edge-aware flip, submenus, disabled items
  with tooltips); flow editor refactored to use it
- object list right-click menu: focus camera, duplicate, rename (inline
  input), show/hide, enable/disable flow effects; lock-aware disabling
- duplicate replicates with a lean message: peers clone their own copy of
  the source and apply the originator uuids (same depth-first order),
  cloned meshes get their own materials
- Ctrl+D duplicates the selected object
- per-object flow-effects mute list respected by the animation runtime
- drop .glb/.gltf/.json files anywhere on the viewport to import; other
  formats show a supported-formats toast
- toast container no longer blocks clicks on the area beneath it

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- right-click on the viewport (click, not orbit-pan drag) opens a menu:
  undo/redo, snapping submenu, focus selected, duplicate selected, grid
  toggle, screenshot; measure/align greyed out as to-be-implemented
- transform-only history (50 steps): one entry per gizmo drag captured
  via dragging-changed; undo/redo re-broadcasts the move to peers and
  explains itself with toasts at the limits; Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z
- snapping for translate, rotate AND scale with selectable steps,
  persisted in localStorage (snap to surface deferred, shown disabled)
- screenshot re-renders and downloads the canvas as png

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- pointer position broadcast in flow coordinates (throttled to 20/s),
  so every peer renders it correctly under their own pan/zoom
- PeerCursors overlay projects flow -> screen via the shared viewport
  store; arrow + name pill colored per peer (stable hash of the id)
- cursors vanish on pane leave (explicit message) or after 4s without
  updates (peer closed the drawer or disconnected)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Texture row in mesh properties: pick an image (downscaled client-side
  to 1024px webp/jpeg), thumbnail preview, remove button; replicates via
  objectParameters map messages and reaches late joiners through the
  existing GLTF full-object sync
- roughness/metalness (standard), shininess (phong) and wireframe
  controls replicate with a generic materialParam message
- material type switching now carries over color/texture/opacity both
  locally and on peers (receivers previously reset to a default material)
- shared stores get JSDoc types (fixes ~260 pre-existing check errors)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- importFile refactored into a per-format dispatch with a shared
  add-to-scene tail (select, gizmo, GLTF replication to peers)
- OBJ (no .mtl materials), STL (standard material), FBX (static meshes,
  corrupt/old versions surface a toast instead of failing silently)
- drag-and-drop and the Import button accept the new extensions
- opt-in window.__stores test hook behind a localStorage flag

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- animation time base switches to wall clock (wrapped daily) so all
  peers compute the same phase without any sync protocol
- Settings -> Scene: Sync animations checkbox (default on), persisted

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- primitives catalog drives the sidebar: Basics (Cylinder, Plane, Torus,
  Capsule) join Cube/Cone/Sphere, plus a Building blocks group
- custom geometries Wedge, Stairs, Arch, Corner (Shape+Extrude based),
  centered on X/Z and resting on y=0
- registered in createGeometry so the replicated /create command builds
  identical shapes on every peer

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- dropToSurface: raycast down from the dragged object, rest its
  bounding-box bottom on the first surface underneath (other objects or
  the ground plane); world-space math so grouped objects behave
- surface snap toggle in the viewport Snapping submenu (persisted);
  applied during translate drags except deliberate Y-axis lifts, peers
  receive the snapped positions
- Align to ground un-greyed: one-shot drop, replicated and undoable

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- moveObjectToGroup shared action (group uuid / up / root) with cycle
  guards; root moves replay one replicated up-hop per level because the
  root group uuid differs per client
- object rows are draggable (locked rows excluded); group rows highlight
  and accept drops; dropping on the list header moves back to the root
- Properties Move to group select now uses the same shared action

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- vrhands message: world-space controller poses broadcast from inside a
  VR session (same movement-threshold pattern as the camera stream),
  active:false sent on session end
- peers render two colored controller markers with an aim pointer per
  VR user; markers clear on session end and on disconnect
- manual headset check pending; rendering path verified by injecting
  vrhands messages in a two-peer test

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- per-object edit mode (object list / viewport menu / Tab; Esc exits):
  instanced vertex handles deduplicated by position so corners never
  tear, dragged with the regular gizmo via a proxy object
- wireframe overlay while editing; object locked for peers meanwhile
- verts message replicates handle moves live (throttled) with a final
  unthrottled state on drag end; late joiners covered by the GLTF sync
- undo/redo gains pluggable entry kinds; vertex drags are undoable and
  replay through the same replicated path
- duplicated objects get their own geometry so editing a copy no longer
  deforms the original

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- peers render as configurable characters (body color, hat: cap/top
  hat/crown, optional avatar-photo face) instead of the gray sphere;
  the name label finally shows the username when set
- config rides userdata slot 5: replicated on join and on change,
  persisted in localStorage; the userData merge forwards it
- Customize Character (user dropdown) opens a modal with live-applying
  controls; late joiners receive avatars through the userdata handshake
- moveCamera/spectate untouched: the rig root keeps the peer id name

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- custom 3D quick-menu floats above the configurable menu hand (A/X
  toggles it): move/rotate mode, snapping, grid, undo/redo, spawn
  primitives in front of the user, swap hands, close; the other hand
  points and confirms with the trigger
- squeeze grabs the pointed-at object: Move follows the controller
  (grid snap honored), Rotate applies controller rotation, squeezing
  with both hands scales by controller distance; everything broadcasts
  the regular move message and records undo entries
- VR menu hand selectable in Settings (and from the menu itself),
  persisted; risky HTMLMesh floating panels skipped per scope decision
- manual headset checklist: menu on configured hand, tile actions,
  grab/rotate/two-hand scale replicate, selection via trigger

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
writeSelectedHandle returned a vector aliased to the shared tempVector,
which refreshHandleMatrix overwrote with the handle WORLD position right
before the value was broadcast and recorded in history. With the object
at the origin world == local and nothing showed; after moving it, peers
received vertex positions offset by the object translation - clicking a
handle (gizmo attach fires a change event) made the peer vertex jump and
drags continued from that offset.

- write path uses a dedicated vector and captures a plain array before
  any shared temp vector is reused (broadcast + undo history now local)
- gizmo attach no longer broadcasts: no-move changes are skipped

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- IndexedDB snapshots of the scene (GLTF json), node graph and camera:
  debounced 30s after changes + 3-minute interval + best-effort on
  unload; empty scenes never overwrite a good snapshot
- restore toast on boot when a snapshot exists and the scene is empty;
  restoring re-adds objects (replicating to connected peers) and the
  flow graph, and repositions the camera
- Settings: Autosave toggle + Clear saved session; annotations hook
  registered for the upcoming pins phase

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- measure mode (viewport menu, Esc exits): click two points (objects or
  the ground plane), a line with a billboarded distance label renders;
  local-only inspection tool
- camera bookmarks: save up to 5 views, recall from the menu or
  Shift+1..5 with the smooth flyTo tween (extracted from focusObject
  and reused), persisted in localStorage
- shortcut combos use event.code for digits so Shift+1 works across
  keyboard layouts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- mic toggle button (bottom right) streams to every whitelisted peer via
  peerjs MediaConnection; hold V is push-to-talk while the toggle is off
  (track-enable flip on a persistent stream, so it is instant)
- listeners need no mic permission: answering without a stream still
  receives audio; only whitelisted callers are answered
- speaking detection (AnalyserNode) drives a green ring under talking
  avatars; right-click a user avatar to mute/unmute them locally
- calls join new peers automatically on the connection handshake and
  tear down on disconnect

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- CLAUDE.md: architecture map, replication golden rules, hard-won
  svelte/three gotchas, verification requirements, workflow preferences,
  and the agreed modules/addons + code-node scripting baseline
- .claude/skills/e2e-verify: Playwright single-page and two-peer recipes
  (debugStores hook, PeerJS cloud flow, known flakes)
- .claude/skills/peer-feature: checklist for adding replicated features
  and the module/addon extension-point strategy

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add note from the object list or viewport menu: a billboarded numbered
  pin anchors to the object (object-local offset, follows moves); one
  note per pin with author and timestamp
- replicates live (annotation set/delete messages) and to late joiners
  via a getannotations handshake reply; pins die with their object
- clicking a pin flies the camera to it and opens the note editor;
  annotations persist inside the autosave snapshot

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- every light gets a type-specific helper plus a wireframe bulb pick
  proxy at the scene root (never inside objectsGroup, so saves and peer
  syncs stay clean); clicking the proxy selects the light and the gizmo
  drags it with the existing move replication
- Show light helpers toggle in Configure Scene, persisted
- WASD flies the camera on its horizontal plane, Q down / E up, Shift 3x
  (camera and orbit target pan together); inert while typing, in play
  mode, spectating or VR
- transform-mode hotkeys move from W/E/R to 1/2/3 to free the keys;
  tooltips and the shortcuts list updated

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- thin ray lines on both controllers, visible only in a session and
  shortening to the first hit
- the pointer-hand ray tints the pointed object emissive (restored on
  leave, suppressed while the quick-menu is open)
- thumbstick flick on the pointer hand snap-turns the rig around the
  viewer via reference-space rotation (movement math stays consistent);
  angle 15/30/45 in Settings, persisted

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Alt+click anywhere (object or ground), Ping this object in the list
  context menu, or VR thumbstick press on the pointed spot
- pulsing ring + beam + author label rendered at the world point on
  every peer (peer-hash color, same scheme as flow cursors), expires 4s

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- dragging/undoing/aligning an animated object now edits its animation
  base instead of fighting the tick (suspend while dragging, adopt the
  release spot; remote moves and undo rebase via notifyExternalMove)
- slider node scales its target (20 = neutral), switcher swaps the
  target geometry cube/pyramid - both deterministic on every peer
- peers exchange a node-graph hash every 10s and the poorer side pulls
  a snapshot on mismatch, healing missed nodedata/move messages;
  snapshots now update existing nodes in place

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AlexZ005 and others added 27 commits July 15, 2026 00:20
The vrhands broadcast labeled each pose by getController(slot).userData
.handedness (the 194/210 stamp). In hand-tracking sessions that stamp can be
missing, so BOTH left/right came out null -> the VR peer broadcast empty hands
and desktop peers rendered nothing (the VR user still saw their own hands via
threlte, hiding the bug).

- resolve a slot's handedness robustly: stamped value, else the live
  session.inputSource (positional/handedness match)
- isHandTracking now checks inputSource.hand through the same resolver
- e2e: vr-peer-hands-net (two-peer) proves a vrhands broadcast reaches a
  desktop peer and renders (25 joint spheres + a box) -- the cross-peer
  coverage that was missing

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gers)

Hand-tracking still fell back to the controller box on peers because the
capture read renderer.xr.getHand(SLOT).joints by slot index — the tracked hand
is not necessarily at that slot, so joints came back empty. Now read joints
from threlte useHand(left/right) stores (the SAME XRHand spaces it renders
locally), keyed by handedness, so a tracked hand at either slot is captured.

- broadcastVRHands fills each hand by handedness: articulated joints if that
  hand is tracked (wrist.visible), else the controller slot pose
- readControllerPose returns joints: [] (not null); peers branch on
  joints.length (Player), so a box shows for 0 joints and spheres for 25
- keeps the 194/210-safe controller labeling for the box path

Still needs the on-device check (no hand-tracking session in CI); the two-peer
+ render suites stay green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- CLAUDE.md: status = roadmap #7 shipped; architecture gains packs/PACKS.md,
  ModelPreview, networkQuality; gotchas add Threlte oncreate ref-capture (ref
  passed directly, not destructured) + reading WebXR hand joints via threlte
  useHand by handedness
- e2e-verify skill: __stores adds networkQuality/packs/snapping/flowSockets;
  bare dynamic import unresolvable in page eval (build zip in Node); cross-peer
  hand test recipe; param JSDoc ignored in Scene.svelte (use default params)
- peer-feature skill: the deliberately-local do-not-replicate pattern
  (networkQuality, pack library, local prefs)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- P1: double-clicking an object item always opens the 3D preview popup; the
  enable3dPreview toggle now gates only the inline Properties preview
- P2: cache each pack item resolved thumbnail URL (localStorage), so switching
  packs no longer re-probes webp/png/screenshot or flashes; dropped on remove
- P3: importPackZip descends a single wrapper folder (GitHub Download-ZIP shape,
  metadata/ + assets/ inside)
- P4: single-click the Packs root shows a grid of pack cards; double-click
  expands the tree; clicking a pack (card or tree row) opens its items
- P5: right-click a pack -> Delete (imported) or Hide (built-in, reversible via
  Settings show-hidden) + Attribution; built-ins are never hard-deleted
- P6: right-click empty packs grid -> Import pack (.zip) or Load pack from URL
  (.zip or a GitHub repo link via codeload; CORS-gated, graceful failure)
- e2e: packs (thumb cache + wrapper-zip), packs-explorer (click model + menus +
  place), model-preview (popup with toggle off)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…menu

- 2.1 target refresh rate: applyVRFrameRate reads supportedFrameRates on
  session start and requests auto(max)/90/120 per the vrTargetHz setting
  (gated on supported rates - bad values reject a promise threlte cannot
  catch). noVR Settings select (Max/90/120) + VR panel cycling row. Root
  cause of the stuck 90Hz: <XR> never received a frameRate.
- 2.2 hands<->controllers switch now reaches peers: the !moved && !hasJoints
  gate ate the switch-back message; shouldSendHands forces a send when the
  representation flips (last-sent joints.length per hand)
- 2.3 cuboid-bone peer hands: handBoneSegments builds ~24 box segments from
  the 25 wrist-local joints; peerHandStyle setting (hands default | spheres),
  LOCAL pref, rows in noVR Settings (under Hold to move vertex) + VR panel
- 2.4 pinch-HOLD (>=500ms) on the menu hand toggles the radial (hands have no
  B/Y button); the release-select is swallowed so it cannot click a sector
- e2e: vr-hz-pinch (rate picker + fallback, rep-flip decision, bone math,
  pinch state machine); vr-peer-hands(+net) updated for the style setting
- roadmap-9 plan files: pending/opus-b1-save-packs-fixes, pending/b5-network-
  stress (skipped), backlog flow-stage-2 entries, overview pointer

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- .tpscene = the session .zip bundle as a first-class save format:
  session.json (objects/flow/annotations/camera) + assets/<hash> (scene
  manifest binaries) + NEW packs/ section (imported packs metadata + item
  blobs, content-hash restored with item-id remap + re-registered on import)
- exportSessionZip gains include options {assets, packs, flow}; flow OFF
  strips nodes/edges; buildSessionPayload exported
- fileHandler: save(tpscene) downloads the bundle honoring the export
  settings; load() applies .tpscene via importSessionZip + requestLoadSession
- Sidebar: format row is now [GLTF | Scene | cog]; the cog opens an export
  settings modal (include assets/packs/flow + Show JSON toggle) - JSON is
  demoted behind the toggle (default off), Scene is the default format
- e2e: tpscene (bundle contents, checkbox matrix, pack restore round-trip,
  cog UI); sidebar-reorg updated (Packs now opens the packs grid, not the
  old 126 library drawer)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s (B4.1)

- NODES.md: the 34-type audit table (verdicts OK/FIX/DOC) + new-node
  recommendations (clamp/map-range + select land in 4.6; split/join, delay,
  text, keypress recommended for a user pick)
- edge ids now include handles: e-<src>.<h>-<tgt>.<h>. One source wired into
  both a+b of a node used to collide ids, the peer dedupe dropped edge #2 and
  the graphs diverged permanently (nodesync loop, never converging)
- evalNode cycle guard is PATH-based: the global seen set made a source
  feeding two inputs of one node evaluate once (second input read undefined
  and used its fallback) - found by the new suite
- distance/proximity accept wired vector3 LITERALS as world points (the
  coercion matrix allowed it; the runtime returned 0/false)
- e2e: flow-audit (distinct a+b ids, 2+2=4 through both edges, 3-4-5
  vector3 distance); all flow regressions green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Socket.svelte: shared wrapper around the xyflow Handle that paints each
  socket with its TYPE color (flowSockets.typeColor - defined in 165, unused
  until now): source = outputType(node), target = inputType(node, handle)
- all 33 handles across 21 node components migrated (mechanical swap);
  unmigrated/module handles keep the --node-accent fallback via flow.css
- an incompatible drag target reads RED (xyflow stamps valid only when
  isValidFlowConnection passes - pure CSS on connectingto:not(.valid))
- socket type -> color legend in the Flow properties panel (Graph view)
- e2e: flow-socket-colors asserts computed handle colors per type + legend;
  all flow regressions green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- the Flow right panel gains Explorer-style tabs: info = the SELECTED node
  parameters (editors live here, cards stay compact); settings = graph
  settings + node name/note (unchanged, default tab)
- slider: adjustable min/max (node data, catalog-seeded, runtime CLAMPS so
  stale values cannot escape the range); card readout shows the raw value
- switcher: adjustable items list (add/edit/remove rows in the info tab,
  fresh-array setNodeData writes) and becomes a REAL number source outputting
  the selected index (OUTPUT.switcher=number, sourceValueTypes); the legacy
  cube/pyramid geometry swap keeps working via items[index] with a shape
  fallback for saved graphs
- number: step editor in the info tab (was stored but uneditable)
- e2e: flow-adjustable-params (clamp semantics, index output, legacy shape,
  info editors write + BROADCAST nodedata, card re-renders a grown list);
  all flow regressions green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- CustomNode renders one target Socket per def RANGE param (the runtime
  already resolved wired inputs via resolveInputs - only the sockets were
  missing, so custom-node params were unwirable)
- pruneCustomNodeEdges(defId): editing a def to remove a param drops edges
  into the now-gone socket. Runs in applyNodeDef (every peer prunes
  identically - an applier-side invariant, never a broadcast) AND as a
  post-pass in applyNodesSnapshot so a stale drift-heal snapshot cannot
  resurrect dangling edges
- customNodes + nodesHandler exposed on the __stores debug hook
- e2e: flow-customnode-io (sockets per param, prune on def edit, socket
  disappears, stale-snapshot resurrection blocked)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- maprange (Logic): remap a from [inMin..inMax] to [outMin..outMax] with an
  optional clamp - the missing glue between free-range sources (time,
  distance, counter) and bounded effect params
- select (Logic): outputs a when index < 0.5 else b - pairs with the
  switcher-as-number output and compare/gate booleans (previously only a
  script node could choose between two wired values)
- registered across catalog, component map, typed sockets (number in/out),
  valueTypes + evalNode; NODES.md rows added
- e2e: flow-new-nodes (remap math, clamp on/off, wired switcher index drives
  select, card + socket counts)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Found while writing the node docs: the typed-socket rules rejected wiring an
On Click (event) output into the Object Selector (effect) input - the ONLY
sink through which fireObjectClick can act on the scene. Saved graphs worked
(edges are not re-validated) but the connection could not be drag-authored.
canConnect now allows event->effect explicitly. NODES.md corrected (onclick
verdict; script/customnode outputs are effect, not number).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
B1.1: dropping a file into the Explorer Packs view now routes a .zip through
the pack importer and rejects non-zip with a toast, instead of importing with
a bogus folderId (orphaned/invisible). Scene (derived, read-only) views also
reject drops. Library/prefab drops unchanged.

B1.2: GLTF save exports the SELECTION (primary selection + multi-select set),
not the whole scene. Nothing selected shows a warning toast with an Export-all
action. JSON save keeps its whole-scene behavior. exportGltf/selectedRoots
extracted; parkAnimatedAtBase preserved in all paths.

- e2e: packs-drop (zip imports, non-zip rejected, library drop still works);
  gltf-export-selection (warning toast on empty selection, 1-mesh export for a
  single selected object via the download)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- the modal/toast z-tiers were 50/60, but the avatar/peers popover (Users,
  ~996-998) and Connect (300) live outside the scale, so flowbite modal X
  buttons were covered. Raise --z-modal to 1100 / --z-toast to 1200 and map
  flowbite dialog + backdrop onto the modal tier (unlayered CSS beats the
  hardcoded z-40/z-50 utilities; still below ThemedSelect 9999 so in-modal
  dropdowns work)
- export-settings panel was a fixed child of .app-sidebar, whose backdrop-blur
  creates a containing block -> it centered on the ~200px sidebar and spilled
  off the left edge (8-bit theme hid it via a wider sidebar). Rendered at the
  component root now, on the modal tier, max-w-[92vw]
- e2e: modal-layering (settings modal z>=1100; export settings centered + fully
  on-screen)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- mobile + HUD button (bottom-left, MobileAddButton.svelte) opens the same
  create/context menu as a right-click; a canvas touch long-press (~500ms)
  opens it at the touch point. Scene exposes the opener via viewportMenuOpener;
  openViewportMenuAt is the shared entry (forceEmpty -> always the create menu)
- object context menu is now one shared builder (objectMenu.js
  buildObjectMenuItems): the direct object menu (Controls) and the empty-space
  menu's submenu (ViewportMenu) expose the SAME actions (they had drifted). The
  submenu title is a fixed Selected (object names get very long); pingObject
  moved into ping.js so the shared builder needs no THREE import
- object-list window drags/resizes via pointer (was mouse) so touch works;
  drag/resize handles get touch-action:none
- narrow screens: the + and the chat/mic stack lift above the centred
  Controls pill (media query) so the pill stays one line; the + shows only on
  touch/narrow. mic button gets an id for the reflow
- e2e: mobile-hud (opener, parity set, pointer drag); context-menu-v2 updated
  for the Selected label + full submenu

Note: Scene gains 2 implicit-any touch-event params (this file treats the DOM
canvas as any; no JSDoc form is honored) -> svelte-check 502, the documented
baseline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…able)

A window docked to the LEFT sat at x:0 on the z-drawer tier, but the app-sidebar
floats above it (z-hud) — on a narrow screen the docked panel is thin and its
topbar dock/close buttons ended up under the sidebar. When the menu is open, a
left-docked panel now insets past the sidebar (mirrors the right-side drawer
offset); re-applied next frame since the sidebar mounts after the store fires.

- e2e: dock-sidebar-inset (menu closed -> flush at 0; menu open -> left clears
  the sidebar's right edge)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Connect (approved revision): moderate corners (not a full pill); a fixed-size
floating bar centred on top for wide screens; it never shrinks.

Narrow screens:
- Connect becomes a full-width bar stuck to the top edge (the input flexes so the
  Connect button never runs off-screen); the logo + peers/profile chrome drop to
  a second row below it, with a clear gap
- neither Connect nor the Controls pill shrinks with window width

Z-order + menu:
- new --z-menu tier (above Connect 300 and toasts 1200): the logo/burger + its
  sidebar now open ON TOP of Connect and toasts (were behind them)
- opening a modal from the menu (Modules/Sessions/Settings) closes the menu so
  the top-most menu can't cover the modal
- the logo menu touch-scrolls on short viewports (no visible scrollbar)

Popups:
- export-settings opens anchored BELOW-RIGHT of its cog (shows the relation),
  clamped to the viewport instead of centred
- the connected-peers list pins to the viewport on narrow screens so it can't
  spill off the left edge

- e2e: modal-layering (export anchored to the cog, on-screen); sidebar-reorg
  (menu closes when it opens a modal)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Affects every menu (viewport +, object, Flow, Explorer, Users — one shared
ContextMenu):
- a too-tall menu/submenu caps to the viewport and scrolls vertically with a
  visible slim scrollbar (.ctx-scroll); never scrolls horizontally
- each submenu decides its own flip in openSubmenu from its row rect (open
  left if it would cross the right edge, up if more room above) instead of
  inheriting the root click flip, so deep chains no longer march off-screen
- max-height is the available space in the chosen direction, so the box always
  fits; fixed submenus still escape the root scroll box (no x-bar)
- e2e: context-menu-overflow (tall menu caps + scrolls + on-screen; right-edge
  submenu flips left); context-menu-v2 updated (vertical scroll now allowed,
  horizontal + transform still forbidden); CLAUDE.md note updated

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hable)

The undocked Flow window persisted at 760px wide with no clamp on load or
window resize, so on a narrow (mobile) viewport its header Dock/X buttons ran
off the right edge (the window is overflow-hidden, so they could not be
scrolled to). Now winW/winH clamp to the viewport on init + on resize, and the
corner-resize minimum drops to 280 so the window can shrink below a phone width.

- e2e: flow-window-mobile (over-wide preset clamps on a 420px viewport, Dock
  button on-screen, re-clamps on resize)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The palette used draggable + dragstart/dataTransfer, which mobile browsers do
not synthesize from touch, so holding a palette item did nothing. A tap/click on
a palette item now adds that node at the flow pane centre (it can then be dragged
on the canvas, which touch supports). A real mouse drag fires no click event, so
desktop drag-to-place is unchanged.

- Nodes.addNodeAtCenter reuses addNode + screenToFlowPosition (handles the
  customnode: prefix like the drop path); passed to the palette as onPick
- e2e: flow-palette-touch (tap adds a positioned node); palette regressions green

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- CLAUDE.md: status = roadmap #9 shipped (B1-B4/B6 + Opus UI/mobile tail);
  architecture gains flowSockets/Socket + NODES.md, objectMenu shared builder,
  custom-node input sockets, .tpscene as the session-bundle format; new gotchas:
  backdrop-filter is a containing block for fixed children, the z-index tier
  table (modal 1100 / toast 1200 / menu 1300 above the ad-hoc chrome), and the
  mobile/touch rules (no right-click or HTML5 DnD -> long-press + tap-add,
  pointer not mouse for window drag, clamp floating windows). Retired the blanket
  keep-as-is note for Connect/Controls/Toasts (revised for responsive).
- e2e-verify skill: __stores adds customNodes/nodesHandler/objectMenu +
  viewportMenuOpener; download capture, OS file-drop via dt.items.add,
  responsive/setViewportSize + dispatched-contextmenu testing, and the
  git-stash exact-delta trick (machine output double-escapes paths). Baseline 502/77.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Connect: the peer-ID input now shrinks (down to a min-width) so the Connect
  button stays visible when the row is tight; the pill reserves room for the
  logo/profile so it never slides under them (input flexes instead). Button no
  longer disappears at ~640-760px widths.
- object-list window: clamp its persisted rect to the viewport on open + on
  window resize (was reopening partly off a narrow screen with clipped
  subgroups and no scroll — same bug the Flow window had).
- ContextMenu: portal the menu + backdrop to <body> so it escapes a host
  window's stacking context (the Flow editor's docked/floating window trapped
  its context menu below other windows); its z-1000 now ranks above windows.
- e2e: mobile-ui-fixes (object-list clamp + ctx-menu portal); context-menu +
  object-list-drag regressions green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Flow + Explorer share the bottom dock when both are docked (only the active one
shows). The toolbar icons now highlight the panel that is actually VISIBLE, not
just open:
- flowVisible/explorerVisible derive from flowGraphClose/explorerClose +
  dockShared + bottomDockActive
- clicking Explorer opens it AND makes it the shown dock occupant (Flow
  un-highlights); clicking Node editor shows Flow (Explorer hidden but still
  open, so windowed Flow + docked Explorer can both show); clicking a shown
  panel again closes it
- e2e: flow-explorer-dock (both-docked highlight, switch active, close-on-reclick)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Flow tab strip now shows [Node editor][+] (Explorer removed - it is toolbar-
  controlled since the dock-highlight change); the + (docked strip + floating
  header) opens an add-menu: Flow Code (works) + Animation (soon, next batch).
- FlowCode.svelte: an editable JSON view of the flow graph (ex-backlog). Seeds
  from the live graph; Apply parses + REPLACES it (delete removed + snapshot the
  rest) locally and broadcasts (nodedelete/edgedelete + nodes) so peers converge.
  A floating window (tabbable) - drag to group with Flow, tear a tab off to detach.
- TabStrips: right-click a tab -> Hide tab (closeMember) via the portaled ContextMenu.
- e2e: flow-code (+ menu opens the window + seeds the graph JSON);
  window-tabs / docking / flow regressions green.

Animation window (local-only, no blending per the fork) is the next batch and
will join the + menu when built.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Submenu placement used a width GUESS to pick left/right, so on a narrow screen a
suboption could still run off either edge. Now the submenu MEASURES itself
(placeSubmenu action) and positions against the row: prefer right, flip left if
it would cross the right edge, and if it still will not fit, clamp it fully into
the viewport - even if that covers the parent menu (better than off-screen).
Vertically it aligns to the row then clamps; too-tall submenus still cap+scroll.
Fixes both the viewport and Flow context menus (shared ContextMenu).

- e2e: context-menu-overflow gains a 360px-wide case asserting the submenu
  clamps on-screen when neither side fits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- ContextMenu root now measures + clamps fully into the viewport (place
  action) instead of guessing flip direction, so a menu opened near the
  right edge on a narrow screen (e.g. 360px) no longer runs off-screen
- ContextMenuItems submenus keep the measure-and-clamp placement; dropped
  the now-unused flipX/flipY props from both components
- Connect bar spans the full width at <=640px (max-width:none on the full
  bar) instead of inheriting the wide pill reserve-room cap, which had
  squished it to ~80px at 360px
- extend context-menu-overflow e2e: root menu opened near the right edge
  on a 360px viewport clamps on-screen

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- the create/context + button (MobileAddButton, bottom-left) was gated to
  touch/<=820px; show it on every width (user request) so a fine-pointer
  desktop gets it too, alongside right-click
- the + now opens its menu anchored to the button (place flips it up off
  the bottom edge) instead of at screen-centre; it still rays a new object
  into the middle of the view
- viewportMenuOpener gains optional menuX/menuY (default to the ray coords)
  so the menu position can be decoupled from the ray point
- extend mobile-hud e2e: button visible on a wide desktop; menu opens at the
  button anchor while the ground point still comes from the ray coords

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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