Visitors can now fully interact with someone else's scene — move objects,
add shapes, edit colors, etc. The previous strict view-only mode is gone.
Save remains the only auth/ownership-gated action; its rewiring lands in
a follow-up.
Renames computeIsViewOnly to computeIsVisitingOthersScene to reflect the
remaining role (it only drives the PreviewBox today; Save-copy label and
Reset button consume it next). Drops getIsViewOnly()/useIsViewOnly()
early-returns from engine actions, drag-drop, gizmo attach, keybinds,
and the Outliner row controls. Strips !isViewOnly wrappers from chrome
in Stage3DBody so the prompt box, controls, panel, and model selector
show for everyone.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two unrelated noise sources after wiring the editor into the webapp:
1. FontLoader was fetching /resources/fonts/CarterOneRegular.json,
which exists in the Tauri public/ but not in the webapp's. Vite's
SPA fallback returns index.html (200, HTML body), the FontLoader's
progress callback fires "Font Loaded: 1" three times as it streams,
then JSON.parse chokes on the leading `<` — surfaces as an
unhandled promise rejection from LoadingPlaceHolderManager.
Copy the same font asset into the webapp's public/ tree (same
pattern Tauri and editor3d already use).
2. Editor.initialize() reads sceneContainerEl.offsetWidth/offsetHeight
to size the WebGLRenderer + post-processing pipeline. On the
webapp the flex chain (SidebarInset → wrapper → SceneContainer)
isn't always laid out by first call, so the dimensions are
transiently 0×0. setSize(0,0) then configureMain(0,0) allocates
six post-process framebuffer attachments at 0 size, each emitting
INVALID_FRAMEBUFFER_OPERATION until the next window.resize event
cascades through and resizes them properly. Clamp the initial
container offset to ≥1 (Math.max(1, ...)) and replace the
`?? 0` fallbacks on the canvas dimensions with `|| 1`. First
frame renders into a harmless 1×1, next resize fixes everything.
PromptBox3D's handleWebEnqueue was POSTing to /v1/image_studio/prompt
via PromptsApi.enqueueImageGeneration, but that endpoint does not exist
on the Rust backend — image_studio_routes.rs only registers
/scene_snapshot and /update_job_status under /v1/image_studio (the
remaining routes live under legacy_routes/). The webapp's /create-image
and other generation flows already use OmniGenApi.generateImage hitting
/v1/omni_gen/image/generate; this commit aligns the 3D editor with that
modern path.
- Snapshot upload still goes through PromptsApi.uploadSceneSnapshot
(that endpoint is the one image_studio route still in use).
- The returned snapshot media token is prepended to image_media_tokens
ahead of the user's reference images, matching how the Tauri path
treats the scene snapshot as the primary input.
- aspect_ratio uses commonAspectRatio for models that support the new
shape, falling back to getCurrentAspectRatio() otherwise.
- On success: toast "Image added to queue" plus the same
__storeTaskEnqueueMeta telemetry the Tauri path fires.
- Failure path routes through the existing handleError modal.
- Drops a dead 2-second setTimeout that delayed the success toast.
Tauri path unchanged — IsDesktopApp() still gates handleTauriEnqueue,
which goes through Tauri IPC and bypasses storyteller-web entirely.
The Color section in ControlPanelSceneObject had two issues that
compounded into a "no picker shows up" symptom in Safari:
1. setColor(editorEngine?.selected?.userData.color) wrote `undefined`
into React state for shapes that never had a color set, which made
<input value={undefined}> uncontrolled and gave the Button no inline
background color (it fell back to the primary variant, so the swatch
looked like a generic button instead of a white swatch).
2. The hidden <input type="color"> was a sibling of the swatch
<label htmlFor={id}>, with h-0 w-0 opacity-0. Safari refuses to open
the native color picker when the label's target input has no layout
box — Chrome/Firefox tolerate it, Safari doesn't.
Fixes:
- Default the color state to "#ffffff" when userData.color is unset
(both at setColor time and at value={} time for belt-and-braces).
- Seed userData.color = "#ffffff" at shape creation in addShape so
the default carries through save/load roundtrips too.
- Move the <input type="color"> to be a CHILD of the Button label
and position it absolute inset-0 over the swatch. Click lands
directly on the input (no label[htmlFor] indirection Safari is
flaky about) and the input now has a real full-size layout box.
- Gate File → Load my scene for anon: route through promptSignup
instead of opening a dialog that would just error-toast on the
silent listUserMediaFiles failure.
- Remove dead SettingsModal mount in Controls3D (no UI ever triggered
it). Drops the @storyteller/ui-settings-modal import from this file.
- New AnonHintChip pill in the top-right corner of the playground
("Sign up to save"). Renders only when currentUserToken is undefined
AND not in view-only mode; click routes to promptSignup. Tauri users
never see it.
- Scaffolding for view-only PreviewBox: new component renders a small
card with the author's generation result; returns null when no URL
is supplied. Mounted in Stage3DBody under the isViewOnly guard,
reading sceneMeta.previewImageUrl (new optional field). Data-path
wiring (populating previewImageUrl from adapter.loadScene's
response) is the next step — leaving the UI plumbing in place so
that follow-up is purely data, not layout surgery.
useFeaturedObjects' auto-fetch effect synchronously flipped
firstFetch.current to IN_PROGRESS before calling fetchFeaturedObjects.
On AssetModal's first mount the EngineContext editor is null, so the
fetch bailed at `if (!editor) return`. When the editor later became
available the effect re-fired (editor is in fetchFeaturedObjects'
useCallback deps), but the IN_PROGRESS guard blocked it — the network
request never fired for any featured category. CHARACTER/OBJECT tabs
appeared populated only because they're merged with local demo arrays
inside AssetModal's assetTabs useMemo; Sets and Creatures have no demo
fallback so the bug was visible there.
Move the editor check into the auto-fetch effect itself and add editor
to its deps, so IN_PROGRESS isn't burned on the no-op call. Regression
from PR #1492.
- styles.css: move .glass into @layer components so Tailwind's
.absolute utility wins the cascade. OnboardingHelper was rendering
position: relative and pushing the editor down on first paint until
clicking dismissed it.
- SceneContainer + Stage3DBody: drop hardcoded calc(100vh - 68px) and
height - 56 topbar offsets; the lib now fills its parent container.
- Webapp adapter: drop the +56 getViewportSize compensation now that
the lib doesn't subtract a topbar from the supplied height.
- Tauri PageScene: wrap Stage3D in a calc(100vh - 56px) container
(matching .topbar-spacer) so its shape is preserved.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds /pagescene (and /pagescene/:sceneToken) under the webapp's public
chrome (inside ProtectedLayout, outside RequireAuth) so anonymous users
can view scenes too. Adapter implementation mirrors the website's, with
two webapp-specific tweaks: imports the local useSession / showToast /
invalidateSession helpers, and getViewportSize returns the wrapper's
live ResizeObserver-measured rect so DnD hit-testing matches the visible
canvas (not window.innerWidth).
Sidebar handling: the lib's position-fixed UI is scoped to a wrapper
div with transform: translateZ(0), which creates a new containing block.
The wrapper renders inside SidebarInset's flex flow, so the editor sits
naturally to the right of the sidebar and below the topbar; expanded-
sidebar behavior is deferred (currently shrinks the page further per the
flex layout).
Sidebar nav gets an "Edit 3D" entry between Image and Video. Tailwind
config gets brand-primary/secondary/tertiary, action color, red/green/
blue DEFAULTs; styles.css adds .bg-ui-panel-gradient, .prevent-select,
.dot-typing — same gap-fills landed on the website.
No new endpoints. tsconfig churn from nx sync.
Hoists the three feature-specific upload modals out of the artcraft
Tauri host into @storyteller/ui-upload-modal so the website (and any
future host) can render the same flows through PageScene's adapter
slots. Replaces direct `~/Classes/ApiManager` calls with @storyteller/api's
MediaUploadApi/MediaFilesApi.
Removes the old lib-internal UploadModal/UploadFiles/uploadAssets
placeholders (unused) and inlines the host-local enums
(MediaFileAnimationType, UploaderState/States, file-type enums) into
Types.ts. Adds UploadSpzFile to @storyteller/api's MediaUploadApi and
the SPLAT variant to FilterEngineCategories.
When the stored `commonAspectRatio` (e.g. `SquareHd`) isn't in the
newly-selected model's `aspectRatios`, `PopoverMenu` toggle mode found
no selected item and collapsed the trigger to icon-only.
Reset to `model.defaultAspectRatio` from inside `AspectRatioPicker` so
the fix applies to all three callers (image/edit/3D). Drops the
duplicate workaround from `PromptBox3D`.
- Lightbox now shows full media metadata: Aspect Ratio, Resolution,
Size, Duration, Audio, and Created (with timestamp).
- Added Author header at the top so the close button no longer overlaps
the Prompt copy button.
- Extracted aspect ratio / resolution / duration formatters into
`@storyteller/common` so the website and desktop app share them.
Customers occasionally saw "0 Credits" with no signal that the fetch had
failed silently. Add a status badge on the coin (slow/failed/ recovered,
hidden in the happy path), a 60s background refresh, and gate the
initial fetch on AUTH_STATUS.LOGGED_IN to remove the 1s setTimeout
band-aid that was racing intermediate auth states.
Only the submit button triggers generation by default. The "Enter to
generate" toggle (available in both app and website settings top right
nav) still allows opting into Enter-to-submit.
- Pressing **Enter** in any prompt box now adds a new line instead of
submitting - fixes complaints about accidental generations
- **Shift+Enter** is the new submit shortcut
- Desktop app gets an "Enter to generate" toggle in Settings → General
(off by default) for users who prefer the old behavior
- Applies to all prompt boxes in the Artcraft desktop app and the
website
## Artcraft website: unified lightbox, recreate, desktop CTA, prompt
fixes
**Lightbox**
- Gallery modal and `/media/:id` page now share one details component -
no more drift.
- Author sticks to the top when you scroll; `X` moved to the details
panel; "Show more" prompt toggle added to the media page.
- Shows aspect ratio, resolution, duration, and audio on/off.
**Recreate (new)**
- Button in the lightbox restores prompt, refs (image / end-frame /
video / audio), model, aspect, resolution, duration, audio toggle, and
input mode from a past generation.
- Routes to Create Image vs Create Video based on the saved model class,
not URL guessing.
- Does not auto-generate.
**Desktop CTA**
- Dismissible banner above the promptbox linking to the desktop app.
Hidden on mobile, reappears 24h after dismissal.
**Model names**
- Model selector uses `full_name` from the OmniGen API so new models
show up correctly without a code change.
- Added Seedance 2.0 Fast to the shared model list for the lightbox
details panel.
**Promptbox `@mentions`**
- Triggers correctly after Chinese, punctuation, or emoji - not just
whitespace.
- Case-insensitive: `@image1` highlights as `@Image1`.
fixes:
- prompt box no longer overlaps bottom buttons or wastes space on big
screens
- long unbroken text wraps instead of escaping the box
- resizing the window updates the layout without needing to refocus