diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8cc6fc1a..8dbad971 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,8 @@ jobs: - run: npm ci - run: npm run build # svelte-check exits non-zero at the legacy baseline; fail ONLY when the - # counts grow past it (baseline 435 errors / 62 warnings, 2026-08-01, node 24) + # counts grow past it (baseline 421 errors / 62 warnings, 2026-08-01 — the + # roadmap #15 C one-way `hex` picker rework dropped it from 435, node 24) - name: svelte-check baseline gate run: | npm run check 2>&1 | tee check.log || true @@ -34,9 +35,9 @@ jobs: ERRORS=$(echo "$LINE" | awk '{print $2}') WARNINGS=$(echo "$LINE" | awk '{print $5}') fi - echo "svelte-check: $ERRORS errors / $WARNINGS warnings (baseline 435/62)" + echo "svelte-check: $ERRORS errors / $WARNINGS warnings (baseline 421/62)" if [ -z "$ERRORS" ]; then echo "could not parse svelte-check output"; exit 1; fi - if [ "$ERRORS" -gt 435 ] || [ "$WARNINGS" -gt 62 ]; then + if [ "$ERRORS" -gt 421 ] || [ "$WARNINGS" -gt 62 ]; then echo "baseline exceeded"; exit 1 fi - name: zip the build diff --git a/src/components/editors/Explorer.svelte b/src/components/editors/Explorer.svelte index 4df05374..f72a6573 100644 --- a/src/components/editors/Explorer.svelte +++ b/src/components/editors/Explorer.svelte @@ -832,12 +832,21 @@ } // N6: place a default-pack item into the scene (double-click / Enter) at origin + // 15-B3: the CDN fetch takes seconds — hold the SAME loading toast the drop + // path shows (it used to look like nothing happened until the model popped in) async function placePackItem(item: any) { + const { holdLoadingToast } = await import('$lib/explorerDrop'); + const dismiss = holdLoadingToast(String(item.name || 'model')); try { const res = await fetch(item.glbUrl); - if (!res.ok) return showToast('Could not fetch the pack item'); + if (!res.ok) { + dismiss(); + return showToast('Could not fetch the pack item'); + } await importFile(new File([await res.blob()], item.name + '.glb'), item.name, 'glb'); + dismiss(); } catch { + dismiss(); showToast('Could not load the pack item (check the network / CORS)'); } } diff --git a/src/components/menu/Connect.svelte b/src/components/menu/Connect.svelte index c5ff8262..488ee421 100644 --- a/src/components/menu/Connect.svelte +++ b/src/components/menu/Connect.svelte @@ -1,6 +1,6 @@ @@ -892,7 +905,8 @@
{ - $backgroundColor = event.detail.hex; - $globalScene.background = new THREE.Color($backgroundColor); - sendBackgroundColor(); - }} + hex={$backgroundColor} + onInput={(/** @type {any} */ c) => setBackground(c.hex)} /> { - if (hexColor.test(e.currentTarget.value)) { - $backgroundColor = e.currentTarget.value; - $globalScene.background = new THREE.Color($backgroundColor); - sendBackgroundColor(); - } + if (hexColor.test(e.currentTarget.value)) setBackground(e.currentTarget.value); }} />
@@ -927,7 +933,8 @@
{ - fogColor = event.detail.hex; + hex={fogColor} + onInput={(/** @type {any} */ c) => { + fogColor = c.hex; applyFog(); }} /> @@ -963,10 +970,9 @@ size="xs" color="alternative" onclick={() => { - $globalScene.fog = null; fogNear = null; fogFar = null; - sendFogColor(); + editEnvSky({ fog: null }); }}>Remove Fog
@@ -1169,7 +1175,8 @@
{ - $selectedObject.color.set(event.detail.hex); - color = event.detail.hex; + hex={color} + onInput={(/** @type {any} */ c) => { + $selectedObject.color.set(c.hex); + color = c.hex; sendLightUpdate(); }} /> @@ -1203,7 +1210,8 @@ { - $selectedObject.groundColor.set(event.detail.hex); - groundColor = event.detail.hex; + hex={groundColor} + onInput={(/** @type {any} */ c) => { + $selectedObject.groundColor.set(c.hex); + groundColor = c.hex; sendLightUpdate(); }} /> @@ -1333,7 +1341,8 @@ {#if material.color && material.type !== 'MeshNormalMaterial'} { - trackColorGesture($selectedObject.uuid, event.detail.hex); - $selectedObject.material.color.set(event.detail.hex); - $peers.send({ type: 'color', uuid: $selectedObject.uuid, color: event.detail.hex }); + hex={color} + onInput={(/** @type {any} */ c) => { + // live drag: ONE debounced undo entry per gesture (setObjectColor + // would record on every frame), then apply + replicate + trackColorGesture($selectedObject.uuid, c.hex); + $selectedObject.material.color.set(c.hex); + $selectedObject.material.needsUpdate = true; + objectsGroup.update((v) => v); + $peers.send({ type: 'color', uuid: $selectedObject.uuid, color: c.hex }); }} /> { if (hexColor.test(e.currentTarget.value)) { color = e.currentTarget.value; - $selectedObject.material.color.set(color); - $peers.send({ type: 'color', uuid: $selectedObject.uuid, color }); + // a typed value is ONE discrete change — the shared write path + // applies, replicates and records a single undo entry + setObjectColor($selectedObject.uuid, color); } }} /> diff --git a/src/components/menu/SessionsManager.svelte b/src/components/menu/SessionsManager.svelte index 5c4bd918..d9cdc922 100644 --- a/src/components/menu/SessionsManager.svelte +++ b/src/components/menu/SessionsManager.svelte @@ -34,6 +34,43 @@ /** @type {any} */ let picker = null; + // 15-B5: naming a session used a browser prompt() — now an inline textbox in + // the toolbar (and inline rename on a card), matching the Explorer's + // no-prompt rename. Legacy-mode file: plain lets, never $state. + let saving = false; + let saveName = ''; + /** @type {string|null} */ + let renamingId = null; + let renameValue = ''; + + /** focus + select a freshly revealed input (autofocus would trip a11y) */ + /** @param {HTMLInputElement} node */ + function focusInput(node) { + node.focus(); + node.select(); + } + + function beginSave() { + saveName = 'Session ' + new Date().toLocaleDateString(); + saving = true; + } + function confirmSave() { + const name = saveName.trim(); + if (!name) return; + saveSession(name); + saving = false; + } + /** @param {any} meta */ + function beginRename(meta) { + renamingId = meta.id; + renameValue = meta.name; + } + function confirmRename() { + const name = renameValue.trim(); + if (renamingId && name) renameSession(renamingId, name); + renamingId = null; + } + /** @param {any} meta */ async function openPicker(meta) { const payload = await getSession(meta.id); @@ -121,14 +158,30 @@ >