Skip to content

41-controls: G23 toolbar order survives a reload + G1 Edit positions - #360

Open
AlexZ005 wants to merge 14 commits into
feat/41from
feat/41-controls
Open

AlexZ005 wants to merge 14 commits into
feat/41from
feat/41-controls

Conversation

@AlexZ005

Copy link
Copy Markdown
Collaborator

Roadmap 41, lane 41-controls: G23 (toolbar order breaks icons after a reload) and G1 (Edit positions).

G23 — the reload bug

Cause: the page is prerendered with the default toolbar. Svelte 5 hydrates a keyed {#each} positionally, so a stored custom order was hydrated onto the default cells' markup. Titles and tints were repaired by their effects, but each cell's <svg> kept the paths of whichever button sat in that slot by default.
Fix: Controls hydrates with the default record and applies the stored one right after mount (same for the viewport width). The order model moved into a pure leaf, src/lib/toolbarLayout.js. It provides stable ids and a never-throwing, idempotent normalizeLayout (drops unknown ids and duplicates across regions, closes holes, clamps). A new seen list means a removed default stays removed. Legacy-row migration now applies only to pre-41 records. Every write goes back through normalizeLayout.
Proof: vitest toolbarLayout, and e2e toolbar-reload-order (real right-click → Move left ×N, then reload). On feat/40-int's Controls.svelte the suite has 2 FAIL (Node editor, Animation, Pivot and Interact draw the wrong icons); with the fix it is green.

G1 — Edit positions

  • Right-click a toolbar button (or long-press it on touch) → Edit positions, the first Toolbar row. It is also in Customize.
  • The four round corner buttons ("+", AI assistant, chat, mic) are now roster entries in two corner stacks, max 3 each. Their ids, titles and handlers are unchanged.
  • Drag to rearrange, with pointer events, so mouse and finger work the same.
  • A red − on every placeholder except Play.
  • A green + wherever a place is empty. It opens the unplaced Customize buttons, never Play.
  • A round green ✓ at the bar's top right applies; Escape or a click outside cancels.
  • Keyboard: the arrow keys move the focused item, Delete removes it.
  • Long-press uses its own timer, so it also works on iOS.
  • The draggable face of each placeholder is a real <button>. Otherwise Chrome's touch target adjustment snapped a finger onto the small "−".
  • Folding to the phone shell cancels a draft in progress; the corner stacks are hidden under the phone shell.

Proof: e2e toolbar-edit-positions, all passing. It covers desktop mouse and keyboard in dark and light, the OPPO N6 unfolded size with real CDP touch, and the fold transition. Screenshots are in ~/.code/lanes-30/after-41/41-controls/.

Merge notes

  • Merged origin/feat/41-modals (z-index scale). The corner buttons it moved to --z-chrome are now Controls roster entries; the stacks use var(--z-chrome).
  • MobileAddButton.svelte and AiHudButton.svelte are deleted. VoiceChat's mic button moved into Controls.
  • ui.css: the per-id corner rules for narrow screens became one .hud-stack rule. phone.css gains one selector.
  • svelte-check: 300 errors / 39 warnings. The baseline is ratcheted 40 → 39 warnings. check:tokens clean, vitest 1977/1977.
  • Pre-existing reds, identical on feat/40-int: layout ("new height persisted"), roadmap-13-ui-polish (appNotice). After the 41-modals merge, roadmap-13-ui-polish also stops at #logo-menu covered by the Settings dialog. That is identical on pristine origin/feat/41-modals (its "modals cover the logo" change), so the suite needs updating on that side.

🤖 Generated with Claude Code

AlexZ005 and others added 14 commits October 11, 2026 02:44
…ogo, What's new tops every button

- src/styles/ui.css: ONE ordered scale, lowest first: --z-canvas < --z-canvas-overlay < --z-chrome
  (FABs, Controls pill, side drawers) < --z-selection (phone strip) < --z-dock < --z-sheet <
  --z-window (40..44) < --z-phone-bar < --z-hud < --z-side-panel < --z-chrome-top (logo, undo/redo,
  Connect, notes/notifications/peers/profile) < --z-popover (main menu, profile, Add, search) <
  --z-toast-low < --z-modal < --z-onboarding (What's new, Welcome, tours) < --z-portal (selects,
  context menus, drag ghosts) < --z-toast. Old names stay as aliases (--z-drawer/-bottom/-viewport/
  -menu) so a branch written before the scale still resolves.
- every page-level hard-coded z-index moved onto it (80 sites: Users 996-999, Connect/PhoneShell
  300, Add/ObjectSearch 999/1000, ThemedSelect 9999, drag chips 1400, Sheet 60/61/42/43,
  PhoneShell 36-44, HUD 38/46, FABs z-30, Inspector z-50, ...); in-component stacking stays < 30.
- G3b: the logo is --z-chrome-top, so Settings and every app modal cover it (was --z-menu 1300 over
  --z-modal 1100); What's new is --z-onboarding at every width (no focusStack, no wn-sheet hack).
- G20 (scale half): the phone selection strip sits on --z-selection, under docks and sheets, and no
  longer rises with the bar over an open main/profile/Add menu.
- scripts/check-zindex.cjs + `npm run check:zindex` + a CI step: a page-level literal (>= 30)
  fails; `z-ok: <reason>` marks the numbers code reads back (windowFocus, docking).
- mobile-ui-fixes: the wn-sheet checks become "the changelog outranks the logo at every width".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…hen the stored record) + the order model as a pure leaf

