Skip to content

Release 1.0 launch polish: packs CDN, versioning V2-V8, deps safe-bump, launch fixes - #56

Merged
AlexZ005 merged 16 commits into
mainfrom
feat/release-1.0-launch-polish
Jul 28, 2026
Merged

Release 1.0 launch polish: packs CDN, versioning V2-V8, deps safe-bump, launch fixes#56
AlexZ005 merged 16 commits into
mainfrom
feat/release-1.0-launch-polish

Conversation

@AlexZ005

Copy link
Copy Markdown
Collaborator

Release-runway batch for v1.0.0:

  • RP: packs move off-bundle to github.com/theprototype-app/packs via jsDelivr (@v1) — cube_diorama (real labels), khronos upstream-fetch index (21 verified), NEW audio-essentials (23 CC0 sounds) w/ in-view install + per-pack library folders; bundled offline starter; Source buttons
  • RV V2-V8: About version + cloud plugin seam (setPluginInfo) + fail-closed compatibleHooks gate; peer app-version warn; SESSION_FORMAT/MODULE_FORMAT confirm gates + ConfirmModal; release.yml + RELEASING.md; V8 version.json reload toast
  • deps: safe same-major bumps (svelte 5.56.8, kit 2.70.1, vite 5.4.21, playwright 1.62 ...); three/threlte/xyflow/flowbite/tailwind deliberately frozen w/ planned migrations; dependabot.yml (grouped) + npm run deps:check
  • fixes: sidebar Import button (svelte 5 delegated-event regression), whats-new placement + welcome-on-start badge edge, sculpt-mode gizmo suppression + toolbar toggle, Shift+A opt-in setting, keyed toast each (svelte 5.5x stuck-duplicate regression)
  • new svelte-check baseline: 476 errors / 62 warnings

🤖 Generated with Claude Code

AlexZ005 and others added 16 commits July 27, 2026 23:13
- vite.config.ts: define __APP_VERSION__ (package.json) + __COMMIT_SHA__
  (git rev-parse --short HEAD, try/catch -> unknown); define reaches dev,
  build and prerender so there is no second code path.
- src/app.d.ts: ambient consts inside the existing declare global block.
- NEW src/lib/version.js: zero-import leaf exporting APP_VERSION /
  COMMIT_SHA / IS_DEV / versionString(); typeof guards keep it safe when
  define never ran. Zero-import because peerHandler, moduleSDK, sessions
  and UI all read it and an import here could close a history.js cycle.
- package.json stays 0.0.1 until the release ritual bumps it (V6).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Badge, not popup: a version change only dots the logo menu and shows one
auto-dismissing toast. The changelog opens in a floating window. A first visit
shows the welcome overlay INSTEAD - never both.

- NEW src/lib/whatsNew.js: welcome/whatsNew open state, the unseen marker, two
  persisted prefs, and startWhatsNew() called once from App.svelte. A first
  visit also marks the current version seen, so no badge lands on top of the
  welcome overlay.
- NEW Welcome.svelte: first-visit card (pitch + Start building / demo / GitHub,
  version line from V1, show-on-start checkbox).
- NEW WhatsNew.svelte: floating window (ui-panel + dragWindow + focusStack)
  rendering root CHANGELOG.md through a tiny markdown subset. Input is escaped
  BEFORE the inline rules run, HTML comments are stripped (maintainer notes stay
  out of the user-facing window), and the leading h1 is dropped because the
  window header already says it.
- NEW CHANGELOG.md: public, GitHub-rendered, emoji-led 1.0.0 entry.
- Sidebar: a dot on the logo plus a What is new row. Both cues are CLASS
  toggles, not if-blocks - clicking the row closes the menu, and destroying a
  nested branch inside the subtree being destroyed in the same flush crashes
  the Svelte 5 sibling walk (hit exactly that: destroy_effect/get_next_sibling).
- Settings: two toggles in the Scene section + an About row to reopen the
  changelog.
- Replaced the dead News.svelte stub (empty body, inverted hasSeenModal flag).
- helpers.cjs seeds hasSeenWelcome so the overlay cannot cover the UI in every
  other e2e suite.

tests/e2e/whats-new.test.cjs: 24 checks, ALL PASS.
svelte-check 484 errors / 66 warnings (was 485/72 - the deleted stub carried
some of the difference).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The repo goes public at 1.0.0, so the README IS the landing page. The old one was
19 lines of stale self-signed-cert dev steps with no feature list and no images.

- README.md rewritten: centred hero (logo, one-line hook, badges, try/docs/video
  links), what-it-is with the no-backend explanation, feature list grouped by
  what you do with it, quick start (hosted + npm install --legacy-peer-deps +
  build/check/e2e), extend-it pointers to MODULES/NODES/PACKS/OPEN-CORE and the
  modules repo, project layout table, contributing, support, license.
