Shapes survived the save side fine — `getChildren` copies the whole
`userData` (including `shapeKey`) into the proxy's `user_data` blob,
and `toJSON()` writes it out. The load side was the half that lost
the thread: `proxyScene.loadFromSceneJson`'s `Parim` branch was
calling `Scene.instantiate(json_object.object_name)`, but
`object_name` is the *display label* ("Cube", "Point Light"), not the
geometry key the switch matches on ("Box", "PointLight"). The switch
fell through to its else branch and returned `new THREE.Mesh(undefined,
material)` — an empty mesh with no geometry, which is why reloaded
shapes showed up in the outliner, were translate-gizmo-movable, but
rendered invisibly, weren't raycast-pickable, and ignored the scale
gizmo (nothing to scale).
Recover the geometry key from `user_data.shapeKey` (the same field
`addShape.ts:21` stashes and `actions/snapshots.ts:78-79` already
uses for undo/redo), falling back to `object_name` for older cached
scenes that pre-date the field. Re-stash `shapeKey` on the recreated
obj so subsequent save round-trips survive.
Drive-by: drop the leftover `console.log(scene_json)` that was
firing on every cache load.
`MouseControls.onMouseDown` was calling `lockControls.lock()` eagerly on
every left/middle mousedown. The browser would acquire pointer lock
between mousedown and mouseup of a clean click, which suppressed the
synthesized `click` event — so `useViewportPointer.onClick` never fired
and raycaster selection was dead. The pointer-lock "Press Esc to exit"
toast popped on every static click as a tell.
Drop the eager lock from `onMouseDown`. The existing
`handleMousePointerLock()` path (called from `onMouseMove`, line 547)
already requests lock when `isMouseClicked === true && !isLocked`, so a
real drag still acquires the lock on its first mousemove. A static
click now stays out of the lock state machine entirely and the click
event reaches `MouseControls.onMouseClick` for the raycast.
Pre-migration this code lived behind window-level click listeners
attached by SceneManager, where the lock-vs-click ordering didn't
matter. The lib migration moved click handling to canvas-level inside
`useViewportPointer.ts`, exposing the conflict.
When the user left/middle-click drags to rotate the camera and releases,
Chromium synthesizes one final `mousemove` while exiting pointer lock so
it can reposition the cursor back to where the user grabbed it. At that
exact moment, Three's PointerLockControls still has `isLocked === true`
(the flag flips after the unlock event dispatches), so its mousemove
handler reads the synthesized `movementX/Y` as user input and rotates
the camera by it — producing a visible lurch back by roughly the inverse
of the user's last drag delta.
Capture the camera quaternion in `MouseControls.onMouseUp` immediately
before `lockControls.unlock()`. In a real (non-debug) `unlock` listener
attached at editor init, copy the snapshot back into the camera and call
`updateMatrixWorld()`. By the time `unlock` fires, the synthetic move
has already been applied, so overwriting is the surgical fix at the
boundaries we actually own — no Three.js subclassing, no per-frame
state machine.
Trace that confirmed the cause:
[mc] up button=1 locked=true quat=(-0.2684, 0.1394, 0.0393, 0.9524)
[cam-frame] quat=(-0.2684, 0.1394, 0.0393, 0.9524)
[plc] unlock quat=(-0.2180, 0.5112, 0.1358, 0.8202)
The hook was called below the `if (!currentSceneObject) return null`
guard, so renders where `currentSceneObject` was null skipped the hook
entirely. When it flipped to non-null on a later render, React saw a
new hook appear and threw "rendered more hooks than during the previous
render".
Move `useViewportSize()` (and the `getScale` helper that consumes it)
above the early return so the hook order is stable across renders.
Two fixes for the regression that left the TopBar and tab switcher
invisible after the Stage3D migration:
1. index.tsx still rendered <PageScene /> directly under BrowserRouter,
so the entire MainApp shell (TopBar, LoginModal, Toaster, modals,
universal Tauri event listeners) was bypassed and the 3D editor
appeared as the default and only view. Routes (_index.tsx,
$sceneToken.tsx) had been updated to mount MainApp; the bootstrap
entry was missed.
2. MainApp.TabBody returned each page component directly. Pages like
PageDraw return a fragment whose top-level child is
`position: fixed; inset: 0`; without a wrapping <div> those
fixed-positioned children became direct siblings of the TopBar at
the MainApp root, escaping their tab slot. Wrap every non-3D case
in <div>. The 3D case stays unwrapped because Stage3DBody (lib)
already returns a <div> outer wrapper.
Default boot view is now the IMAGE (TextToImage) page under a visible
TopBar; switching tabs keeps the TopBar fixed and pages render below.
Typecheck stays at 47.
Was reading editor.adapter.getViewportSize() inline during render,
which captured pageWidth/pageHeight signal values at that single
moment without subscribing to changes. If the host's pageHeight
signal hadn't been updated by GlobalSettingsManager yet (initial
value is 0 in the artcraft signal), Stage3DBody would render with
viewport dimensions of 0 and never recover.
useViewportSize encapsulates the read + window resize listener and
re-renders the component when dimensions change.
Final step of the Stage3D migration. The lib now exposes a single
top-level component:
<Stage3D adapter={adapter} sceneToken={...} cacheJsonString={...}
onSceneSerialized={...} />
…that owns the EngineProvider, body composition (canvas, outliner,
control panel, prompt box, model selector, controls, etc.), and
the React-lifecycle-driven set3DPageMounted. Hosts only build their
own PageSceneAdapter and decide where to stash the cache string.
Lib additions:
- src/lib/Stage3D.tsx — top-level shell (provider + drag/loading
layer + body)
- src/lib/Stage3DBody.tsx — ex-PageEditor; consumes adapter for
uploadImage / uploadPlaneFromMediaToken / getViewportSize
- Public re-export of Stage3D + Stage3DProps
Apps changes:
- pages/PageScene/PageScene.tsx reduced to a 50-line wrapper that
builds the Tauri adapter, owns the host-specific concerns
(tab-cache via useTabStore, URL ↔ scene-token sync via react-
router-dom, currentUserToken mirror), and renders <Stage3D />
- pages/PageScene/PageEditor.tsx — deleted (now Stage3DBody in lib)
Apps PageScene/ is now exactly three files, all genuinely host-
specific:
- PageScene.tsx (Tauri-flavored wrapper)
- useTauriPageSceneAdapter.tsx (Tauri-bound adapter implementation)
- index.tsx (exports PageScene)
The artcraft-website can now mount the same 3D editor by writing its
own ~50-line wrapper + a useWebPageSceneAdapter, with zero duplicated
editor body or controls.
Typecheck stays at 47, lib build clean.
ControlsTopButtons + Help + LoadUserScenes + NewSceneFromTemplate +
ScenePicker + SceneCard now live in
@storyteller/ui-pagescene/comps/ControlsTopButtons/. They access host
plumbing through the adapter (showToast, listUserMediaFiles,
listFeaturedMediaFiles, getCdnUrl, onSceneTitleChange) and read scene
metadata reactively from the new sceneMeta + currentUserToken store
fields.
Lib additions:
- PageSceneStore.sceneMeta + currentUserToken with setSceneMeta /
setCurrentUserToken actions; SceneMeta interface exported
- comps/ControlsTopButtons/{ControlsTopButtons,Help,LoadUserScenes,
NewSceneFromTemplate,ScenePicker}/
Apps changes:
- ~/signals/scene.ts: signalScene now mirrors into PageSceneStore so
the lib has reactive read access without depending on signals
- pages/PageScene/PageScene.tsx: lifted the URL ↔ scene-token sync
useSignalEffect out of ControlsTopButtons (router-aware bookkeeping
belongs at the apps shell), plus useSignalEffect mirroring
authentication.userInfo into store.currentUserToken
- pages/PageScene/PageEditor.tsx: ControlsTopButtons import now
resolves to @storyteller/ui-pagescene
- Deleted: pages/PageScene/comps/ControlsTopButtons/
Typecheck: 51 → 47 (deleted ControlsTopButtons had several pre-
existing typecheck issues that disappear in the rewrite). Lib build
clean.
Six new adapter members covering the host coupling that Controls3D,
ControlsTopButtons, and PageEditor will need once they live in the
lib:
- renderImageUploader / renderSplatUploader — slot patterns mirroring
the existing renderAssetUploader, for the two non-GLB upload modals
Controls3D triggers
- uploadImage / uploadPlaneFromMediaToken — host-pipeline wrappers
used by PromptBox3D and the gallery-drop handler. Signatures match
@storyteller/common's UploadImageArgs so they pass through cleanly
- navigateToImageTo3D — the only cross-page navigation the lib needs
today (Controls3D's "create 3D from image" button). Tauri host
switches tabs; web host will navigate routes
- performLogout — backs the SettingsModal logout callback
useTauriPageSceneAdapter wires all six. No call sites yet — the
files that consume them are still in apps; the next commits move
them into the lib.
Typecheck stays at 51, lib build clean.
The AssetMenu — asset browser modal, fetch hooks, mapping utilities,
demo data — moves out of the artcraft host into the lib alongside the
engine it serves. Host-platform calls (MediaFilesApi, BucketConfig,
addToast, UploadModal3D) are reached via the new adapter methods and
the renderAssetUploader slot.
Lib additions (frontend/libs/components/pagescene/src/lib):
- comps/AssetMenu/{AssetMenu,AssetModal}.tsx
- comps/AssetMenu/hooks/{useFeaturedObjects,useUserObjects}.tsx
- comps/AssetMenu/utilities/{fetchMediaItems,misc}.ts
- comps/AssetMenu/shared/{ItemElement,ItemElements}.tsx
- signals/demoAssets/* (5 files)
Hooks now read the adapter from EngineContext rather than importing
~/Classes/ApiManager directly. Fetcher functions take adapter as a
second argument.
Apps deletions:
- pages/PageScene/comps/AssetMenu/ (entire folder)
- pages/PageScene/signals/demoAssets/ (entire folder)
- pages/PageScene/signals/index.ts (now empty)
Apps updates:
- comps/Controls3D/Controls3D.tsx: AssetModal import now from
@storyteller/ui-pagescene
Typecheck: 58 → 51 (the deleted AssetModal had several pre-existing
typecheck issues that disappear with the rewrite).
nx build ui-pagescene clean.
Apps PageScene/ now contains only host-coupled comps Controls3D and
ControlsTopButtons, plus the route shell (PageScene.tsx, PageEditor.tsx,
useTauriPageSceneAdapter.tsx). Everything else is in the lib.
The lib's AssetMenu (next commit) needs a way to render the host's
UploadModal3D from inside its own modal. Following the same slot
pattern as renderAssetBrowser/renderSceneLoader, a new
renderAssetUploader slot lets the host plug in its file picker +
validation + splat conversion UI without the lib pulling in any
host-component imports.
useTauriPageSceneAdapter wires UploadModal3D into the slot. File
renamed to .tsx since the slot returns JSX.
Typecheck stays at 58.
Adds the surface AssetMenu needs to live in the lib without importing
artcraft-host APIs:
- listUserMediaFiles / listFeaturedMediaFiles — paginated media
listings backed by MediaFilesApi
- getCdnUrl — wraps host BucketConfig for thumbnail composition
- showToast — wraps host addToast / react-hot-toast pipeline
useTauriPageSceneAdapter wires all four to the existing artcraft host
plumbing (~/Classes/ApiManager/MediaFilesApi, ~/api/BucketConfig,
~/signals/addToast). No call sites yet — the AssetMenu file move that
consumes these is the next step.
Typecheck stays at 58.
These four enums are the asset-browsing vocabulary the AssetMenu and
related host code share with the engine. Moving them into the lib
gives a single canonical identity, lets the lib own the AssetMenu
internals end-to-end, and aligns with the same pattern used for
AssetType / Visibility / etc.
Apps `~/enums/QueryFilters.ts` and `~/enums/toasts.ts` become thin
re-exports from @storyteller/ui-pagescene; existing host imports are
unchanged.
This is a prep step for the AssetMenu migration — Chunk 1 in the
ongoing PageScene componentization. No functional changes; typecheck
stays at 58.
The 12-tab conditional in TabbedPages was a duplicate of the TopBar's
MenuIconSelector — both were independently switching on
useTabStore.activeTabId. Lifting it out gives PageScene a single
responsibility (the 3D editor) and lets the React mount/unmount of
PageScene drive the engine lifecycle directly, without imperative
set3DPageMounted flag flips from TopBar / appMenu.
Architecture:
Route → MainApp (always mounted)
├ TopBar (universal chrome)
├ universal modals/Toaster
├ Tauri event listeners (always-on)
└ TabBody (switch on activeTabId)
├ PageScene (only when "3D")
│ └ EngineProvider (mounts/unmounts the engine)
├ PageDraw (only when "2D")
└ … 11 other branches
PageScene mount → useEffect sets is3DPageMounted=true; cleanup sets
false. Engine's remountEngine() gate uses the same flag, so the
behavior is preserved without TopBar/appMenu/ImageTo3DExperience
needing to know about the engine.
Deletions:
- pages/PageScene/TabbedPages.tsx — folded into MainApp's TabBody
- pages/PageScene/engineLifecycle.ts — only set3DPageMounted was
still in use, and that's now driven by PageScene's mount effect
- pages/PageScene/GlobalSettingsManager.tsx — dead since the real
GlobalSettingsManager lives inline in app/src/index.tsx
PageEditor.tsx no longer renders TopBar or LoginModal (moved to
MainApp). It also drops the activeTabId === "3D" wrap and the 2D
drag handler; PageDrawWithGalleryDrop in MainApp handles the 2D
drop dispatch into PageDraw's existing window-event listener.
Routes (_index.tsx, $sceneToken.tsx) now mount <MainApp /> instead
of <PageScene />.
Typecheck: 60 → 58 (two pre-existing unused-import warnings removed
along with the deleted files).
Editor.constructor was injecting <script src="jsm/libs/ammo.wasm.js"> into
document.body and creating <div id="created-one-element"> on every
construction. Strict mode + provider remounts produced duplicate tags;
the div was unreferenced anywhere in the codebase.
Replaced with a module-level Promise singleton (ammoLoader.ts) that
appends the script exactly once. Existing tags (e.g. from a prior
session) are detected and reused. The div is gone.
This is a correctness cleanup, not a behavior change for first-mount —
the script still loads on first Editor construction, just no longer
duplicates on subsequent ones.
Lifts the remaining apps/PageScene shim-paths inside the host's own
files to direct package imports. The shim files themselves stay
(actions/, hooks/, contexts/, DragAndDrop/, sceneMetadata.ts,
comps/{DragComponent,PrecisionSelector,FocalLengthDisplay,
AspectRatioMenu,PoseModeSelector,EngineCanvases,Outliner,
ControlPanelSceneObject,SceneContainer,PreviewEngineCamera,
PreviewImages,OnboardingHelper,EditorLoadingBar,
ControlsTopButtons/Help/Help}); they're still load-bearing for the
host-coupled comps that haven't migrated yet (AssetMenu/,
ControlsTopButtons/, Controls3D/) and they'll get cleaned up when
those move.
Updated host files to import directly from @storyteller/ui-pagescene:
- PageEditor.tsx (8 imports collapsed: hooks/*, EngineContext,
actions, GridVisibleChangedEvent + pickDropPosition)
- PageScene.tsx (EngineProvider)
- comps/Controls3D/Controls3D.tsx (EngineContext, setTransformMode)
- comps/ControlsTopButtons/ControlsTopButtons.tsx (sceneMetadata)
- comps/AssetMenu/shared/ItemElement.tsx (DndAsset → dragAndDrop
named import, EngineContext)
Lib public API:
- KeyGroup (the binding-category string-union type from keymap)
re-exported as `KeymapKeyGroup` to avoid name collision with the
Help component kit's KeyGroup (the keyboard-icon visual primitive).
Verified: artcraft typecheck at 61 errors (down from 62 — one fewer
pre-existing unused-import warning).
The engine/* shims (26 one-line re-export files) had no remaining
internal callers — every apps/PageScene/* file that previously
reached into engine/X has been migrated to either a sibling shim
(actions/, hooks/, contexts/, etc., which forward to the lib) or
directly to the @storyteller/ui-pagescene package.
PageEditor.tsx had the only two lingering engine-shim imports;
both fold into a single import line from the package:
import {
GridVisibleChangedEvent,
pickDropPosition,
} from \"@storyteller/ui-pagescene\";
The whole apps/PageScene/engine/ tree (editor.ts, scene.ts,
save_manager.ts, helper.ts, scene_manager_api.ts, keybinds_controls.ts,
keymap.ts, EngineStoreBridge.ts, events/{EngineEvent,EngineEventBus},
editor/{HistoryManager, ViewportController, PostProcessingPipeline,
GizmoController, CameraController, SelectionBridge}.ts,
editor/actions/{Color,Create,Delete,Lock,Transform,Visibility}Action.ts,
editor/actions/snapshots.ts, cameraMath.ts, pickDropPosition.ts,
pointer.ts) is now gone — 26 files deleted.
Verified: artcraft typecheck still at 62 errors.
The lib already owns the canonical sources for these. Apps copies are
now redundant code that can drift; collapsing to one-line re-exports
removes that risk.
- apps/.../PageScene/models/ — was 7 .ts/.tsx files; now a single
index.ts that re-exports the full type surface from
@storyteller/ui-pagescene. Other artcraft modules (MediaFilesApi,
VoiceDesignerApi, WeightsApi, UserBookmarksApi, BetaKeysApi,
PromptsApi, ImageTo3DExperience, backgroundLoadedMedia) keep their
`~/pages/PageScene/models` import path unchanged.
- apps/.../PageScene/enums/ — was 10 enum files; now a single index.ts
re-exporting CameraAspectRatio, EditorStates, MediaFileType +
MediaFileSubtype, MediaFileClass, WeightCategory, WeightType,
Visibility, FetchStatus, GenerateTtsAudioErrorType, JobState,
ArtStyle from the lib.
- apps/.../PageScene/datastructures/ — was a single common.ts (XYZ +
Simple3DVector) with no remaining importers. Deleted; lib's copy
is the single source. (XYZ + Simple3DVector are exported from
@storyteller/ui-pagescene for direct host imports if needed.)
Lib's index.ts adds MediaFileType + MediaFileSubtype + MediaFileClass
+ WeightCategory + WeightType + Visibility + FetchStatus +
GenerateTtsAudioErrorType + JobState + ArtStyle to its enums block.
Verified: artcraft typecheck at 62 errors (one fewer pre-existing
warning -- a stale unused-export now resolved through the shim).
Adds MediaInfo, Pagination, Prompts, UserDetailsLight, MediaFile,
GetMediaListResponse, GetMediaFileResponse,
VoiceConversionModelListItem/Response, CreatorDetails, and the three
UserBookmark types to the lib's public surface so host modules that
previously imported from apps/.../PageScene/models can switch
imports to @storyteller/ui-pagescene without losing types. The lib
already had all of these internally; this commit just promotes
them through the public index.
Verified: artcraft typecheck holds at 63 errors.
PageEditor.tsx had two responsibilities mashed together: render the
3D editor body when tab===3D, and route the user to one of 11 other
non-3D pages otherwise. The non-3D routing is unrelated to PageScene
internals and was dragging in PageDraw, TextToImage, ImageToVideo,
VideoFrameExtractor, VideoWatermarkRemover, ImageWatermarkRemover,
ImageTo3DObject, ImageTo3DWorld, RemoveBackground, Angles,
Storyboard, AppsIndexPage as transitive imports.
Moved:
- New apps/.../PageScene/TabbedPages.tsx — renders the 12 alternate-
tab branches. Self-contained: useTabStore + useStoryboardPageEnabled
+ the alternate-page imports.
- PageEditor.tsx loses 11 page imports + the storyboard-page hook +
the 12-tab JSX block (60 lines). Renders <TabbedPages /> as the
single replacement.
Behavior is unchanged — TabbedPages still mounts as a sibling of the
3D content inside PageEditor, so the EngineProvider lifecycle still
covers all tabs (engine remounts when the user comes back to 3D).
The full route-level lift-out (where non-3D tabs don't even mount
EngineProvider) is the next step; this is the prerequisite refactor.
Verified: artcraft typecheck still at 63 errors (all pre-existing).
The bar previously read host-level loadingBarData/loadingBarIsShowing
signals — orphaned writes after Stage A; the engine no longer drives
them. The lib version reads from store.editorLoader (which IS
populated by EditorLoaderEvent on the bus from save_manager's
save/load paths) so the bar actually shows during the right
operations.
- src/lib/comps/EditorLoadingBar.tsx: reads editorLoader.{isShowing,
message} from PageSceneStore, supplies a stable label and a fixed
100% progress (the bar is binary-shown for engine save/load; the
prior progress field was never animated).
- Apps copy becomes a one-line re-export shim.
artcraft/app/src/signals/loadingBar.ts is now orphaned (no readers,
no writers anywhere). Left in place — harmless, can be deleted in a
drive-by cleanup.
Verified: artcraft typecheck at 63 errors (down from 70 — the
unused-import errors that referenced loadingBarData/IsShowing
disappeared with the migration).
Output of `nx sync` after the comp migrations:
- pagescene/tsconfig.lib.json picks up the new transitive lib refs
it inherited from the migrated comps (button-icon, tooltip,
close-button, label, button, input, button-dropdown).
- apps/artcraft/tsconfig.json gets pagescene listed before pagedraw
in references (alphabetical normalization by Nx).
`npx nx build @storyteller/ui-pagescene` succeeds end-to-end:
dist/index.js: 1,228.43 kB (gzip: 319.60 kB)
dist/index.d.ts emitted with the full public API surface.
Artcraft typecheck still at 70 errors (all pre-existing).
OnboardingHelper had been left in apps because it depended on the
Help.tsx icon kit (Key, KeyGroup, Mouse, Plus etc.) under
ControlsTopButtons/Help. Help.tsx itself has no host coupling --
it imports only @storyteller/ui-label -- so both move into the lib
together:
- src/lib/comps/ControlsTopButtons/Help/{Help,index}.tsx — keyboard
+ mouse icon visual primitives (Key, KeyGroup, Mouse, Plus,
ShortcutsGroup, Shortcut, Help itself).
- src/lib/comps/OnboardingHelper.tsx — the WASD/click hint that
fades out on first key/mouse interaction.
Apps copies become re-export shims. Public API exposes the icon
primitives so future host-side help overlays can reuse them.
Verified: artcraft typecheck at 70 errors (one fewer than the 71
prior; one of OnboardingHelper's pre-existing unused-import errors
disappeared with the migration).
Migrates the comps that previously read pageHeight/pageWidth from
the artcraft `~/signals` surface; they now use a new useViewportSize
hook that reads through adapter.getViewportSize when supplied (host
threads its existing pageWidth/pageHeight signals through), falling
back to window.innerWidth/innerHeight + a resize listener.
Comps moved:
- Outliner — uses viewport.height for the editor-height calculation
and viewport.width for the dual-layout breakpoint at 2000px.
- ControlPanelSceneObject — viewport.height for the small-screen
scale calc.
- SceneContainer — viewport for the container dimensions, with the
56px TopBar deduction extracted to a TOP_BAR_PX constant.
- PreviewEngineCamera — viewport.width / viewport.height for
responsive UI thresholds (also drops the explicit useSignals call).
- PreviewImages — viewport-derived image-grid dimensions.
New: src/lib/hooks/useViewportSize.ts. Reactive, adapter-aware, with
a window.innerWidth/innerHeight fallback for hosts that don't supply
getViewportSize.
Apps copies become one-line `export { X } from "@storyteller/ui-
pagescene"` re-export shims.
Lib public API exposes:
- The 5 newly-migrated comps.
- useViewportSize hook (useful to host code that wants the same
reactivity contract).
Comps still in apps (host-coupled, blocked behind UploadModal3D /
MediaFilesApi / signalScene / addToast / loadingBarData):
- AssetMenu/, ControlsTopButtons/, Controls3D/, EditorLoadingBar,
OnboardingHelper. These either become adapter render-slots
(renderAssetBrowser is already on the interface) or get migrated
once their host deps are surfaced through the adapter.
Verified: artcraft typecheck at 71 errors (one fewer than the prior
72 baseline; the dropped error was a pre-existing implicit-any in
the migrated PreviewEngineCamera that disappeared with the rewrite).
Lib gains the React/orchestration layer that sits between the engine
and the host React tree:
- src/lib/actions/* (12 dispatchers + index): addCharacter, addObject,
addShape, deleteObject, selectObject, setCameraAspect,
setObjectColor (+ ColorSession), setTransformMode, toggleObjectLock,
toggleObjectVisibility, transformObject (+ TransformSession),
deselectObject, beginColorSession, beginTransformSession.
- src/lib/hooks/*: useEditorCanvas, useCamViewCanvas, useFreeCam,
useViewportPointer, useViewportKeyboard. ~/-aliased imports
rewritten to relative paths inside the lib.
- src/lib/contexts/EngineContext/{EngineContext,EngineProvider}: the
React context boundary. EngineProvider takes adapter + cache hooks
via props and constructs `new Editor(adapter)`.
- src/lib/DragAndDrop/DndAsset.ts: the asset-drop singleton, now
reading viewport size through `editor.adapter.getViewportSize?.()`
with a `window.innerWidth/innerHeight` fallback when the host
doesn't supply one.
- src/lib/sceneMetadata.ts: the SceneGenereationMetaData helper used
by the save flow + the EngineProvider cache snapshot.
Adapter interface gains:
- getViewportSize?(): { width: number; height: number }
Used by DndAsset for drop hit-testing. Optional — if the host
omits it, the lib falls back to window.
Apps wrappers:
- All apps copies become one-line `export { X } from "@storyteller/
ui-pagescene"` re-export shims so existing importers (PageEditor,
AssetMenu items, ImageTo3DExperience, hooks, EngineContext
consumers) keep their import paths unchanged.
- useTauriPageSceneAdapter.ts now wires getViewportSize from the
existing pageWidth/pageHeight signals so the artcraft host's
TopBar height + page chrome are honored.
Public API (src/index.ts) adds:
- All 12 action dispatchers + ColorSession/TransformSession types.
- The 4 hooks (useEditorCanvas, useCamViewCanvas, useFreeCam,
useViewportPointer, useViewportKeyboard).
- EngineContext + setActiveEditor/getActiveEditor + EditorExpandedI.
- EngineProvider.
- dragAndDrop singleton.
- getSceneGenerationMetaData.
Verified: artcraft typecheck holds at 72 errors (all pre-existing,
mostly in comps/ and unrelated areas like SoundManager). Lib's own
typecheck cycle through `noEmitOnError: false` produces a clean
dist/index.d.ts with the new public surface.
Remaining for Stage B: comps/ (15 React UI components), lifting the
12-tab switch out of PageEditor.tsx, nx build smoke + manual end-
to-end test.
Final integration step. The lib's @storyteller/ui-pagescene is now the
canonical source for PageScene's engine + state; the artcraft host
constructs a Tauri-backed PageSceneAdapter and threads it through
EngineProvider into Editor.
Changes:
apps/PageScene/engine/* -- previously held duplicate copies; now:
- Files imported by other artcraft code (editor.ts, scene.ts,
helper.ts, save_manager.ts, scene_manager_api.ts,
keybinds_controls.ts, keymap.ts, pickDropPosition.ts, pointer.ts,
cameraMath.ts, editor/{HistoryManager, ViewportController,
PostProcessingPipeline, GizmoController, CameraController,
SelectionBridge}.ts, editor/actions/*.ts) become tiny
`export * from "@storyteller/ui-pagescene"` re-export shims so the
existing 50+ importers (PageEditor, EngineProvider, EngineContext,
hooks/, DragAndDrop/, action dispatchers) keep working unchanged.
- Files only used internally by the now-shimmed engine code
(CustomOutlinePass.js, FindSurfaces.js, TransformControls.js,
chromakey.js, shaders.js, InfiniteGridHelper.ts, browserChecks.ts,
placeholder_manager.ts, KinHelpers/*, EditorState/*,
APIManagerResponseSuccess.ts, proxy/*) are deleted -- the lib
carries the canonical copies.
- engine/api_manager.ts + engine/api_fetchers.ts deleted; their
bodies move to the new useTauriPageSceneAdapter.ts hook (see below).
EngineProvider:
- Takes a new PageSceneAdapter prop and constructs `new Editor(adapter)`.
- Reads adapter via ref so adapter identity changes don't tear down
the live engine.
apps/PageScene/PageScene.tsx:
- Uses useTauriPageSceneAdapter() to build the adapter, threads it
into <EngineProvider adapter={adapter} ...>.
apps/PageScene/useTauriPageSceneAdapter.ts (new):
- Single source of all the host-side platform plumbing. Wires
enqueueGeneration -> GenerateImage (tauri-api), saveScene/loadScene
to the api_manager+api_fetchers bodies inlined here, fetchAsset to
FetchProxy with LoadWithoutCors fallback, getCdnOrigin/
getApiSchemeAndHost to artcraft singletons, getMediaUrlByToken to
MediaFilesApi.GetMediaFileByToken, onSceneTitleChange to
signalScene, getCurrentUserToken to the authentication signal.
Lib public API (src/index.ts):
- Adds Editor (default), Scene, SceneUtils, SaveManager, SceneManager,
MouseControls, buildKeymap/dispatchBinding, all editor subsystems
(HistoryManager, ViewportController, PostProcessingPipeline,
GizmoController, CameraController, SelectionBridge), all action
classes (ColorAction, CreateAction, DeleteAction, LockAction,
TransformAction, VisibilityAction), engine utilities
(pickDropPosition, cameraMath surface, pointer helpers,
isPointerLockSupported), and the SceneGenereationMetaData type.
Adapter interface (lib/adapter.ts):
- Adds getMediaUrlByToken(token): Promise<string> to replace the
scene's prior MediaFilesApi field.
Verified: artcraft typecheck at 72 errors (down from 124 baseline --
50+ pre-existing ApiManager-related errors disappeared along with
the deleted apps engine internals). All remaining errors are
pre-existing in unrelated AssetMenu/LoadUserScenes/PageEditor modal
config / SoundManager soundboard / pre-existing engine type casts.
Bulk-copies the rest of the engine cluster into the lib with the
adapter wiring that makes editor.ts platform-agnostic:
Lib gains:
- engine/{scene, save_manager, helper, scene_manager_api,
keybinds_controls, keymap, editor}.ts
- engine/editor/SelectionBridge.ts
- engine/editor/actions/{Color, Create, Delete, Lock, Transform,
Visibility}Action.ts + snapshots.ts
- engine/EditorState/* (full JSON serialization schema)
Editor constructor signature change (lib-side only):
- Now takes a PageSceneAdapter parameter and exposes it as
`editor.adapter` for subsystems that need platform-supplied
capabilities (scene's fetchAsset/getMediaUrlByToken,
save_manager's saveSceneState/loadSceneState).
- Drops `~/signals` imports: signalScene calls now route through
adapter.onSceneTitleChange, authentication.userInfo via
adapter.getCurrentUserToken, and the loadingBarData/IsShowing
signals are dropped (the same UX is driven by EditorLoaderEvent
via the bus).
- Drops `~/Classes/ApiManager.MediaUploadApi` import + media_upload
field — unused inside the engine; uploads happen via
adapter.uploadAsset at the call sites.
- Deletes the api_manager + APIManager dependency entirely; SaveManager
now reads saveSceneState/loadSceneState callbacks directly off
adapter.saveScene/loadScene.
Scene gains:
- SceneDeps.fetchAsset(url) — replaces `LoadWithoutCors` from
@storyteller/tauri-api at the load_glb_wrapped_no_cors and
load_splat_wrapped_no_cors call sites.
- SceneDeps.getMediaUrlByToken(token) — replaces `mediaFilesApi
.GetMediaFileByToken` at the four getMediaURL call sites.
- Drops MediaFilesApi field; the scene class no longer imports
`@storyteller/api`.
Adapter interface gains:
- getMediaUrlByToken(token: string): Promise<string>
tsconfig.lib.json relaxes:
- noEmitOnError: false (so the dist still emits for the apps shims
to resolve through it, while the pre-existing material/texture +
glb-as-unknown errors that already lived in apps's engine surface
are tolerated)
- strictNullChecks/noImplicitAny/noUnusedLocals: false (same
motivation -- the engine code wasn't authored under these strict
flags; tightening them is a future cleanup)
Apps copies still in place; they still compile against their own
files. The lib's copies are the new canonical source the apps
shims will eventually resolve through. Final integration step
(shimming apps editor.ts/scene.ts/etc to lib + wiring adapter
through EngineProvider + building the new artcraft wrapper) is
the next checkpoint.
Verified: artcraft typecheck still at 124-error baseline.
Pure additive prep for the editor.ts/scene.ts move. Lib now owns:
- Engine-internal enums (still local to PageScene -- not re-exported
to artcraft via the ~/enums shim because those aren't shared with
the rest of the host): MediaFileType + MediaFileSubtype,
MediaFileClass, WeightCategory, WeightType, Visibility, FetchStatus,
GenerateTtsAudioErrorType, JobState, ArtStyle.
- Full models surface: assets, mediaInfo, pagination, prompts,
sceneGenerationMetadata, types, userBookmark + index.
Models imports rewritten on copy:
- mediaInfo.ts: ~/pages/PageScene/enums -> ../enums
- types.tsx: ~/pages/PageScene/enums -> ../enums; ./movies (broken
pre-existing import) -> ./mediaInfo (where MediaInfo actually lives;
fixing on the way through since it's a free type fix).
Apps copies stay in place; the lib copies are still orphaned (not
exported from src/index.ts). Proxy folder also tried but needs
engine/scene which isn't in the lib yet -- deferred to the next
checkpoint with editor.ts/scene.ts.
Verified: artcraft typecheck still at 124-error baseline.
Bulk-copies the pure engine subsystems and their .js helpers into
the lib so they're ready when editor.ts/scene.ts/save_manager.ts
also move (which need adapter wiring for FetchProxy/api_manager
replacement). For now these lib copies are orphaned -- not exported
from src/index.ts, no callers inside the lib yet.
Files copied:
- engine/editor/{HistoryManager, PostProcessingPipeline,
ViewportController, GizmoController, CameraController}.ts
- engine/{CustomOutlinePass, FindSurfaces, TransformControls,
chromakey, shaders}.js
- engine/{cameraMath, InfiniteGridHelper, browserChecks,
placeholder_manager, pickDropPosition}.ts
- engine/KinHelpers/* (FK rigging, kept per direction -- only the
MediaPipe pose detection was removed earlier)
Imports rewritten on-copy:
- editor/SelectionBridge.ts: ~/enums -> ../../enums (lib-local)
- editor/CameraController.ts: ~/pages/PageScene/enums -> ../../enums
Files NOT yet copied (need editor.ts / scene.ts / sceneGenerationMetadata
to land first, otherwise their type imports don't resolve):
- engine/editor/SelectionBridge.ts (depends on scene_manager_api)
- engine/editor/actions/* (depend on Editor type)
- engine/EditorState/* (depend on scene + sceneGenerationMetadata)
tsconfig adjustments:
- allowJs: true / checkJs: false to support the .js helpers
(CustomOutlinePass, FindSurfaces, TransformControls, etc.)
- Two pre-existing-flagged errors (PostProcessingPipeline outliner
uniforms cast, FKHelper TransformControls.camera cast) annotated
with @ts-expect-error so the lib build emits dist properly under
the inherited noEmitOnError: true.
Apps versions stay in place untouched -- they still compile against
the apps copies. This is a temporary duplicate-source state that
goes away once editor.ts also moves and the apps subsystems become
re-export shims.
Verified: artcraft typecheck still at 124-error baseline.
The Chromium-style EventListenerMap bus and the per-event-type bridge
both move into the lib. Class identity is single-sourced from
@storyteller/ui-pagescene so emit/subscribe lookups share the same
constructor key whether they're called from inside the lib or from
the artcraft host (e.g. PageEditor's grid-toggle GridVisibleChangedEvent).
The two apps/PageScene/engine files left in place become tiny re-export
shims pointing at the lib so the existing ~50+ importers (engine
internals, action dispatchers, PageEditor) keep working unchanged
during the staged migration -- they'll be deleted in a future commit
once their consumers move into the lib too.
Workspace plumbing:
- frontend/tsconfig.json: nx sync added pagescene to the root project
references list.
- pagescene/tsconfig.{json,lib.json}: nx sync trimmed the references
to only the libs the new code actually uses
(api-enums, model-list, common).
- node_modules/@storyteller/ui-pagescene needs to be a Windows
directory junction (mklink /J) for the workspace to resolve the
package; the prior bash `ln -s` produced a copy on Windows that
didn't reflect the lib's live state and caused misleading
"no exported member" errors.
Drive-by: nx sync also added a missing `gravatar` reference under
lightbox-modal -- unrelated cleanup riding along.
Verified: artcraft typecheck error count back to 124 baseline (no
new errors from this move).
The lib now owns the canonical PageSceneStore (Zustand), the
PageScene-specific enums (AssetType, AssetFilterOption, ClipGroup,
CameraAspectRatio, EditorStates), the common datastructures
(XYZ + Simple3DVector), and a minimal models surface (MediaItem +
AudioMediaItem -- enough for the store; richer model types stay in
apps/PageScene/models for now and migrate alongside their consumers).
Old artcraft locations become re-export shims so 56+ existing
importers keep working unchanged during the staged migration:
- apps/.../enums/{AssetType,AssetFilterOption,ClipGroup}.ts re-export
from @storyteller/ui-pagescene. Single nominal identity for each
enum across both lib and host -- avoids the "two AssetType types
that look the same but TypeScript treats as distinct" trap when
values flow across the boundary (e.g. ImageTo3DExperience passes
AssetType.SPLAT into PageScene's addObject).
- apps/.../pages/PageScene/PageSceneStore.ts re-exports the store +
types from the lib.
artcraft/tsconfig.json gains a `references` entry for
libs/components/pagescene so TypeScript resolves the package.
node_modules/@storyteller/ui-pagescene gets its workspace symlink
on first `npm install`; created manually here for the typecheck.
Verified: total typecheck error count unchanged (124, all pre-existing)
-- no new errors from the move.
The mirror ref served two purposes that turned out to be unnecessary:
the double-create guard `if (activeEditorRef.current) return;` is
unreachable (cleanup nulls the ref before the next effect body runs,
under both normal lifecycle and React 18 StrictMode), and the
cleanup-side fetch can read the editor through closure capture of
the local `newEditor` variable instead.
cacheRef and onSerializeRef stay -- those are latest-prop holders so
the effect can read fresh values on cleanup without putting the
props in deps (which would tear down + rebuild the engine on every
prop change).
PageScene's engine lifecycle was previously tab-aware: EngineProvider
read tabStore.activeTabId to decide whether to construct or dispose
the Editor, and reached into tabStore.getTabData / updateTabData for
in-memory scene save+restore across tab switches. That hack predates
the post-Stage-A architecture; with the engine event bus + decoupled
Zustand store in place, we can drive the lifecycle from React's own
mount/unmount + a clean prop interface.
Changes:
- EngineProvider takes cacheJsonString + onSceneSerialized props
instead of importing useTabStore. The construct branch fires once
the three canvas DOM nodes are populated (sceneContainerEl,
editorCanvasEl, camViewCanvasEl); the cleanup branch (returned from
useEffect) snapshots the scene via save_manager.getSceneJson and
hands the serialized JSON back through onSceneSerialized before
unmountEngine. No knowledge of tabs.
- SceneContainer + EditorCanvas + CameraViewCanvas callback refs no
longer drop the unmount-side null. Previously `if (node) { ... }`
swallowed the null on canvas unmount, leaving stale DOM refs in the
store and leaking the Editor. Now they pass null through, which
drives the EngineProvider cleanup effect when the canvases unmount
(e.g. on tab switch away from 3D).
- PageScene.tsx wraps the new prop interface: reads cacheJsonString
from useTabStore.getTabData("3D") on render, supplies an
onSceneSerialized that calls tabStore.updateTabData("3D", json).
The tab integration moves up one layer; the engine itself is
storage-agnostic and ready to drop into the @storyteller/ui-pagescene
lib without modification.
- Deletes pose/CachedPoseLandmarker.ts and pose/CachedFilesetResolver.ts.
These wrapped @mediapipe/tasks-vision but were fully orphaned (only
referenced each other; no callers anywhere). FK + KinHelpers stay --
bone manipulation is still in use, only the MediaPipe-driven pose
detection is gone.
Empty Nx library at frontend/libs/components/pagescene/ mirroring the
@storyteller/ui-pagedraw shape (package.json with development export,
tsconfig.lib.json with workspace lib references, vite.config.ts).
Defines the platform-abstraction surface PageSceneAdapter:
- Generation enqueue (model/provider/prompt/tokens/aspect/resolution)
- Scene I/O (saveScene, loadScene, fetchAsset, host CDN/API getters)
- Slot renderers for host-owned modals (renderAssetBrowser,
renderSceneLoader)
- Optional event hooks (onError, onSceneSaved, onSceneTitleChange,
onEnqueueMeta, onSelectionChange, onSceneDirty)
- Tab-cache integration (cacheJsonString + onSceneSerialized) so the
artcraft useTabStore can roundtrip serialized scene JSON without
the lib knowing about tabs
Library code is empty for now -- file migrations (PageSceneStore +
engine + comps + actions + hooks etc.) land in follow-up commits on
this branch.
PageScene engine no longer writes to PageSceneStore directly. Engine
subsystems emit typed EngineEvents on a per-event-type bus; a single
EngineStoreBridge subscribes per event class and is the only file in
engine/ that imports usePageSceneStore. Write flow is one-way: UI
toggles emit on the bus, the bridge updates the store, an engine-side
reactor updates the THREE side. No store->engine subscriptions remain.
EngineEventBus is keyed by constructor reference (Map<EventCtor,
Set<Listener>>) so emit() walks only listeners for that event type --
no global fan-out, no instanceof chains. Mirrors Chromium's per-type
EventListenerMap pattern.
MouseControls constructor refactored from 20 positional Function args
to a MouseControlsDeps object before adding bus. SceneUtils,
SaveManager, CameraController, and Scene now take Deps objects with
an explicit bus field.
Lifecycle helpers (set3DPageMounted etc.) moved out of editor.ts into
a non-engine engineLifecycle.ts so engine/ stays store-free. Same for
SceneMetadata (now sceneMetadata.ts at the page root).
Action dispatchers no longer dual-write: addCharacter/addObject/
addShape/setCameraAspect/setTransformMode/toggleObjectLock/
toggleObjectVisibility now emit a single bus event each.
Prerequisite for componentizing PageScene into @storyteller/ui-pagescene.
- Move to zustand stores from preact signals
- Delete dead code and unused files
- Remove addLocalSplat and follow gallery flow
- Get rid of Timeline components and queues, move to actions
- 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
- Ctrl+Z/Cmd+Z was calling redo() instead of undo(); fixed and added
missing Ctrl+Shift+Z/Cmd+Shift+Z and Ctrl+Y redo bindings
- Wrap all async keybind handlers in try/finally so isProcessing is
always cleared even if undo/redo/copy/paste throws
- Fix === typo in disableHotkeyInput (comparison instead of assignment),
and use full value replacement so the Preact signal update is reactive
- Quick actions on gallery cards: Recreate, Share, and Download buttons
now appear on hover directly on every image/video card, so users can act
on content without opening the lightbox first.
- Media type and model badges are larger and restyled for better
readability on thumbnails.
- The library page now uses the same card component as the generation
gallery, so any future card improvements land everywhere at once.
- Library grid tightened from up to 6 columns to a cleaner max of 5 per
row.
- Removed the dotted background on the library page so skeletons and
cards sit on a solid surface.
## 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
## Website bug fixes: Seedance Fast + Characters + Toasts
### Create Video page
- Switching between Seedance and Seedance Fast no longer loses your
attached references
- Seedance Fast now supports Characters (was missing from the UI)
- Characters work in both Keyframe and Reference modes
- Fixed a bug where typing `@Bob2` could accidentally attach an older
character named `@Bob`
### Task queue notifications
- No more ghost "job complete" toasts every time you reload the site
- Cleaner wording: "Image creation complete" instead of "Image complete
— Image Generation"
- Character jobs now say "Character" instead of the generic "Task"
### Toast redesign
- Matches the prompt box style instead of the old bright green
- Success = green circle with a white checkmark, error = red circle with
a white exclamation
- Centered on mobile, stays top-right on desktop
- Message no longer awkwardly wraps onto multiple lines on mobile
Should have done this all along for ease of development.
- Gets rid of refs between data structures.
- Clones won't impact our SLA or performance at our scale, this isn't
systems programming.
getCurrentAspectRatioIcon() was reading from aspectRatioList (useState),
which is only updated on manual picker clicks, not external store
writes. Reading aspectRatio from the store directly fixes the stale
icon.
## Video reference library picker
- Added "Pick from library" option to the Video Ref slot, with gallery
filtered to videos
- Added loading state to "Use selected" button in gallery modal
- Enforced seedance-2 limits (max 3 videos / 15s total) for both upload
and library flows, with a deduped video-ref-limit toast so users see a
warning on count overflow, duration overflow, or submitting when already
at max
Two bugs were preventing @image / @video / @character references from
being tagged in prompts written with Chinese, Japanese, or Korean text:
1. The mention-highlight regex was built with just the "g" flag, so a
user-typed "@image6" never matched the canonical label "@Image6" and the
reference was rendered as plain text. Make the regex case-insensitive
and do the colorMap lookup via a lowercase-keyed table so "@image6"
still finds the color for "@Image6".
2. detectMention required the character immediately before "@" to match
/\s/, which never matches CJK characters. Typing "@" right after a
Chinese character like "从" therefore never opened the autocomplete
dropdown, leaving users to type the mention by hand (which then hit bug
1). Replace the whitespace check with a "not an ASCII identifier char"
check so CJK, quotes, and punctuation all count as valid word boundaries
before an @mention.
- **Characters for Seedance 2.0** - Character creation/selection modal,
@-mention support with colored inline highlights via contentEditable
MentionTextarea, character tokens sent in video generation API requests
- **Mobile responsive navbar** - Hamburger menu with slide-down panel,
all nav items accessible, credits/library/sign out included
- **Mobile responsive PromptBox** - Tighter padding, stacked toolbar on
small screens, flex-wrap for option buttons
- **Mobile responsive library** - Proper sticky header offset, icon-only
filter tabs on mobile
- **Task queue overhaul** - Prompt text with marquee hover, copy button,
model creator icons, detailed failure reasons on all card types
(matching desktop app)
- **Gallery improvements** - 512px thumbnails (was 250px), 12px grid
gap, model + media type badges on hover, model icon + prompt on
in-progress cards
- **Quality selector** - QualityPicker for image models
(High/Medium/Low), wired to cost estimate and generation API
- **Video batch enqueue** - Multiple videos enqueue as separate jobs so
each completes independently
- **Form consistency** - All auth pages use shared `<Input>` component,
`<form onSubmit>` for Enter key, consistent styling
- **Bug fixes** - Lightbox type error, library carousel disabled,
popover icon alignment, end frame library picker, image ref max default
(1 → 6), task queue mobile overflow, clear session button removed
- **Mobile model selector** - Model picker now appears in the PromptBox
toolbar on mobile/tablet (`lg:hidden`), since the floating bottom-left
selector is desktop-only
- **Responsive image prompt row** - Start frame and end frame sections
stack vertically on mobile with a horizontal divider, instead of
side-by-side
- **Responsive lightbox** - Stacks vertically on mobile (media top, info
below), nav arrows always visible (not hover-only), sidebar goes
full-width, darker backdrop (`bg-black/80`)
- **Safari video inline playback** - Lightbox videos use
`disablePictureInPicture`, `controlsList` restrictions, and
vendor-prefixed attributes to prevent fullscreen/PiP pop-out on Safari
When listUserMediaFiles fails (HTTP error, network error, null
reference, or any other exception), the gallery now shows the error
message with a "Copy error" button instead of silently falling through
to "No items yet".
This is a diagnostic aid for a macOS-only blank gallery bug where the
root cause is unknown (CORS, session cookie, or runtime exception) and
there is no access to production logs.
setInitialLoading(true) and setUsername(name) are batched together so
React never produces a frame where username is set but initialLoading is
still false, which would fall through to the No items yet branch before
refreshGallery has a chance to set initialLoading(true) itself.
This is a substantial number of tests to ensure we don't screw up parameters when migrating over to "omni image generation" from the distinct one-off, model-by-model endpoints.
- Remove useless forceFilterRef and its stale useEffect; use forceFilter
prop directly in JSX
- Show skeleton immediately while username is being fetched (!username
condition) instead of flashing "No items yet"
- Track username fetch failure with usernameError state and surface an
error message with a working Retry button
- Fix retry: setUsername("") was a no-op (same value → React bails out);
use usernameRetryCount in deps to actually re-trigger the fetch
- Guard error UI with allItems.length === 0 so stale items stay visible
if a retry fails mid-session
Notable behavior:
- Mixing supported and/or unsupported file types will only keep files of
file type that match the first supported file type in the list received
from browser (e.g. PNG, OBJ, EXE, JPG, OBJ order selection of files will
only show PNG, JPG for image upload)
- Removing files until only one is left will result in the sidebar
vanishing and only a single preview showing. More files can be added by
selecting multiple files again through the file selection button
- Failure to upload has been handled but not tested. It is expected any
failed uploads could be retried with a "retry failed" button. Any
individual fail can also be retried via a retry button upon hovering on
the exclamation mark by the failed file's name
- All multi-file uploads are queued async and likely wouldn't be ordered
as shown in the preview list (first-come-first-serve)
- Potentially deadlocks if a task never responds. Likely handled by
backend timeouts. This deadlock state is only partial and not lossy as
successfully uploaded files will appear in gallery even if the task is
cancelled. Cancellation can be attempted at any point during the upload.
Uploads shown in progress may be successful after cancellation depending
on the internal upload progress.
- TopBar upload images button now opens the image modal instead for
consistency and same-component use.
---------
Co-authored-by: bflatastic <bombayapirat@gmail.com>
## Summary
- **Fix blank screen crash** during video recreate flow: typing text,
adjusting time slider, typing again, then clicking the text box caused
an uncaught DOM exception in MentionTextarea's contentEditable caret
operations, which unmounted the entire React tree (no error boundaries
existed)
- **Fix text selection disappearing** on mouse release in
MentionTextarea: click handler and sync useEffect were destroying active
selections by rewriting innerHTML and restoring only a collapsed caret
- **Add React Error Boundary** around PromptBoxVideo as a safety net
## Changes
### MentionTextarea.tsx
- Wrap `getCaretOffset`, `setCaretOffset`, `scrollCaretIntoView`,
`handleInput`, sync `useEffect`, and `getOffsetRect` in try/catch to
prevent uncaught DOMExceptions
- Add null guard for `childNodes` iteration and `parentNode` access
- Skip `detectMention` in `handleClick` when user has active text
selection
- Skip innerHTML rewrite in sync useEffect when editor has selected text
### PromptBoxVideo.tsx
- Stabilize `characterNames` with `useMemo` to prevent `mentionColorMap`
from rebuilding every render
- Move empty characters array to module-level constant for stable
reference
- Add cleanup `useEffect` for `durationTimerRef` to prevent stale timer
after unmount
### New: PromptBoxErrorBoundary.tsx
- React Error Boundary with "Try again" recovery button
### ImageToVideo.tsx
- Wrap `<PromptBoxVideo>` with `<PromptBoxErrorBoundary>`
## Reproduction steps (original bug)
1. Click "Recreate" on a video
2. Type text into the prompt box
3. Adjust the time slider
4. Type text again
5. Adjust the time slider and click the text box → **UI goes blank**
## Test plan
- [x] Reproduced blank screen bug on Mac before fix
- [x] Verified fix: repeated recreation flow no longer causes blank
screen
- [x] Verified text selection persists after mouse release
- [x] Video generation with @mentions, reference images, and duration
changes works correctly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of looping within the image set, pressing Right on the last
image advances to the next set and Left on the first image goes to the
previous set, consistent with Up/Down behavior.
- was still getting dozens of pages from these two endpoints
- fixed the error chain so this is semantically correct
- improved code quality of legacy code/endpoints
On Windows production builds, convertFileSrc() generates
https://asset.localhost/... URLs. The connect-src CSP only had "asset:"
(covering asset:// on macOS/Linux) but not https://asset.localhost,
causing the fetch() in GlobalFileDropHandler to be silently blocked —
overlay appeared but no modal opened on drop.
- grouped fail message with fail reason under one tooltip for cleaner ui
- prompt / copy prompt for every type of task card
- larger thumbnails, wider task queue dropdown
- removed prompt hover on card thumbnails
- marquee css updated
- other styling cleanups
- Copy button on failed task cards copies the prompt to clipboard with a
3s check/green feedback
- Prompt preview row with marquee scroll on hover (only when text
overflows), tooltip showing full prompt up to 300 chars
- Fix dismiss and action button icon centering across all card types
(InProgress, Completed, Failed)
- Add marquee keyframe animation to tailwind.config.ts
PageEdit was never rendered in the app. Removes all PageEdit files
(~4000 lines) and migrates the shared types (BaseSelectorImage,
ImageBundle) and HistoryStack component to @storyteller/ui-pagedraw
which already owns the canonical definitions.
Storyboy, Story Girl, Knight, and News Anchor are now pinned to the
front of the Characters row in that order. AI Trump is moved from
Characters into Memes. Both are resolved client-side after the featured
items fetch by matching on lowercased item names.
<img width="1219" height="790"
alt="{A15AF77F-C555-4C85-BC15-B4B8D44D2248}"
src="https://github.com/user-attachments/assets/ec4b52a2-68c4-42b0-88fc-00ee3b322321"
/>
A "recreate" button in the media lightbox modal.
On clicked, it will prefill the image or video promptbox with the same
model, prompt and ref images that was used to generate that media the
button was clicked from.
Temporary and doesnt support seedance video/audio ref yet.
- move a lot of "deprecated" crates so we don't use them
- kill several dead crates
- rename crates to be more semantic/clear
- roll back broken sha3 version bump
Paint/Mask cursor now draws properly regardless of canvas scale and drag
offset.
Inpainting now has its own brush with add and subtract actions.
A total node array is used for ordered-rendering of nodes, which fixes
the layer forward/backward operations.
3D nodes and drawings on the canvas were missing from the reference
image sent to the model in the default generation path. The fix mirrors
the Nano Banana Pro approach: render a composite of the base image and
the draw layer, upload it via uploadSceneSnapshot, and pass it as
scene_image_media_token rather than putting editedImageToken in
image_media_tokens.
Possible problem in our Seedance 2.0 provider means we need a backup.
This is here in case we need it.
Muapi is inferior for several reasons:
- Videos contain watermarks
- Videos cost 2x as much
- Not as many aspect ratio controls
- Not as many duration controls
- No starting frame / ending frame
- No video references
- No audio references
It does seem fast, however.
Generous use of caching used for react elements. Made a pretty
significant difference too.
Minor refactor to move helper functions outside instead of within
component tree.
3D objects can now be drag-n-dropped from gallery onto 2D canvas, with
support for scale, rotation and FOV changes.
History is tracks so changes support undo/redo as well.
We're totally blind. Google Analytics helps a little bit, but we need to
know which communities find ArtCraft the most valuable. (Am I wasting my
time posting to Reddit?)
- failed tasks to show in image generation session view
- failed tasks now show in task queue dropdown
- completed and failed tasks clearable on "clear all" click
- styling updates
## Library/Gallery Modal Improvements
### Performance
- Library items are cached so reopening the modal is instant instead of
loading from scratch every time
- Only visible sections are rendered - off-screen groups are swapped out
as you scroll, keeping things smooth even with lots of media
- Username session is cached to skip a redundant API call on every open
### UX
- Replaced loading spinner with a skeleton grid placeholder
- New items from completed generations appear at the top without wiping
the library
- Library auto-refreshes when a generation finishes (if open)
- Refresh icon spins while refreshing
### UI Refinements: Topbar, Menu, and Tooltips
- **Top bar organization:** Removed the extra logo from the top bar and
added the Artcraft icon to the center text for a cleaner layout.
- **Menu interaction:** Improved the behavior of the menu icon selector
so that confusing tooltips no longer appear when the menu is temporarily
disabled (e.g., during product switching).
- **Tooltip positioning:** Fixed an issue where tooltips were
occasionally extending beyond the edges of the screen, ensuring they now
stay fully visible.
- shows credits to be deducted on generate buttons
- credits usage modal to show costs in detail (toggleable from bottom
right)
Do not merge. Awaiting backend cost calculation implementation.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
**Lightbox & Gallery Navigation Improvements**
- Added prev/next navigation buttons to lightbox (hover-visible arrows
on left/right of image)
- Added arrow key (←/→) keyboard navigation between gallery items in
lightbox
- Gallery builds a flat ordered item list to power sequential lightbox
navigation
- Debounced prompt/batch API calls (180ms) to prevent spam when rapidly
navigating
- Stale in-flight responses are cancelled so wrong data never flashes
- Added shimmer loading skeletons for prompt, reference images, and
generation details
- Removed focus outlines on nav buttons and modal when using arrow keys
- Fixed toggleBulkSelect used-before-declaration error in gallery modal
- Added display name for flux_dev_juggernaut
- fixed the bug where long prompts overflow the lighbox modal causing
buttons to be pushed off the screen and non interactable.
- added a generic billing popup if user doesn't have a subscription plan
We need to remove irrelevant FakeYou records that do not impact the
ArtCraft product
- 87M irrelevant media file records (no relation to ArtCraft)
- 105M jobs records (which we purge occasionally anyway)
- lots of other files: audit logs, old/irrelevant stripe event logs,
feed data, etc. etc.
Two reasons for this:
- super brittle when pinned to non-main commit
e1a70095c4409478c1ded499fb8dd4afa540fb90
- I was recently stuck on a flight without good wifi and setting up a
new devenv. Cargo was totally unable to download the remote github
dependency, but was fine downloading the entire artcraft repo. Seems bad
to link to other repos that Cargo can't quickly pull on low bandwidth.
We'll need to slim this down later and eventually get back on main
branch.
- in the library/gallery modal, added a checkbox on top left of each
item
- when checkbox is checked it enters toiggle select mode which allows
multi selecting and bulk deleting objects via one delete button on the
bottom right of the modal.
- added trash can icon to lightbox delete button
New endpoints:
- checkout flow with integrated account creation (!!!)
- change password
- change email
New updates:
- Session info handler returns onboarding state
- added login / sign up and pricing flow
- download page in nav and signup and pricing links/buttons
currently missing features:
- forget password function
- google social login
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Bumps [pnpm](https://github.com/pnpm/pnpm/tree/HEAD/pnpm) from 10.24.0
to 10.27.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pnpm/pnpm/releases">pnpm's
releases</a>.</em></p>
<blockquote>
<h2>pnpm 10.27</h2>
<h2>Minor Changes</h2>
<ul>
<li>
<p>Adding <code>trustPolicyIgnoreAfter</code> allows you to ignore trust
policy checks for packages published more than a specified time ago<a
href="https://redirect.github.com/pnpm/pnpm/issues/10352">#10352</a>.</p>
</li>
<li>
<p>Added project registry for global virtual store prune support.</p>
<p>Projects using the store are now registered via symlinks in
<code>{storeDir}/v10/projects/</code>. This enables <code>pnpm store
prune</code> to track which packages are still in use by active projects
and safely remove unused packages from the global virtual store.</p>
</li>
<li>
<p><strong>Semi-breaking.</strong> Changed the location of unscoped
packages in the virtual global store. They will now be stored under a
directory named <code>@</code> to maintain a uniform 4-level directory
depth.</p>
</li>
<li>
<p>Added mark-and-sweep garbage collection for global virtual store.</p>
<p><code>pnpm store prune</code> now removes unused packages from the
global virtual store's <code>links/</code> directory. The algorithm:</p>
<ol>
<li>Scans all registered projects for symlinks pointing to the
store</li>
<li>Walks transitive dependencies to mark reachable packages</li>
<li>Removes any package directories not marked as reachable</li>
</ol>
<p>This includes support for workspace monorepos - all
<code>node_modules</code> directories within a project (including those
in workspace packages) are scanned.</p>
</li>
</ul>
<h2>Patch Changes</h2>
<ul>
<li>Throw an error if the value of the <code>tokenHelper</code> or
<code><url>:tokenHelper</code> setting contains an environment
variable.</li>
<li>Git dependencies with build scripts should respect the
<code>dangerouslyAllowAllBuilds</code> settings <a
href="https://redirect.github.com/pnpm/pnpm/issues/10376">#10376</a>.</li>
<li>Skip the package manager check when running with --global and a
project packageManager is configured, and warn that the check is
skipped.</li>
<li><code>pnpm store prune</code> should not fail if the dlx cache
directory has files, not only directories <a
href="https://redirect.github.com/pnpm/pnpm/pull/10384">#10384</a></li>
<li>Fixed a bug (<a
href="https://redirect.github.com/pnpm/pnpm/issues/9759">#9759</a>)
where <code>pnpm add</code> would incorrectly modify a catalog entry in
<code>pnpm-workspace.yaml</code> to its exact version.</li>
</ul>
<h2>Platinum Sponsors</h2>
<!-- raw HTML omitted -->
<h2>Gold Sponsors</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/pnpm/pnpm/blob/v10.27.0/pnpm/CHANGELOG.md">pnpm's
changelog</a>.</em></p>
<blockquote>
<h2>10.27.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p>Adding <code>trustPolicyIgnoreAfter</code> allows you to ignore trust
policy checks for packages published more than a specified time ago<a
href="https://redirect.github.com/pnpm/pnpm/issues/10352">#10352</a>.</p>
</li>
<li>
<p>Added project registry for global virtual store prune support.</p>
<p>Projects using the store are now registered via symlinks in
<code>{storeDir}/v10/projects/</code>. This enables <code>pnpm store
prune</code> to track which packages are still in use by active projects
and safely remove unused packages from the global virtual store.</p>
</li>
<li>
<p><strong>Semi-breaking.</strong> Changed the location of unscoped
packages in the virtual global store. They will now be stored under a
directory named <code>@</code> to maintain a uniform 4-level directory
depth.</p>
</li>
<li>
<p>Added mark-and-sweep garbage collection for global virtual store.</p>
<p><code>pnpm store prune</code> now removes unused packages from the
global virtual store's <code>links/</code> directory. The algorithm:</p>
<ol>
<li>Scans all registered projects for symlinks pointing to the
store</li>
<li>Walks transitive dependencies to mark reachable packages</li>
<li>Removes any package directories not marked as reachable</li>
</ol>
<p>This includes support for workspace monorepos - all
<code>node_modules</code> directories within a project (including those
in workspace packages) are scanned.</p>
</li>
</ul>
<h3>Patch Changes</h3>
<ul>
<li>Throw an error if the value of the <code>tokenHelper</code> or
<code><url>:tokenHelper</code> setting contains an environment
variable.</li>
<li>Git dependencies with build scripts should respect the
<code>dangerouslyAllowAllBuilds</code> settings <a
href="https://redirect.github.com/pnpm/pnpm/issues/10376">#10376</a>.</li>
<li>Skip the package manager check when running with --global and a
project packageManager is configured, and warn that the check is
skipped.</li>
<li><code>pnpm store prune</code> should not fail if the dlx cache
directory has files, not only directories <a
href="https://redirect.github.com/pnpm/pnpm/pull/10384">#10384</a></li>
<li>Fixed a bug (<a
href="https://redirect.github.com/pnpm/pnpm/issues/9759">#9759</a>)
where <code>pnpm add</code> would incorrectly modify a catalog entry in
<code>pnpm-workspace.yaml</code> to its exact version.</li>
</ul>
<h2>10.26.2</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p>Improve error message when a package version exists but does not meet
the <code>minimumReleaseAge</code> constraint. The error now clearly
states that the version exists and shows a human-readable time since
release (e.g., "released 6 hours ago") <a
href="https://redirect.github.com/pnpm/pnpm/issues/10307">#10307</a>.</p>
</li>
<li>
<p>Fix installation of Git dependencies using annotated tags <a
href="https://redirect.github.com/pnpm/pnpm/issues/10335">#10335</a>.</p>
<p>Previously, pnpm would store the annotated tag object's SHA in the
lockfile instead of the actual commit SHA. This caused
<code>ERR_PNPM_GIT_CHECKOUT_FAILED</code> errors because the checked-out
commit hash didn't match the stored tag object hash.</p>
</li>
<li>
<p>Binaries of runtime engines (Node.js, Deno, Bun) are written to
<code>node_modules/.bin</code> before lifecycle scripts (install,
postinstall, prepare) are executed <a
href="https://redirect.github.com/pnpm/pnpm/issues/10244">#10244</a>.</p>
</li>
<li>
<p>Try to avoid making network calls with preferOffline <a
href="https://redirect.github.com/pnpm/pnpm/pull/10334">#10334</a>.</p>
</li>
</ul>
<h2>10.26.1</h2>
<h3>Patch Changes</h3>
<ul>
<li>Don't fail on <code>pnpm add</code>, when
<code>blockExoticSubdeps</code> is set to <code>true</code> <a
href="https://redirect.github.com/pnpm/pnpm/issues/10324">#10324</a>.</li>
<li>Always resolve git references to full commits and ensure
<code>HEAD</code> points to the commit after checkout <a
href="https://redirect.github.com/pnpm/pnpm/pull/10310">#10310</a>.</li>
</ul>
<h2>10.26.0</h2>
<h3>Minor Changes</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/pnpm/pnpm/commit/6bdba72ad31e4d6b79821405e09c6bdcc93894ee"><code>6bdba72</code></a>
chore(release): 10.27.0</li>
<li><a
href="https://github.com/pnpm/pnpm/commit/512f18817d610ae83646c4f864089710e1a28f17"><code>512f188</code></a>
fix: git dependencies respect <code>dangerouslyAllowAllBuilds</code> (<a
href="https://github.com/pnpm/pnpm/tree/HEAD/pnpm/issues/10387">#10387</a>)</li>
<li><a
href="https://github.com/pnpm/pnpm/commit/c23bdf7311cdc5763e2c9527e1b0a130448301c4"><code>c23bdf7</code></a>
feat(cli): skip package manager check when using --global option (<a
href="https://github.com/pnpm/pnpm/tree/HEAD/pnpm/issues/10368">#10368</a>)</li>
<li><a
href="https://github.com/pnpm/pnpm/commit/3f2c5f4d3958e6a150f734917eabd6ad88452e98"><code>3f2c5f4</code></a>
feat: add <code>trustPolicyIgnoreAfter</code> (<a
href="https://github.com/pnpm/pnpm/tree/HEAD/pnpm/issues/10359">#10359</a>)</li>
<li><a
href="https://github.com/pnpm/pnpm/commit/8ec7939657b78167c2753f05dacd62feabd59180"><code>8ec7939</code></a>
chore(release): 10.26.2</li>
<li><a
href="https://github.com/pnpm/pnpm/commit/4986c46b48f48ae7c5aeef2aca90820ab7b3775c"><code>4986c46</code></a>
chore(release): 10.26.1</li>
<li><a
href="https://github.com/pnpm/pnpm/commit/244e33b4e963b257952dadf192d829bfec858fb9"><code>244e33b</code></a>
chore(release): 10.26.0</li>
<li><a
href="https://github.com/pnpm/pnpm/commit/40775391d54d400c7d516bffaca667215a5e472d"><code>4077539</code></a>
fix(git-fetcher): block git dependencies from running prepare scripts
unless ...</li>
<li><a
href="https://github.com/pnpm/pnpm/commit/b0cd2dea489e28162a233cf02af3646cdd57bb8c"><code>b0cd2de</code></a>
chore(release): 10.25.0</li>
<li><a
href="https://github.com/pnpm/pnpm/commit/fa82ec393a2627fee47956b639b6ce57a4679318"><code>fa82ec3</code></a>
docs: update sponsors</li>
<li>Additional commits viewable in <a
href="https://github.com/pnpm/pnpm/commits/v10.27.0/pnpm">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/storytold/artcraft/network/alerts).
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Only allow free users one Nano Banana (Pro) or GPT Image at a time as
this can lead to high costs.
Users can bring their own subscription or API key, or they can subscribe
to ArtCraft to bypass this.
- refactored all markdown content as a shared library for use across
both the ArtCraft website and the app (FAQ, tutorials, news)
- added a news page to the website
- added a news & updates option to the app's bottom right help context
menu
Instructions to add and build News Posts for both the site and app is in
``NEWS_POSTS.md`` but in short:
- To add a new news post, create an .md file in
``libs/markdown-content/src/lib/content/news/`` folder
- Restart the artcraft-website dev server after adding new posts (or in
/frontend directory, run ``node scripts/generate-news-json.mjs``
manually) - this will update the public ``news.json`` file on the
website.
- This is just a temporary workaround - this way if the ArtCraft app
itself hasn't updated, it'll still be able to load the latest public
``news.json`` file from the website to show within the app.
**UX: Improve video generation validation feedback**
- Enable "Generate" button interactions to provide actionable error
toasts instead of silent disabling.
- Add inline "Starting frame required" warning with icon when a
reference image is missing.
- Update tooltip component to support conditional disabling for better
UX control.
- working red marker
- aspect ratio and resolution buttons
- styling fixes, canvas centering/padding
- nano banana not stuck at 1 image per generation anymore
- context images working for image edit
- Disabled Outline Pass
- Disabled SAO Pass
- Disabled Bloom Pass
- Disabled SMAA Pass
SAO and Outline passes are necessary to disable for adequate
performance. No idea why.
edit product ui refactor:
- nano banana model now has marker and eraser
- marker color can be changed, all brush, marker and eraser can be size
adjusted separately
- flux ones have edit region and eraser (removed select since i dont
think thats neccessary)
- smooth fade between history image switches
Note: this should not be necessary. Nx/Vite should build all of this for
us. Something in our build configuration fails to do this, which causes
us to need to build these packages.
- clicking from text to image page results now shows action buttons in
lightbox
- resizing modal from sides or corners that are not bottom or right now
doesn't move the modal once it reaches min size
- limited the min size of the lightbox modal so that the layout doesnt
break
- colors will match selected theme now
- buttons relayout and added "turn into video" button for images that
will take you to the Image to Video page
- reference image matches imapge prompt row component now - opens full
preview in a separate resizable modal
Fal is causing really inflated build slowdowns on account of the library
using so much Serde/proc macros. We really need to rethink FAL client
workings before adding it.
shared component for tutorials
app:
- tutorial modal accessible from any page on the app on the bottom right
- plays right in the modal through youtube embed
- it remembers the play progress and saves it in the zustand state so in
that current session if modal is opened again, it will continue where
the user left off
website:
- navbar updates (home and tutorials link)
- tutorial page with category buttons to filter by product or show all
- plays through youtube embed on website page
```
Run # Build the frontend app from the correct directory
Building with npx nx...
NX Failed to process project graph.
An error occurred while processing files for the @nx/eslint/plugin
plugin (Defined at nx.json#plugins[4])
.
- apps/editor3d/.eslintrc.cjs: Failed to load plugin 'storybook'
declared in '.eslintrc.cjs': Cannot find module
'eslint-plugin-storybook'
Require stack:
-
D:\a\storyteller-rust\storyteller-rust\frontend\apps\editor3d\__placeholder__.js
Referenced from:
D:\a\storyteller-rust\storyteller-rust\frontend\apps\editor3d\.eslintrc.cjs
```
- huge css changes throughout app to support theme switching
- 5 themes: light (white), gray(default), black, and gradient themes:
aurora and sunset
note:
- light theme still needs a second pass since some styling / text color
on white isn't fully updated yet there's some area that hasn't been
covered but most have been
- added 6 memes models and custom thumbnails
- upload modal now has checkbox to allow character uploads
- gallery drag and drop now works properly for characters
- make top image prompt row always show for video
- video promptbox pops a toast when reference image is empty
- description changes for ending frame section
- hid the add image button for video generation for now since that's
almost always required
- video prompt box now supports image prompt (starting and ending frame)
- shared image prompts section between image / video generation prompt
boxes
- video history / pending generation like text to image
- background change
- the history stack is commented out for now because backend doesnt
support it yet.
- reverted to previous ux
- added placeholder loaders for generating images (matched by frontend
subscriber id)
- can now generate multiple images at a time
- auto scrolls to top of history stack on enqueue
- image prompts linked to models (maximum and if it's allowed or not)
- lots of styling updates
- promptbox doesn't overflow bottom anymore (dynamic height detection)
- selected image prompts can be clicked to be previewed in a modal
<img width="938" height="472" alt="image"
src="https://github.com/user-attachments/assets/e4fc7445-0031-4adf-a6f6-dbcbe1c3ea67"
/>
- @echelon on handleEnqueue - image prompts need to be added
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
"query":"\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n\n jobs.status as `status: enums::common::job_status_plus::JobStatusPlus`,\n jobs.attempt_count,\n\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n\n jobs.product_category as `product_category: enums::by_table::generic_inference_jobs::inference_job_product_category::InferenceJobProductCategory`,\n jobs.inference_category as `inference_category: enums::by_table::generic_inference_jobs::inference_category::InferenceCategory`,\n jobs.maybe_model_type,\n jobs.maybe_model_token,\n jobs.maybe_raw_inference_text,\n jobs.maybe_inference_args,\n\n jobs.frontend_failure_category as `maybe_frontend_failure_category: enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory`,\n\n jobs.on_success_result_entity_type as maybe_result_entity_type,\n jobs.on_success_result_entity_token as maybe_result_entity_token,\n jobs.on_success_result_batch_token as maybe_result_batch_token,\n\n model_weights.title as maybe_model_weights_title,\n tts_models.title as maybe_tts_model_title,\n voice_conversion_models.title as maybe_voice_conversion_model_title,\n\n tts_results.public_bucket_wav_audio_path as maybe_tts_public_bucket_path,\n voice_conversion_results.public_bucket_hash as maybe_voice_conversion_public_bucket_hash,\n\n media_files.public_bucket_directory_hash as maybe_media_file_public_bucket_directory_hash,\n media_files.maybe_public_bucket_prefix as maybe_media_file_public_bucket_prefix,\n media_files.maybe_public_bucket_extension as maybe_media_file_public_bucket_extension,\n\n jobs.assigned_worker as maybe_assigned_worker,\n jobs.assigned_cluster as maybe_assigned_cluster,\n\n jobs.is_keepalive_required,\n\n jobs.created_at,\n jobs.updated_at,\n\n jobs.first_started_at as maybe_first_started_at,\n jobs.successfully_completed_at as maybe_successfully_completed_at,\n\n NOW() as database_clock\n\nFROM generic_inference_jobs as jobs\n\nLEFT OUTER JOIN model_weights ON jobs.maybe_model_token = model_weights.token\nLEFT OUTER JOIN tts_models ON jobs.maybe_model_token = tts_models.token\nLEFT OUTER JOIN voice_conversion_models ON jobs.maybe_model_token = voice_conversion_models.token\n\nLEFT OUTER JOIN tts_results ON jobs.on_success_result_entity_token = tts_results.token\nLEFT OUTER JOIN voice_conversion_results ON jobs.on_success_result_entity_token = voice_conversion_results.token\nLEFT OUTER JOIN media_files ON jobs.on_success_result_entity_token = media_files.token\n\nWHERE jobs.token = ?\n ",
"query":"\n SELECT\n token as `token: tokens::tokens::wallets::WalletToken`\n FROM wallets\n WHERE owner_user_token = ?\n AND wallet_namespace = ?\n ORDER BY id ASC\n LIMIT 1\n ",
"query":"\nSELECT\n u.id as `id: u64`,\n u.token as `token: tokens::tokens::users::UserToken`,\n u.username,\n u.display_name,\n u.username_is_not_customized as `username_is_not_customized: bool`,\n u.email_address,\n u.email_confirmed as `email_confirmed: bool`,\n u.is_without_password as `is_without_password: bool`,\n u.ip_address_creation,\n u.maybe_source as `maybe_source: enums::by_table::users::user_signup_source::UserSignupSource`,\n u.maybe_signup_method as `maybe_signup_method: enums::by_table::users::user_signup_method::UserSignupMethod`,\n u.created_at,\n u.maybe_referral_url,\n u.is_temporary as `is_temporary: bool`\nFROM users AS u\nWHERE\n u.id < ?\n AND u.user_deleted_at IS NULL\n AND u.mod_deleted_at IS NULL\nORDER BY u.id DESC\nLIMIT ?\n ",
"query":"\n UPDATE tasks\n SET task_status = \"cancelled_by_user\"\n WHERE task_status NOT IN (\n \"complete_success\",\n \"complete_failure\",\n \"dead\",\n \"cancelled_by_user\",\n \"cancelled_by_provider\",\n \"cancelled_by_us\"\n )\n ",
"query":"\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n\n jobs.status as `status: enums::common::job_status_plus::JobStatusPlus`,\n\n jobs.maybe_external_third_party as `external_third_party: enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty`,\n jobs.maybe_external_third_party_id as `external_third_party_id`,\n\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n\n jobs.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n jobs.maybe_debug_log_event_token as `maybe_debug_log_event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken`,\n\n jobs.created_at,\n jobs.updated_at\n\nFROM generic_inference_jobs as jobs\n\nWHERE jobs.maybe_external_third_party = ?\nAND jobs.maybe_external_third_party_id = ?\n ",
"query":"\nSELECT\n user_subscriptions.subscription_namespace as `subscription_namespace: enums::common::payments_namespace::PaymentsNamespace`,\n user_subscriptions.maybe_stripe_subscription_id,\n users.token as `user_token: tokens::tokens::users::UserToken`,\n users.email_address,\n users.username,\n users.display_name\nFROM user_subscriptions\nINNER JOIN users ON users.token = user_subscriptions.user_token\nWHERE\n user_subscriptions.maybe_stripe_customer_id = ?\n AND user_subscriptions.deleted_at IS NULL\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\nORDER BY user_subscriptions.created_at DESC\n ",
"query":"\nSELECT\n w.token as `token: tokens::tokens::wallets::WalletToken`,\n w.wallet_namespace as `wallet_namespace: enums::common::payments_namespace::PaymentsNamespace`,\n w.owner_user_token as `owner_user_token: tokens::tokens::users::UserToken`,\n w.banked_credits as `banked_credits: u32`,\n w.monthly_credits as `monthly_credits: u32`,\n w.version,\n w.created_at as `created_at: DateTime<Utc>`,\n w.updated_at as `updated_at: DateTime<Utc>`\nFROM wallets as w\nWHERE w.token = ?\nLIMIT 1\n ",
"query":"\nSELECT\n user_token,\n subscription_namespace,\n subscription_product_slug,\n subscription_expires_at\n\nFROM user_subscriptions\n\nWHERE\n user_token = ?\n AND subscription_expires_at > CURRENT_TIMESTAMP\n ORDER BY id ASC\n ",
"query":"\nSELECT\n p.token as `token: tokens::tokens::prompts::PromptToken`,\n\n p.prompt_type as `prompt_type: enums::by_table::prompts::prompt_type::PromptType`,\n\n p.maybe_model_type as `maybe_model_type: enums::common::generation::common_model_type::CommonModelType`,\n p.maybe_generation_provider as `maybe_generation_provider: enums::common::generation_provider::GenerationProvider`,\n\n p.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n\n p.maybe_positive_prompt,\n p.maybe_negative_prompt,\n\n p.maybe_generation_mode as `maybe_generation_mode: enums::common::generation::common_generation_mode::CommonGenerationMode`,\n p.maybe_aspect_ratio as `maybe_aspect_ratio: enums::common::generation::common_aspect_ratio::CommonAspectRatio`,\n p.maybe_resolution as `maybe_resolution: enums::common::generation::common_resolution::CommonResolution`,\n p.maybe_batch_count as `maybe_batch_count: u8`,\n p.maybe_generate_audio as `maybe_generate_audio: bool`,\n p.maybe_duration_seconds as `maybe_duration_seconds: u32`,\n\n p.maybe_other_args,\n\n p.creator_ip_address,\n p.created_at\n\nFROM prompts as p\nWHERE\n p.token = ?\n ",
"query":"\nSELECT\n token as `token: tokens::tokens::user_subscriptions::UserSubscriptionToken`,\n user_token as `user_token: tokens::tokens::users::UserToken`,\n subscription_namespace as `subscription_namespace: enums::common::payments_namespace::PaymentsNamespace`,\n subscription_product_slug,\n maybe_stripe_subscription_status,\n maybe_stripe_customer_id,\n maybe_stripe_product_id,\n maybe_stripe_subscription_id\n\nFROM user_subscriptions\n\nWHERE\n user_token = ?\n AND subscription_namespace = ?\n AND maybe_stripe_customer_id IS NOT NULL\n AND maybe_stripe_subscription_id IS NOT NULL\n AND maybe_stripe_subscription_status IS NOT NULL\n AND maybe_stripe_subscription_status NOT IN ('canceled', 'incomplete_expired')\n AND subscription_expires_at > CURRENT_TIMESTAMP\n ORDER BY id ASC\n LIMIT 1\n ",
"query":"\nSELECT\n token as `token: tokens::tokens::user_subscriptions::UserSubscriptionToken`,\n user_token as `user_token: tokens::tokens::users::UserToken`,\n subscription_namespace as `subscription_namespace: enums::common::payments_namespace::PaymentsNamespace`,\n subscription_product_slug,\n maybe_stripe_subscription_status,\n maybe_stripe_customer_id,\n maybe_stripe_product_id,\n maybe_stripe_subscription_id\n\nFROM user_subscriptions\n\nWHERE\n user_token = ?\n AND subscription_namespace = ?\n AND maybe_stripe_customer_id IS NOT NULL\n AND maybe_stripe_subscription_id IS NOT NULL\n AND maybe_stripe_subscription_status IS NOT NULL\n ORDER BY id ASC\n LIMIT 1\n ",
"query":"\nSELECT\n user_token,\n subscription_namespace as `subscription_namespace: enums::common::payments_namespace::PaymentsNamespace`,\n subscription_product_slug,\n subscription_expires_at\n\nFROM user_subscriptions\n\nWHERE\n user_token = ?\n AND subscription_expires_at > CURRENT_TIMESTAMP\n ORDER BY id ASC\n ",
"query":"\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n\n jobs.status as `status: enums::common::job_status_plus::JobStatusPlus`,\n jobs.attempt_count,\n\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n\n jobs.product_category as `product_category: enums::by_table::generic_inference_jobs::inference_job_product_category::InferenceJobProductCategory`,\n jobs.inference_category as `inference_category: enums::by_table::generic_inference_jobs::inference_category::InferenceCategory`,\n jobs.maybe_model_type,\n jobs.maybe_model_token,\n jobs.maybe_raw_inference_text,\n jobs.maybe_inference_args,\n jobs.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n\n jobs.frontend_failure_category as `maybe_frontend_failure_category: enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory`,\n jobs.failure_reason,\n\n jobs.on_success_result_entity_type as maybe_result_entity_type,\n jobs.on_success_result_entity_token as maybe_result_entity_token,\n jobs.on_success_result_batch_token as maybe_result_batch_token,\n\n model_weights.title as maybe_model_weights_title,\n tts_models.title as maybe_tts_model_title,\n voice_conversion_models.title as maybe_voice_conversion_model_title,\n\n tts_results.public_bucket_wav_audio_path as maybe_tts_public_bucket_path,\n voice_conversion_results.public_bucket_hash as maybe_voice_conversion_public_bucket_hash,\n\n media_files.public_bucket_directory_hash as maybe_media_file_public_bucket_directory_hash,\n media_files.maybe_public_bucket_prefix as maybe_media_file_public_bucket_prefix,\n media_files.maybe_public_bucket_extension as maybe_media_file_public_bucket_extension,\n\n jobs.assigned_worker as maybe_assigned_worker,\n jobs.assigned_cluster as maybe_assigned_cluster,\n\n jobs.is_keepalive_required,\n\n jobs.created_at,\n jobs.updated_at,\n\n jobs.first_started_at as maybe_first_started_at,\n jobs.successfully_completed_at as maybe_successfully_completed_at,\n\n NOW() as database_clock\n\nFROM generic_inference_jobs as jobs\n\nLEFT OUTER JOIN model_weights ON jobs.maybe_model_token = model_weights.token\nLEFT OUTER JOIN tts_models ON jobs.maybe_model_token = tts_models.token\nLEFT OUTER JOIN voice_conversion_models ON jobs.maybe_model_token = voice_conversion_models.token\n\nLEFT OUTER JOIN tts_results ON jobs.on_success_result_entity_token = tts_results.token\nLEFT OUTER JOIN voice_conversion_results ON jobs.on_success_result_entity_token = voice_conversion_results.token\nLEFT OUTER JOIN media_files ON jobs.on_success_result_entity_token = media_files.token\n\nWHERE jobs.token = ?\n ",
"query":"\nSELECT\n w.token as `token: tokens::tokens::wallets::WalletToken`,\n w.wallet_namespace as `wallet_namespace: enums::common::payments_namespace::PaymentsNamespace`,\n w.banked_credits as `banked_credits: u32`,\n w.monthly_credits as `monthly_credits: u32`,\n w.version,\n w.created_at as `created_at: DateTime<Utc>`,\n w.updated_at as `updated_at: DateTime<Utc>`\nFROM wallets as w\nWHERE w.owner_user_token = ?\nORDER BY w.id DESC\n ",
"query":"\nSELECT\n user_token as `user_token: tokens::tokens::users::UserToken`,\n payments_namespace as `payments_namespace: enums::common::payments_namespace::PaymentsNamespace`,\n stripe_customer_id\n\nFROM user_stripe_customer_links\n\nWHERE\n user_token = ?\n AND payments_namespace = ?\n ORDER BY id ASC\n LIMIT 1\n ",
"query":"\nSELECT\n token as `token: tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken`,\n wallet_token as `wallet_token: tokens::tokens::wallets::WalletToken`,\n entry_type as `entry_type: enums::by_table::wallet_ledger_entries::wallet_ledger_entry_type::WalletLedgerEntryType`,\n maybe_entity_ref,\n credits_delta,\n banked_credits_before as `banked_credits_before: u32`,\n banked_credits_after as `banked_credits_after: u32`,\n monthly_credits_before as `monthly_credits_before: u32`,\n monthly_credits_after as `monthly_credits_after: u32`,\n created_at as `created_at: DateTime<Utc>`,\n is_refunded as `is_refunded: bool`,\n maybe_linked_refund_ledger_token as `maybe_linked_refund_ledger_token: tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken`\nFROM wallet_ledger_entries\nWHERE wallet_token = ?\nORDER BY id DESC\n ",
"query":"\nSELECT\n u.id as `id: u64`,\n u.token as `token: tokens::tokens::users::UserToken`,\n u.username,\n u.display_name,\n u.username_is_not_customized as `username_is_not_customized: bool`,\n u.email_address,\n u.email_confirmed as `email_confirmed: bool`,\n u.is_without_password as `is_without_password: bool`,\n u.ip_address_creation,\n u.maybe_source as `maybe_source: enums::by_table::users::user_signup_source::UserSignupSource`,\n u.maybe_signup_method as `maybe_signup_method: enums::by_table::users::user_signup_method::UserSignupMethod`,\n u.created_at,\n u.maybe_referral_url,\n u.is_temporary as `is_temporary: bool`,\n us.subscription_namespace,\n us.subscription_product_slug,\n us.maybe_stripe_subscription_id,\n us.maybe_stripe_customer_id,\n us.maybe_stripe_product_id,\n us.maybe_stripe_price_id,\n us.maybe_stripe_subscription_status,\n us.maybe_stripe_recurring_interval,\n us.maybe_stripe_invoice_is_paid as `maybe_stripe_invoice_is_paid: bool`,\n us.maybe_cancel_at,\n us.maybe_canceled_at\nFROM users AS u\nINNER JOIN user_subscriptions AS us ON u.token = us.user_token\nWHERE\n u.id < ?\n AND u.user_deleted_at IS NULL\n AND u.mod_deleted_at IS NULL\nORDER BY u.id DESC\nLIMIT ?\n ",
"query":"\nSELECT\n id as `id: u64`,\n token as `token: tokens::tokens::characters::CharacterToken`,\n character_type as `character_type: enums::by_table::characters::character_type::CharacterType`,\n is_active as `is_active: bool`,\n character_name,\n maybe_description,\n maybe_avatar_media_token as `maybe_avatar_media_token: tokens::tokens::media_files::MediaFileToken`,\n maybe_full_image_media_token as `maybe_full_image_media_token: tokens::tokens::media_files::MediaFileToken`,\n maybe_original_upload_media_token as `maybe_original_upload_media_token: tokens::tokens::media_files::MediaFileToken`,\n maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n kinovi_character_id,\n kinovi_character_name,\n maybe_kinovi_asset_id\nFROM characters\nWHERE maybe_creator_user_token = ?\n AND is_active = true\n AND deleted_at IS NULL\n AND id < ?\nORDER BY id DESC\nLIMIT ?\n ",
"query":"\nSELECT\n users.token as `user_token: tokens::tokens::users::UserToken`,\n username,\n display_name,\n username_is_generated as `username_is_generated: bool`,\n is_temporary as `is_temporary: bool`,\n username_is_not_customized as `username_is_not_customized: bool`,\n email_address,\n email_confirmed as `email_confirmed: bool`,\n email_is_synthetic as `email_is_synthetic: bool`,\n is_without_password as `is_without_password: bool`,\n ip_address_creation,\n ip_address_last_login,\n maybe_avatar_media_file_token as `maybe_avatar_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n email_gravatar_hash,\n is_banned as `is_banned: bool`,\n created_at,\n updated_at\nFROM users\nWHERE\n users.username = ?\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\nLIMIT 1\n ",
"query":"\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n jobs.maybe_external_third_party_id as `kinovi_character_id`,\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`\n\nFROM generic_inference_jobs as jobs\n\nWHERE jobs.maybe_external_third_party = ?\n AND jobs.job_type = ?\n AND jobs.status NOT IN ('complete_success', 'complete_failure')\n AND jobs.maybe_external_third_party_id IS NOT NULL\n\nLIMIT 25000\n ",
"query":"\nSELECT\n token as `token: tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken`,\n entry_type as `entry_type: enums::by_table::wallet_ledger_entries::wallet_ledger_entry_type::WalletLedgerEntryType`,\n maybe_entity_ref,\n credits_delta,\n banked_credits_before as `banked_credits_before: u32`,\n banked_credits_after as `banked_credits_after: u32`,\n monthly_credits_before as `monthly_credits_before: u32`,\n monthly_credits_after as `monthly_credits_after: u32`,\n is_refunded as `is_refunded: bool`,\n maybe_linked_refund_ledger_token as `maybe_linked_refund_ledger_token: tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken`,\n created_at as `created_at: DateTime<Utc>`\nFROM wallet_ledger_entries\nWHERE token = ?\nLIMIT 1\n ",
"query":"\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n jobs.maybe_external_third_party_id as `order_id`,\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n jobs.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n jobs.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n jobs.maybe_wallet_ledger_entry_token as `maybe_wallet_ledger_entry_token: tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken`\n\nFROM generic_inference_jobs as jobs\n\nWHERE jobs.maybe_external_third_party = ?\n AND jobs.job_type = ?\n AND jobs.status NOT IN ('complete_success', 'complete_failure')\n AND jobs.maybe_external_third_party_id IS NOT NULL\n\nLIMIT 25000\n ",
"query":"\nSELECT\n id as `id: u64`,\n event_token as `event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken`,\n debug_log_type as `debug_log_type: enums::by_table::debug_logs::debug_log_type::DebugLogType`,\n maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n message,\n created_at\nFROM debug_logs\nWHERE event_token = ?\nORDER BY id ASC\nLIMIT ?\n ",
"query":"\n SELECT\n token as `token: tokens::tokens::wallets::WalletToken`,\n wallet_namespace as `namespace: enums::common::payments_namespace::PaymentsNamespace`,\n banked_credits,\n monthly_credits\n FROM wallets\n WHERE owner_user_token = ?\n AND wallet_namespace = ?\n ORDER BY id ASC\n LIMIT 1\n ",
"query":"\nSELECT\n username,\n display_name,\n username_is_generated as `username_is_generated: bool`,\n username_is_not_customized as `username_is_not_customized: bool`\nFROM users\nWHERE\n token = ?\n AND user_deleted_at IS NULL\n AND mod_deleted_at IS NULL\nLIMIT 1\n ",
"query":"\nSELECT\n j.status as `job_status: enums::common::job_status_plus::JobStatusPlus`,\n j.failure_reason as job_failure_reason,\n wle.credits_delta,\n wle.maybe_linked_refund_ledger_token as `maybe_linked_refund_ledger_token: tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken`,\n j.on_success_result_entity_token as `on_success_result_media_token: tokens::tokens::media_files::MediaFileToken`,\n j.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n wle.token as `wallet_ledger_entry_token: tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken`,\n wle.entry_type as `wallet_ledger_entry_type: enums::by_table::wallet_ledger_entries::wallet_ledger_entry_type::WalletLedgerEntryType`,\n j.maybe_external_third_party as `maybe_external_third_party: enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty`,\n j.maybe_external_third_party_id,\n j.created_at,\n j.updated_at\nFROM generic_inference_jobs as j\nLEFT OUTER JOIN users as u\n ON j.maybe_creator_user_token = u.token\nLEFT OUTER JOIN wallet_ledger_entries as wle\n ON wle.maybe_entity_ref = j.token\nWHERE u.token = ?\nORDER BY j.id DESC\n ",
"query":"\nSELECT\n users.token as `user_token: tokens::tokens::users::UserToken`,\n username,\n display_name,\n username_is_generated as `username_is_generated: bool`,\n is_temporary as `is_temporary: bool`,\n username_is_not_customized as `username_is_not_customized: bool`,\n email_address,\n email_confirmed as `email_confirmed: bool`,\n email_is_synthetic as `email_is_synthetic: bool`,\n is_without_password as `is_without_password: bool`,\n ip_address_creation,\n ip_address_last_login,\n maybe_avatar_media_file_token as `maybe_avatar_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n email_gravatar_hash,\n is_banned as `is_banned: bool`,\n created_at,\n updated_at\nFROM users\nWHERE\n users.email_address = ?\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\nLIMIT 1\n ",
"query":"\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n jobs.maybe_external_third_party_id as `operation_id`,\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n jobs.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n jobs.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n jobs.maybe_wallet_ledger_entry_token as `maybe_wallet_ledger_entry_token: tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken`\n\nFROM generic_inference_jobs as jobs\n\nWHERE jobs.maybe_external_third_party = ?\n AND jobs.status NOT IN ('complete_success', 'complete_failure')\n AND jobs.maybe_external_third_party_id IS NOT NULL\n\nLIMIT 25000\n ",
"query":"\nSELECT\n id,\n token as `token: MediaFileToken`,\n created_at as `created_at: DateTime<Utc>`,\n maybe_thumbnail_version as `maybe_thumbnail_version: u8`,\n public_bucket_directory_hash,\n maybe_public_bucket_prefix,\n maybe_public_bucket_extension\nFROM media_files\nWHERE\n id >= (SELECT COALESCE(MIN(id), 0) FROM media_files WHERE created_at >= NOW() - INTERVAL ? HOUR)\n AND id < ?\n AND media_class = ?\n AND maybe_thumbnail_version IS NULL\n AND user_deleted_at IS NULL\n AND mod_deleted_at IS NULL\nORDER BY id DESC\nLIMIT ?\n ",
"query":"\nSELECT\n token as `token: tokens::tokens::user_subscriptions::UserSubscriptionToken`,\n user_token as `user_token: tokens::tokens::users::UserToken`,\n subscription_namespace as `subscription_namespace: enums::common::payments_namespace::PaymentsNamespace`,\n subscription_product_slug,\n maybe_stripe_customer_id,\n maybe_stripe_product_id,\n maybe_stripe_subscription_id,\n maybe_stripe_subscription_status as `maybe_stripe_subscription_status: enums::common::stripe_subscription_status::StripeSubscriptionStatus`,\n maybe_stripe_invoice_is_paid,\n subscription_start_at,\n subscription_expires_at,\n current_billing_period_end_at,\n maybe_cancel_at,\n maybe_canceled_at\n\nFROM user_subscriptions\n\nWHERE\n user_token = ?\n AND subscription_namespace = ?\n AND maybe_stripe_customer_id IS NOT NULL\n AND maybe_stripe_subscription_id IS NOT NULL\n AND maybe_stripe_subscription_status IS NOT NULL\n AND maybe_stripe_subscription_status NOT IN ('canceled', 'incomplete_expired')\n AND subscription_expires_at > CURRENT_TIMESTAMP\n ORDER BY id ASC\n LIMIT 1\n ",
"query":"\nSELECT\n uir.id,\n uir.impersonator_user_token as `impersonator_user_token: UserToken`,\n impersonator.username as impersonator_username,\n impersonator.display_name as impersonator_display_name,\n uir.impersonated_user_token as `impersonated_user_token: UserToken`,\n impersonated.username as impersonated_username,\n impersonated.display_name as impersonated_display_name,\n uir.is_redeemed as `is_redeemed: bool`,\n (uir.expires_at < NOW()) as `is_expired: bool`,\n uir.expires_at,\n uir.created_at,\n uir.updated_at\nFROM user_impersonation_requests uir\nJOIN users impersonator ON impersonator.token = uir.impersonator_user_token\nJOIN users impersonated ON impersonated.token = uir.impersonated_user_token\nORDER BY uir.id DESC\nLIMIT ?\n ",
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.