Align the shared @storyteller/ui-moodboard surface with the webapp's design
language across both hosts (Tauri desktop + webapp), plus several UX fixes.
Theming & layout
- Use `var(--st-bg, hsl(0 0% 9%))` for the workspace/grid background so desktop
follows the active theme while the webapp falls back to its --background token
(replaces the desktop-only bg-ui-background class).
- Fix desktop overflow: the fixed TopBar is 56px, so the moodboard tab now uses
h-screen + pt-[56px] + overflow-hidden, killing the stray vertical/horizontal
scrollbars on an empty board. Grid view root clips too.
Empty state & toolbars
- Empty-state Upload / From-library CTAs now use the shared Button, matching the
webapp create pages (h-12, pill, semibold; primary + secondary).
- Restyle SelectionBar to match the library multiselect bar (solid pill, filled
bg-ui-controls buttons, red Delete, round clear); align the "Move to"
menu/items/Kbd to the same idiom.
- Restyle SmartSearchBar (bg-ui-controls, rounded-xl) and tighten ToolbarShell
padding.
- Add a tooltip to the toolbar's Add Color button to match the sibling buttons.
Notes
- Adding a note creates an empty, immediately-editable note (autofocusing inline
textarea; commit on blur/Enter, Shift+Enter newline, Esc reverts); double-click
re-enters edit mode — no more stuck "New note".
- Pad the note's top so the selection check no longer overlaps the first line.
Selection
- Clicks accumulate: clicking an item adds it to the selection (clicking again
removes it), so multi-select builds up without a modifier; background click
clears. "Use as reference" only shows when the selection has a
reference-capable image.
Item inspector
- Redesign ItemInspector to mirror the shared LightboxModal: a bounded, centered,
draggable/resizable Modal with media on a dark plate, hover nav arrows, and a
fixed info/actions column using shared Buttons.
Webapp homepage
- Add a Moodboard card next to Edit Video on the webapp home page.
2.6 — canvas drag snapping. A shared useNodeDrag hook snaps a dragged
node's near/center/far edges to nearby nodes (and the grid as a fallback)
and draws live alignment guides; a magnet toggle in the canvas toolbar
turns it on/off. Pure computeSnap helper, unit-tested.
2.7 — richer link cards. Show the site's own favicon (client-side, no
proxy; graceful link-glyph fallback) plus a domain line, in both the
grid card and the inspector.
2.8 — drag a library/gallery image onto the board. The grid gains a
drop listener mirroring the canvas's; both consume one event dispatched
by dispatchGalleryMoodboardDrop, which each app wires to gallery-modal's
onImageDrop. Bounds-checked against the mounted board surface.
P2.3 from the UX change plan — organize a board into named sections:
- Grid groups items into section lanes within one virtualized scroll
plane (computeSectionedLayout): header band + masonry + gap, repeated,
with absolute positions so virtualization, marquee, and keyboard nav
all keep working over the flat position list.
- Section headers: inline rename, count, collapse chevron (persists),
delete (items fall back to the ungrouped lane). Ungrouped lane is
shown first when populated and is non-editable.
- 'New section' in the toolbar; 'Move to' menu in the selection bar
assigns the selection to a section / ungrouped / a fresh section.
- Store: createSection / renameSection / deleteSection /
toggleSectionCollapsed / assignItemsToSection, all persisted.
- Tests for the sectioned layout and the section store actions.
P1 + select P2 from the UX change plan:
- Keyboard-navigable grid cards: roving tabindex, geometry-aware
arrow movement (nearestInDirection), Enter opens / Space selects /
Delete removes, programmatic focus on arrow nav, ARIA labels.
- Staggered fade-up entry motion, virtualization-safe via a revealed
Set so scrolling never re-triggers it; respects reduced-motion.
- Complete focus-visible coverage across grid chrome, inspector,
selection bar, presentation, and empty state.
- Button-in-button trailing-icon CTAs on primary actions.
- Drop unsupported tooltipDelay prop that slipped through on a stale
dist; add gridLayout spatial-nav tests.
The "not logged in" state of the gallery picker showed a small "Retry" link,
which doesn't tell the user what to do. Add an optional onLoginClick prop: when
provided, render a proper "Log in" button; otherwise keep the Retry fallback
(backward compatible). The moodboard wires it on both platforms — desktop opens
the login modal (useLoginModalStore), web navigates to /login.
Also adds docs/moodboard/DESIGN_AND_FLOW.md — the moodboard's design language +
user flows, written through the high-end design / UX lens.
Re-bases the moodboard work onto current main (the old branch had diverged
~2 months / 315 commits, and main's "create pages redesign" had deleted the
PageEditor the old integration mounted into). The feature lives entirely in the
shared @storyteller/ui-moodboard lib (grid + Konva canvas + presentation,
adapter pattern); only thin per-app wiring is added.
New webapp (apps/artcraft-webapp):
- /moodboard route (ProtectedLayout) + sidebar entry (BETA).
- webappMoodboardAdapter: upload via the webapp uploader (media token), library
picker via @storyteller/ui-gallery-modal, "use as reference" seeds
useCreateImageStore + navigates to /create-image.
Desktop (apps/artcraft):
- MOODBOARD tab in MainApp/TabState + appMenu entry (BETA), routed via goToApp.
- desktopMoodboardAdapter: upload via @storyteller/api UploadImageMedia, library
picker via gallery-modal, "use as reference" seeds usePromptImageStore +
switches to the IMAGE tab.
Both apps share the lib via the same MoodboardAdapter seam (uploadImage /
sendToGeneration / renderLibraryPicker). Lib + both apps build green; lib tsc/
eslint clean; 15 lib unit tests pass. nx sync updated project references.
## Summary
Reworks the desktop (Tauri) app's Create Image / Create Video pages to
match the webapp,
extracts the webapp's generation gallery into a shared library used by
both apps, embeds the
model selector into every prompt box, adds a folders sidebar + marquee
selection to the gallery
modal, restyles the login/pricing/credits modals to the webapp look, and
turns the Apps page
into the default landing tab.
### Shared generation-list lib
- Extracted the webapp's generation feed into
`@storyteller/ui-generation-list`: list + masonry
grid views, pending/failed/completed rows and cards, thumbnail handling,
gallery/prompt data
hooks, and the shared grid/list view store + toggle. Host-specific
actions (recreate,
make-video, share, download, toasts) inject via render props — webapp
markup unchanged, its
old files are now thin wrappers.
### Desktop create pages
- Prompt box fixed at the bottom, `max-w-5xl` like the webapp; pages
renamed to
"Create Image" / "Create Video".
- Generation feed: in-progress/failed from the Tauri task queue, history
from the library API,
rendered as grid or list (toggle in the TopBar next to credits). Typed
Tauri completion events
push finished media in instantly; the poll reconciles and dedupes.
- Row/card quick actions (recreate, make video, copy link, download)
backed by a shared
`desktopMediaActions` module extracted from the TopBar gallery wiring.
- Hero + background act as a splash that fades when the feed loads;
bottom gradient behind the
prompt box. Batch generations show "Generating N images" on the single
pending chip.
- Video prompt box now expands vertically as far as the image prompt box
(viewport-relative).
### Embedded model selector
- `ClassyModelSelector` gained `variant="embedded"`: a compact pill that
opens the webapp-style
rich-list popover while keeping the desktop's provider selection.
Embedded on all five
prompt-input pages; floating selectors removed; legacy "10 sec." badges
dropped.
### Gallery modal
- Left sidebar lists the user's folders (drag-and-drop, right-click
management, new-folder
button) and shows on the Folders tab too, highlighting the active
folder's root branch.
- Marquee (drag-rectangle) selection in view mode, with edge
auto-scroll; starts only on blank
background so it doesn't interfere with drag-to-folder/canvas.
### Restyled modals (desktop)
- Login/signup: webapp 2-column layout (form left, Vimeo showcase
right), no Google button
(Google auth isn't wired for Tauri), `artcraft-icon` logo, eye toggles.
The dismiss button is
removed so login is required; the post-auth Discord + Success onboarding
steps are kept, as is
the existing Tauri auth flow.
- Pricing + credits: match the webapp visually (unified monochrome plan
cards with
Most Popular / Best Value highlight frames; `#161618` credit-pack cards
with Popular badge).
All billing stays on the existing Tauri `invoke` calls — no billing
logic changed.
### Apps page / navigation
- The Apps page is now the first tab and the default landing tab, with a
Home icon. Restyled to
the webapp home: "What will you craft today?" hero, Create / Edit
sections, gradient-hover
cards sized to their content, full-width scrollbar. The TopBar
quick-menu header reads
"Create" instead of "Generate".
### Fixes
- Video Editor no longer crashes on the desktop app (`Tooltip must be
used within
TooltipProvider`): the editor shell now mounts its own Radix
`TooltipProvider`. The webapp got
one app-wide via `SidebarProvider`; the Tauri app had none, so panel
tooltips threw.
### Misc
- `getCreatorIconPathForModelId()` added to `@storyteller/model-list`
(model id → creator icon
path, desktop/webapp aware).
- **New folder keeps selection:** "Add to folder > Create new folder"
now drops
the selected items into the folder on create instead of leaving it empty
(webapp library + desktop gallery modal)
- **Marquee dead below the fold (main bug):** flex-column scroll parent
shrank
the page root to one viewport; presses lower down missed the handler.
Fixed
with `shrink-0` so the root spans the full scroll height
- **Edge auto-scroll:** holding a drag near the top/bottom edge scrolls
the list
so you can keep selecting past the viewport in one drag (anchor
compensated for
scroll; virtualized tiles stay selected)
- **No blue highlight:** `select-none` on the page (inputs opted back
in) blocks
accidental text/image highlighting and stops it competing with the drag
- **Rectangle no longer covers the top bar:** visible marquee box
clamped to the
gallery viewport; selection hit-testing stays unclamped
The desktop video promptbox built the GenerateVideoRequest without ever
setting `resolution`, so generation always omitted it and the backend
fell back to the model's default (720p) — and billed for it. The cost
preview reads the same store value and sends it, so the preview was
correct (e.g. 117 for 480p) while the charge stayed at the 720p price
(240). Populate request.resolution in buildRequest, mapping the store's
legacy display string ("480p"/"720p"/"1080p") to CommonResolution,
guarded on the model exposing resolutionOptions.
Also fix useImageCostEstimate (webapp) omitting `quality` from its
effect deps, which meant the image cost preview didn't refresh when only
the quality option changed.
…perf
- Add desktop-style marquee drag selection on the webapp library: drag
on blank space to select covered tiles, click blank space to deselect
all
- Multi-select: clicking anywhere on a tile toggles it (fixes a
double-fire that cancelled the toggle so only the checkbox worked);
selection persists across tabs and folder navigation
- Gallery item menus show "View image" / "View video" / "View 3D" above
Add to Folder; menu panels size to content so labels no longer wrap
- Mobile/touch: no tile dragging or marquee (finger drags scroll),
checkbox and menu buttons always visible (no hover on touch), and the
Add to Folder submenu expands inline as an accordion instead of
overflowing the screen
- Select-mode gallery modal: folder cards hide the options menu and
ignore right-click (picker is browse-only)
- Perf: bulk selection moved to a module store with per-tile
subscriptions so selection changes re-render only flipped tiles, never
the page; marquee rectangle is positioned imperatively with cached tile
rects and commits only when coverage changes; webapp date groups
virtualized via the shared LazyDateGroup for smooth scrolling on large
libraries
- Bulk selection bar extracted to its own component, fixed to the
viewport bottom and centered within the content area
- Redesigned the gallery modal with a sidebar replacing the old filter
dropdown - filters now show as a list with item counts
- Added a Folders system - create, rename, and delete folders to
organize assets
- Users can drag items onto folders in the sidebar, including
multi-selected items
- Added "Add to Folder" option in the item right-click menu with a
folder picker submenu
- Added "Add to folder" button in the bulk selection bar with a folder
picker popup
- Clicking a folder in the sidebar opens a folder view with its own
content area and empty state
- Folder names are editable - click the title in the header or
right-click a folder in the sidebar to rename
- Videos are now draggable - previously only images and 3D objects could
be dragged
- Folder data is mocked locally for now - ready for API integration