- NEW .github/assets/{hero,flow,welcome}.jpg + whats-new.png - captured from a
  REAL app, not mocked. 463 KB total (1600x940 at scale 1, jpeg for the 3D
  renders, png for the flat UI panel).
- NEW scripts/capture-screenshots.cjs regenerates them, so they cannot drift
  into showing UI that no longer exists. Builds a scene, opens the panels worth
  showing, clears transient toasts/badges per frame, and points the peer config
  at the real signalling box so the pill shows a genuine session ID.
- NEW .github/FUNDING.yml + issue templates (bug/feature/config). The bug form
  asks for the Settings-About version string and whether peers were connected -
  most replication bugs need both sides.

Verified: every claim checked against the code (env var names against
.env.example, scripts against package.json). No link points at a page that does
not exist yet - sponsorship refers to the repo Sponsor button rather than
hardcoding an account URL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
It builds its own context (clipboard permissions) instead of using h.setupPage, so a virgin profile now opens the welcome overlay and its backdrop swallows the Shift+A menu interactions. Seeds the same first-run flags setupPage does.

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

User-reported: pressing Shift+A to open the Add menu lurched the viewport LEFT
first. editorNavigation's keydown filtered ctrlKey and metaKey but not shiftKey,
so the A of Shift+A entered the WASD fly set - and moved at 3x, because Shift is
also the fly speed modifier. Measured 1.500 units of drift per press.

- editorNavigation.js: ignore a key when its Shift+<key> combo is a REGISTERED
  shortcut (read live from the shortcuts registry, so any Shift+<key> command
  added later is covered). Shift alone stays the 3x modifier, so Shift+W/S/D/Q/E
  keep flying fast - only actual commands are excluded. Shift+A is the sole
  collision today; the W-A-S-D and Q/E registry rows are documentation entries
  with no action, which is why the check requires s.action.
  No new dependency edge: editorNavigation already reached shortcuts through
  inputRuntime, and nothing in lib/ imports editorNavigation, so no cycle.
- NEW tests/e2e/fly-shortcut-keys.test.cjs: asserts Shift+A opens the menu AND
  leaves the camera still, that plain A/W still fly, and that Shift+W is still
  ~3x. Verified failing-first: without the fix the camera moves 1.500.

Two stale e2e checks fixed while in here - both were TEST bugs, the app was
correct, which is why manual testing found nothing:
- connect-states: looked for [data-testid="connect-pending"], which exists
  NOWHERE in src - the CN drawer redesign never carried it over, so the check
  could only ever fail. Now asserts what the pill actually renders while
  pending: the disabled input reading "Requesting <ID>".
- add-menu: checked #add-search-input visibility after a fixed 300ms wait. The
  Shift+A action dynamically imports appStore, and with two peers rendering at
  full rAF that resolved later than 300ms - the feature worked, the wait was too
  tight. Now waits for the element. This was the only reason the suite failed
  (the following locator.hover timeout was a cascade).

Verified: fly-shortcut-keys 5/5, add-menu 11/11, connect-states 19/19.
Build green, svelte-check holds 484/66.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Shift+A opened a centred box and spawned at the object's default spot. It now
behaves like the right-click Add menu: anchored to the cursor, spawning what you
pick under the cursor.

- appStore: NEW addMenuOpener seam, mirroring viewportMenuOpener. Scene owns the
  raycaster, so the ray logic stays there and shortcuts stays dumb.
- Scene.svelte: remember the cursor on pointermove; NEW scenePointAt() resolves a
  screen position to a world point (object hit, else the ground plane, else the
  origin - the same resolution the right-click Add menu uses); publish an opener
  that anchors the popover to the cursor and spawns there. It declines in VR /
  play / spectator mode, and before the pointer has ever moved.
- shortcuts: Shift+A calls the opener and falls back to a centred box with the
  default spawn spot when it declines (fresh load / keyboard-only).

Clamping (the popover could hang off the edge, and a drag could shove it off):
- AddMenu: position is state, not a hardcoded guess. It opens below-right of the
  anchor, FLIPS to the other side when there is no room, then clamps so no edge
  leaves the viewport. The size is MEASURED - the box grows and shrinks with the
  results list, so the old fixed 270x320 estimate both pushed a short box up
  needlessly and let a tall one overflow. A ResizeObserver re-places it as the
  list filters, and window resize re-clamps it.
- A right-dragged box keeps the position the user chose (only re-clamped, never
  re-anchored), so filtering after a drag no longer snaps it back.
