Add Billing tab to webapp settings
- New "Billing" tab in the settings modal, between Account and API Keys
- Shows your current plan (Free/Basic/Pro/Max)
- Subscribers get a "Manage Plan" button that opens the Stripe portal,
same as the pricing page, plus a "View Plans" link for switching
- Free users get a "View Plans" button to the pricing page
- Handles signed-out and loading states like the other tabs
## Summary
Adds a unified, remappable keyboard-shortcut system
(`@storyteller/keybinds`) across the webapp and desktop app, replacing
the scattered hardcoded `keydown` listeners in the 3D editor, 2D editor,
and moodboard with one registry, two presets (**Gamer** = today's
defaults, **Blender-like**), a Settings → Keybinds pane, and a hold-Ctrl
/ pinnable cheatsheet on every editor surface.
This PR was approved in June and then sat behind ~195 commits on `main`
(timeline, build/record modes, lucide migration). It has been merged
with current `main` and extended to cover the features that landed
since. **Nothing changes by default** — the Gamer preset reproduces
every existing key exactly.
## Architecture
- **Registry of `ActionDef`s** keyed by surface-namespaced ids
(`pagescene.timeline.playPause`), bound to physical `event.code`.
Resolution: user override → preset delta → base bindings. Persisted in
the `artcraft-keybinds` zustand store.
- **Five surfaces**: `global`, `pagescene` (3D), `pagedraw` (2D),
`moodboard` (canvas), `moodboard-grid` (board library / inspector /
presentation).
- **Declared availability** — `ActionDef.when(ctx)` gates decide whether
a binding is live for the current editor state (`sceneMode`, `encoding`,
`timelineExpanded`, `timelineSelection`, …). An unavailable binding
falls through *without consuming the event*, which is what lets Space /
arrows / Delete / Escape be shared by context-exclusive actions. A
sampled-context checker (`actionsCoAvailable`) proves the exclusivity in
tests and drives the conflict UI in Settings.
- **Global surface is a pure trigger layer**: features register behavior
with `useGlobalAction(id, handler)`; a window capture-phase dispatcher
fires above the modal key-swallow, and an unregistered binding is inert
per host. First inhabitant: Ctrl/Cmd+B sidebar. This is the intended
home for the upcoming privacy-blur toggle (one `ActionDef` + one binding
+ one `useGlobalAction` call).
- Cross-surface conflict detection (global ↔ everything, both
directions); preset-integrity tests guard every preset against
same-surface collisions.
- **Video editor keeps its own keybinds system** — deliberate non-goal.
## What's in it
**3D editor (pagescene)**
- Timeline actions: Space play/pause, ←/→ frame step or keyframe/strip
nudge, Home/End, `I` add keyframe (Blender parity), Shift+T expand,
Delete/Backspace on a valid timeline selection (exact complement of
scene-object delete).
- Record mode: Tab toggles build/record, `C` capture still, `R` record
video (bare letters — Firefox eats Ctrl+Shift+S/E), Escape cancels an
in-flight encode (overlay Cancel button too). Record mode is immutable:
all scene-mutating actions gate off, Escape no longer drops out of the
render cam.
- **Per-edit timeline undo** — every timeline edit is one Ctrl+Z step
(`TimelineEditAction` with whole-timeline snapshots; drags and the
easing popover coalesce into single steps). Save now only moves the
Cancel baseline; Cancel is itself undoable.
- The old "Shortcuts" modal is replaced by the live cheatsheet, pinnable
from the same button.
- Dead code removed: `MouseControls.onkeydown`, `OnboardingHelper`, the
hardcoded `Help` view, three unused pagedraw hotkey hooks.
**2D editor (pagedraw)**
- Tool hotkeys 1–5 with live key badges on the toolbar icons; the shape
key cycles rectangle → circle → triangle (submenu-aware).
- Ctrl+= / Ctrl+- (and numpad) resize the active tool. The eraser now
has its own size (was sharing the brush's) and its own slider popout;
also fixes the eraser button never highlighting.
**Moodboard**
- Canvas keys unchanged (now registry-backed), plus Ctrl+0 reset
viewport.
- Grid: rate 0–5 / `P` pick / Delete / Escape, inspector and
presentation ←/→/Space/Esc — all rebindable. Escape now closes the
inspector; arrows no longer navigate while typing in the tag field.
**Settings & cheatsheet**
- Settings → Keybinds: preset picker, name/key search, per-action rebind
with conflict warnings, per-section reset, and a bottom "danger zone"
reset-all with a two-step confirm (2 s lockout + visible 5 s
auto-disarm).
- Cheatsheet: hold Ctrl (⌘) 3 s to peek; **General → "Keep shortcut
cheatsheet open"** makes it stick until Esc / click-outside; the 3D
"Shortcuts" button pins it.
## Testing
- `@storyteller/keybinds` unit tests (29): resolution layering,
overrides, per-surface/global reset, conflict detection incl.
global↔surface, matcher semantics, and preset integrity across all
presets.
- Builds green: `keybinds`, `ui-pagescene`, `ui-pagedraw`,
`ui-moodboard`, `ui-settings-modal`, `artcraft-webapp`, `artcraft`
(desktop).
- Manual QA pass completed on the webapp for every area above (settings
pane, cheatsheets, Blender modal transforms, timeline/record keys,
pagedraw toolbar, global surface, reset UX, pinned cheatsheet, timeline
undo, Escape contexts).
## Notes for review
- The keybinds store is unversioned-migration-free on purpose — the PR
was never merged, so there's no persisted shape to migrate from.
- Space-hold pan (moodboard) and held WASD camera movement stay as
gestures, resolved from the same store but outside the discrete-action
dispatch.
---------
Co-authored-by: bflatastic <bombayapirat@gmail.com>
Drag and drop didn't work while the prompt focus mode was open - the
modal sat on top of the inline promptbox's drop zone, so files just did
nothing (webapp and desktop).
### Changes
- `usePromptBoxDrop` now exposes `fullscreenDropZoneProps` - same
handlers/routing, second ref so the Tauri hit-test follows the
focus-mode modal while it's open
- `PromptFullscreenModal` (shared lib + webapp copy) takes optional
`dropZoneProps` / `dropOverlay` and renders the drop zone + hover
overlay on its panel
- Image / Video / Audio boxes + webapp `PromptBox` pass their existing
drop wiring through - one overlay element now serves both the inline box
and focus mode
- No new drop logic anywhere: same MIME routing, accept/reject toasts,
keyframe handling, and paste behavior as dropping on the inline box
On iOS, adding more than 2 image prompts can blow past the browser's
memory limit and force-reload the page, wiping your prompts. We were
storing every uploaded image as a full-res base64 string just to show a
56px thumbnail.
- Thumbnails are now small ~512px previews instead of full-res data URLs
(memory per image goes from ~100MB+ to under 1MB)
- Tapping a thumbnail still shows the original full-res image
- Nothing changes for the backend — uploads and generation still use the
original file + media token
- Fixed the flicker when an upload finishes: the spinner card now turns
into the image card in place, instead of a new card popping in next to
it
- Small cleanups along the way: leaked object URLs in the old prompt
row, and upload spinners that never actually spun
Applies to webapp (desktop + mobile), website, and the desktop app since
they share the upload code.
## Fixes
- Slow paste in prompt box: `execCommand("insertText")` is quadratic per
line in Chromium - now inserts the clipboard as a single text node
(linear)
- Model selector submenu not scrolling in focus mode: the modal's scroll
lock swallows wheel/touch on body-portaled panels - flyout now stops
propagation (same pattern as mention dropdown)
- Cap the always-expanded deck panel at container width so cards wrap
into rows; scroll past 3 rows instead of squeezing the editor
- Drop shrink-0 on the expanded deck root (min-w-0 max-w-full) so it
shrinks inside the webapp's flex-row wrapper
- Collapsed fan badge shows total ref count instead of +hidden
MentionTextarea's root wrapper lacked min-h-0, so as a flex child of the
fullscreen modal's bounded column it kept full content height and got
clipped by overflow-hidden instead of letting the editor scroll.
Removes Font Awesome entirely
### Changes
- All ~1,000 icon call sites now import Lucide components directly from
`lucide-react` (`<PlayIcon className="..." />`); no FA packages, tokens,
or `fa*` identifiers remain anywhere.
- New `@storyteller/icons` lib holds the pieces Lucide doesn't ship:
`DynamicIcon` for runtime-chosen icons (`icon={loading ?
LoaderCircleIcon : SaveIcon}`), 11 brand logos (Discord, TikTok, ...) as
inlined CC BY 4.0 paths, and HD/SD quality badges drawn in Lucide's
stroke style.
- Global `.lucide { display: inline-block; width/height: 1.2em }` rule
in each app stylesheet: keeps icons font-relative like FA (1.2em
compensates Lucide's 24px-viewBox/20px-grid padding) and undoes Tailwind
preflight's `svg { display: block }`. Explicit `w-*`/`size-*` utilities
still win.
- FA-only props migrated: `spin` → `animate-spin`, FA size strings → em
values.
- Dropped `@fortawesome/*` + `@awesome.me` kit deps, both `.npmrc`
files, and
the FA rule in `shared-vite-config.ts` (now externalizes
`lucide-react`).
- Select multiple items in the library and add tags to all of them at
once
- Tag suggestions pop up as you type, based on your existing tags
- Fixed the selection bar looking squished (buttons no longer wrap onto
two lines)
- Video generation now defaults to Seedance 2.5 (was 2.0)
- Omni Reference is the default video input mode for everyone
- Model pickers on the create pages now group models by family
(Seedance, Veo, Kling, etc.) with a flyout submenu per family, so the
list is no longer one long scroll
- The flyout opens on hover or click, and automatically scrolls the
currently selected model into view
- New setting in **Settings → General** ("Group models by family") to
switch back to the classic flat list. Grouped is the default and works
in both the webapp and the desktop app
- Model rows now use full-color brand icons, while the picker button
keeps its existing look
- Video models show capability tags: **Audio Support**, **Start/End**,
and **Reference**
- Short model lists (under 8 models) stay flat, and mobile keeps the
existing bottom-sheet list
- Fix: videos in the media viewer can now go fullscreen (the expand
button was disabled)
## Summary
New marketing page at /minimax-h3 where visitors can try MiniMax H3 for
free,
right on the page.
- Hero is a working promptbox, locked to MiniMax H3 with a "Free" badge
- Full settings available: aspect ratio, resolution, duration, sound,
start/end frames
- New users sign up in a modal when they hit Generate (Google or email),
then their prompt runs immediately without leaving the page
- Generated videos show up right below the promptbox, with a queue
notice and a CTA to keep creating in the app while they wait
- Example videos from MiniMax's announcement, self-hosted with
thumbnails, only load when played, credited to MiniMax
- Overview, highlights, prompt tips, and FAQ copy making the free offer
clear Website promptbox updated to match the current webapp promptbox
(reference deck, drag & drop, focus mode, same buttons)
- SEO, redirects, and URL aliases (/minimax, /hailuo) wired up