- root cause: the page is prerendered with the DEFAULT bar and svelte 5 hydrates a keyed
  {#each} positionally, so a stored custom order hydrated onto the default cells' <svg> paths
  (titles/tints repaired by their effects, icons not) — "move far-right buttons to the leftmost,
  reload, icons break". Controls now hydrates with the default record and applies the stored one
  right after mount; viewportW starts at the server's width for the same reason
- $lib/toolbarLayout.js (imports nothing): normalizeLayout (unknown ids, non-strings, duplicates
  across bar/corners, hidden-not-in-order, spacerIndex holes, never throws, idempotent), visualRow,
  moveCell/hideButton/showButton/swapCell, `seen` so a removed default stays removed (pre-41
  records keep the old append rule), legacy-row migration only for pre-41 records, and the G1
  placements API (left/bar/right regions, max 3 per corner, arrow-key steps)
- every setLayout write goes back through normalizeLayout
- vitest toolbarLayout (17), e2e toolbar-reload-order (15, real right-click Move left + reload)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ls, toasts stay for outcomes

- inventory: all 826 toast call sites on feat/40-int classified (STATUS-41-modals.md) — 12 become
  modals, 5 reconnect toasts go to G21, the rest stay toasts (passive outcomes, failures, peer requests).
- SessionPrompts.svelte: "Restore previous session?" and the too-large-scene ingest gate were
  STATE-MIRRORED sticky toasts in Toasts.svelte; now compact true modals (ConfirmModal's look) open
  exactly while their store holds a value. X / Esc / outside = Dismiss (restore) or Cancel (gate).
- confirmDialog (showConfirm / showChoice) for the one-shot questions: Delete flow, delete a group and
  its children, update / delete a prefab, rebuild discarding vertex edits or a sculpt, leave a session
  to join another, join on another server, export everything with nothing selected, create a local copy.
- peer REQUESTS (load a session, take control, see a private scene) stay cards like approvals.
- suites: toast-to-modal (new; counterfactual red on feat/40-int); gltf-export-selection,
  overload-guard, settings-autorestore-colors, touch-actions, ingest-gate, invite-link-live read the
  dialogs instead of the toasts.
- z-scale-layering: the desktop What's new window is clamped below the top bar, so the logo check
  applies only where the window overlaps it.

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

- ui/SettingRow: the row is an inline-size container (`container: sr`), its grid moves to `.sr-body`.
  Under 560 px of ROW a wide control (flagged `wide`, segmented, slider, or two pieces such as a
  control and its status badge) drops under the label at full width; under 420 px every control but a
  lone switch does. Replaces the 640 px VIEWPORT rule, which never fired on an unfolded phone or in a
  narrow desktop window — reduced-width.jpg's "Voice typing provider" / "Speech / to / text" column.
- containment makes the row the containing block of fixed descendants; nothing in a row is
  (ThemedSelect portals to <body> in the non-modal Settings dialog).
- settings-narrow (new): every page at Settings widths 360 / 540 / 700 / 900, the N6 folded and
  unfolded, and desktop — every row keeps >= 180 px of text beside its control or spans the row,
  no control overflows; SHOTS=<dir> writes one screenshot per page and width.

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

- $lib/connectionStatus.js: ONE {tone, words} — grey idle, yellow (pulsing) connecting / dialling /
  reconnecting, green connected (stays green through a signaling outage, the words say it), red past
  GIVE_UP_ATTEMPTS (6, ~1 min on the 27-F backoff). Pure connectionStatusOf + vitest.
- peerHandler: the "Lost the peer server", "closed the link", "Cannot reach … Retrying" and
  "Reconnected" toasts are gone (a phone tab switch raised three). Past the threshold ONE
  notification-centre entry per outage; the fallback-to-public notice is a notification too; the
  store ticks on open so the dot turns back. Retry behaviour is unchanged (never gives up).
- phone chip (#ps-connect-chip): dot + chevron (+ mic in a call), no text — fits beside the bell and
  the peers (space-connect-svelte.jpg); title + aria-label carry the words.
- desktop bar: a status dot (#connect-status) leads the bar in place of the "Reconnecting · 12 s"
  badge (its tooltip keeps the elapsed time); the connected chip drops its text label.
- the cloud plugin mounts nothing into the pill any more (mountProfile / mountConnectDrawer) and none
  of its toasts are about reconnecting — no cloud change.
- signaling-reconnect: rewritten to the new contract (no toasts, dot tones, one notification) + a
  phone section (no text on the chip, a drop and return raise no toast).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…amed from --z-bottom)

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

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…us-warn fill token; --ink-warn is a text ink)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s cap), never return a pending confirm promise, object-delete reads the group modal

- settings-narrow (360/540/700/900) + settings-narrow-devices (folded/unfolded/desktop) share
  settingsNarrowShared.cjs; seven sizes in one file ran past run.cjs's 480 s kill, which surfaced as
  'Target page, context or browser has been closed'.
- toast-to-modal: requestDeleteObjectGraph is async now (it awaits the answer), so the evaluate must not
  return it — it waited for a click that never came until the runner killed it.
- object-delete: the group delete asks in a modal (G16).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…'s first BUTTON (the status dot leads now); toast-to-modal reads plain-string toasts whole

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ns join its roster

- right-click / long-press any toolbar button -> "Edit positions" (first Toolbar row; also in
  Customize). Placeholders outline + jiggle (none under reduced motion); drag reorders with
  pointer events (mouse or finger) along the bar and into the corner stacks, live; red "−" on
  every placeholder but Play; a "−" on the bar leaves a green "+" in its place, "+" at both bar
  ends and on every empty corner place opens the unplaced Customize buttons (never Play); max
  three per corner; round green ✓ at the bar's top right applies, Escape / outside cancels;
  arrow keys move the focused item (bar <-> corners at the ends), Delete removes
- the "+", AI assistant, chat and mic are roster entries now (ids/titles/handlers kept), stacked
  in #hud-stack-left/right; MobileAddButton/AiHudButton removed, VoiceChat's mic button moved
  into Controls; the bar-move clamp measures each corner button
- long press opens the toolbar menu by its own timer (iOS never fires contextmenu; Android's is
  de-duplicated); its click-swallow only eats clicks on the held button
- the draggable face is a real <button> so touch target adjustment cannot snap a finger onto the
  "−"; the gesture outlives the node a cross-region move re-renders; a finger drag arms no
  click-swallow
- folding to the phone shell mid-edit cancels the draft; the stacks hide under the phone shell;
  #sim-controls rides above the right stack's height
- suites: toolbar-edit-positions (new: desktop mouse + keyboard, unfolded touch via CDP, the fold,
  dark + light); controls-roster (+4 corner rows), toolbar-position (hide the right stack),
  roadmap-13-ui-polish (corner geometry, not bottom-* classes); vitest toolbarLayout (holes)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- the corner buttons 41-modals moved to z-(--z-chrome) are G1 roster entries now: the two
  deleted components stay deleted, VoiceChat keeps no mic button, Controls keeps the stacks;
  .hud-stack takes var(--z-chrome) (was 30) — the same tier 41-modals gave each button

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…n:click mic button moved into Controls' roster)

Co-Authored-By: Claude Opus 5.5 (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