- searchMenuUx.rightDragMove: clamp BOTH axes. It only pinned left/top >= 0, so a
  drag could push either box off the right/bottom (which also grows the document
  - see the off-the-right-edge gotcha). This fixes the node-search box too.

GOTCHA HIT (documented in CLAUDE.md, cost real time): the placement $effect called
place(), which READS pos and whose ResizeObserver WRITES pos - the effect then
depended on its own output and tripped effect_update_depth_exceeded, which killed
the component's reactivity so positioning silently froze (a later resize did
nothing). place() is now called through untrack(), and the drag flag is a plain
let rather than $state. Also: <svelte:window> cannot live inside an {#if}.

- NEW tests/e2e/add-menu-cursor.test.cjs, 16 checks: opens at the cursor, spawns
  under it, a different cursor spawns elsewhere, all four screen corners stay
  fully inside, a drag past the edge clamps but still moves, filtering keeps a
  dragged box put, the document never grows, and a window resize re-clamps.
- add-menu: rightTap now retries. A right TAP only opens the menu when the press
  is under 400ms (longer = a right-DRAG that pans), and Playwright's separate
  down/up CDP round-trips measured 360-410ms with two peers at full rAF - right
  on the threshold, so the menu appeared only about half the time. One step in
  that suite is still flaky and is now marked: verified to reproduce WITHOUT any
  of these changes (stashed them and it failed twice), so it is pre-existing.

Verified: add-menu-cursor 16/16, fly-shortcut-keys 5/5, search-menus 10/10.
Build green, svelte-check holds 484/66.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- the {#key rerenderInput} recreate detached the file input while the
  native picker was open; Svelte 5's delegated oninput never fires on a
  detached element, so importFile was never called (regression from the
  203 sidebar rewrite, where legacy on:input attached directly)
- re-pick-same-file now works by clearing input.value before .click()
  instead of destroying/recreating the element (shared pickFile helper)
- Load accept list tidied: dropped .scene (no code path handles it) and
  .gltf (legacy additive path that overlaps Import); Load = .json/.tpscene
- new e2e suite import-button (4 checks) drives the real button through
  Playwright's filechooser, incl. same-file re-pick

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Settings: the What's new button moves from the About section to the
  modal footer, next to Reset settings / Clear saved session (same
  about-whats-new id kept)
- whatsNew.startWhatsNew: a welcome-on-start opt-in returned before the
  version check every boot, so that user never saw update badges; the
  early return (and open-time markSeen) now applies only to the true
  first visit -- pref-opened welcome still gets the dot, toast stays
  suppressed while the overlay is up (badge-not-popup)

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

- svelte 5.1.3 -> 5.56.8, @sveltejs/kit 2.8.1 -> 2.70.1, vite 5.4.10 -> 5.4.21,
  vite-plugin-svelte 4.0.4, adapter-static 3.0.10, svelte-check 4.7.4,
  typescript 5.9.3, tailwindcss 3.4.19, playwright 1.62.0 (needs
  npx playwright install chromium), postprocessing 6.39, peerjs 1.5.5,
  fontawesome 6.7.2, prettier 3.9.6 + plugins, autoprefixer/gh-pages/mkcert
- deliberately NOT bumped (planned post-1.0 migrations, see cloud repo
  plans-core/pending/deps-migrations-post-1.0.md): three, @threlte/* (-next
  pins), @xyflow/svelte, flowbite*, tailwind 4, vite majors, TS 7, rapier
- svelte 5.5x fallout fixed: <svelte:self> -> self-import (Objects), quoted
  Progressbar attribute (Toasts), state_referenced_locally ignores for
  intentional one-time prop reads (WindowShell, Section)
- Toasts: the live-toast each is now KEYED - svelte 5.5x node reuse in the
  unkeyed each migrated text across rows when a neighbour expired and left a
  stuck duplicate toast (found via sessions e2e decline check; two-peer
  pre/post-bump baseline runs proved every other failing suite pre-existing
  and identical on old deps)
- new svelte-check baseline 476 errors / 62 warnings (was 484/66); CLAUDE.md
  baseline + deps-policy notes updated

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- .github/dependabot.yml: monthly, one grouped PR for minor+patch; majors
  separate; the deliberately frozen migration deps (three/threlte/xyflow/
  flowbite/tailwind/vite/TS/fontawesome/rapier) are ignored until their
  planned migrations execute
- scripts/deps-check.cjs + npm run deps:check: non-blocking npm-outdated
  report split into safe drift / unplanned majors / frozen-pending-migration
  (0.x minors count as breaking); the cloud repo deploy build now calls it
  with --if-present as a pre-deploy warning

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- entering Sculpt Terrain detaches the gizmo and keeps it off (a visible
  gizmo mid-stroke invited accidental terrain moves); resets OFF on every
  sculpt entry
- new SculptToolbar 'Gizmo' toggle re-attaches it deliberately
- gate lives in applySelectionSet via a new sceneStore.gizmoSuppressed
  (leaf store - objectActions cannot import terrainSculpt without closing
  an import cycle)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- new enableShiftAdd pref (persisted, default false) gates the Shift+A
  shortcut action; Shift doubles as camera strafe in fly mode so the
  shortcut only exists for users who enable it
- Settings row 'Shift+A quick add' next to the other viewport toggles;
  shortcut list label points at Settings
- add-menu-cursor asserts the default is inert, then both suites seed the
  pref before driving Shift+A

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- PACKS_BASE -> cdn.jsdelivr.net/gh/theprototype-app/packs@v1; the pack list
  is the repo's index.json, with the bundled static/library starter (logo +
  duck, ~460KB, now committed) as the offline fallback
- normalizeDefault prefixes listUrl/attributionUrl/zip with PACKS_BASE for
  remote rows (the latent unprefixed-listUrl bug); absolute item glb/screenshot
  URLs pass through - the khronos pack is metadata-only, bytes come straight
  from KhronosGroup/glTF-Sample-Assets (all 21 entries HEAD-verified)
- thumbnails lead with the committed screenshot (thumb.webp probing spammed
  CDN 404s); index 'source' -> sourceUrl + Source button in the pack
  Properties panel (github slug label) and an Open-source row action
- packs e2e rewritten for the CDN model (mocked index, absolute-URL
  passthrough, fallback, Source button); PACKS.md documents index.json + the
  upstream-fetch pattern
- packs repo published: default / cube_diorama (real labels) /
  khronos-sample-assets (index only) / audio-essentials (23 CC0 sounds,
  Kenney + OpenGameArt, installable .zip) - tagged v1

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tes, release flow, update toast

- V2: About shows the baked version string; CLOUD_HOOKS_VERSION +
  cloudPluginInfo store + api.setPluginInfo (About Cloud-plugin row);
  startCloudPlugin fails CLOSED when a plugin declares compatibleHooks
  NEWER than the app (strictly greater - new-app + old-plugin stays fine)
- V3: app version + sha ride the modules handshake; checkPeerAppVersion
  toasts once per differing version per session (warned-Set, reconnects
  cannot re-spam)
- V4: SESSION_FORMAT=1 + appVersion stamped into session payloads; NEW
  confirmDialog.js (promise showConfirm) + ConfirmModal.svelte; importSession/
  importSessionZip confirm a NEWER format BEFORE any asset/pack restore and
  resolve null on cancel (call sites treat null as silent cancel); older or
  absent formats load silently
- V5: MODULE_FORMAT=1 - installZip/installUrl confirm a newer manifest
  format; install records carry appVersion
- V6: .github/workflows/release.yml (v* tag -> build + svelte-check baseline
  gate 476/62 + zip + GitHub Release w/ notes) + RELEASING.md + CLAUDE.md
  pointer (npm version is the single source of truth)
- V8: emitVersionJson vite plugin writes static/version.json (gitignored);
  updateCheck.js polls it every 15 min + on tab focus, one reload toast per
  session, dev skips
- NEW e2e version.test.cjs (8 checks) all green; sessions/session-zip/
  tpscene/modules-manager/whats-new suites green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ad end)

- opening a zip-only pack (no item list) showed an empty 'This pack has no
  items' view - the only install path was a right-click row, invisible on
  touch; the open view now IS the install prompt (#pack-install button,
  shared installZipPack with the row action)
- loadPacks: an INSTALLED zip pack shadows its default-list row (installing
  audio-essentials listed the pack twice after reload)
- loadPackItems short-circuits zip-only packs (fetch('') grabbed the page)
- installZipPack loads items explicitly after install - the activeFolder
  effect does not re-fire when the same pack was already open (svelte 5
  same-value equality)
- packs e2e: zip row in the mocked index + install-card checks; verified
  LIVE against jsDelivr (real install pulls 23 sounds, dedupe survives
  reload)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- importPackZip creates (or reuses) a root library folder named after the
  pack and stores every item in it - 23 audio-essentials sounds no longer
  dump into the library root; content-hash dedupe still wins for items
  already stored elsewhere
- packs e2e asserts the folder placement

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AlexZ005
AlexZ005 merged commit e138948 into main Jul 28, 2026
@AlexZ005
AlexZ005 deleted the feat/release-1.0-launch-polish branch August 5, 2026 20:25
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