1207 lines
53 KiB
Markdown
1207 lines
53 KiB
Markdown
# DM-Pal — UI/UX Improvements
|
||
|
||
*A review of the current app shell, dashboard, and every utility. Findings are
|
||
ordered by impact, not file order. Each item ships with a concrete, checkable
|
||
recommendation.*
|
||
|
||
> **Status:** in progress — P0 and P1 shipped, P2 largely done. The
|
||
> checklist below is updated as items land.
|
||
|
||
---
|
||
|
||
## 1. Executive Summary
|
||
|
||
DM-Pal is a Tauri v2 desktop app for running D&D sessions with a local LLM.
|
||
The visual identity (dark navy + Cinzel headings + gold accents, glassmorphic
|
||
bento cards) is on-brand and consistent across surfaces. The architecture is
|
||
clean — every tool is a self-contained card mounted into either the bento
|
||
dashboard or a single-column detail view.
|
||
|
||
**The biggest problems right now are not aesthetic — they are interaction
|
||
shape and information density.** The dashboard tries to render *full* tool
|
||
UIs inside ~250px tiles, the left rail has grown to **14 entries** with two
|
||
identical `Sparkles` icons, and the "Expand" button is the only escape hatch
|
||
back to a usable view. There is no model-loading state, no streaming
|
||
indicator, no "thinking…" affordance, and the settings panel is a dead end
|
||
until you click "Load Settings". Detail views duplicate the same header
|
||
treatment as the dashboard and don't give the tools room to breathe.
|
||
|
||
The good news: the design system tokens, component primitives (`BentoCard`,
|
||
`Toast`, `GeneratedImage`), and Rust↔TS plumbing are all in place. Most of
|
||
the wins are layout, IA, and copy — not a redesign.
|
||
|
||
### Top 5 wins (do these first)
|
||
|
||
1. ✓ **Kill the "expand to use it" pattern.** Make every bento card a *summary*
|
||
with a clear `Open` button. Tools render full-width in the detail view,
|
||
not at 1/4 size on the dashboard.
|
||
2. ✓ **Reorganize the left rail into 2 visual groups** (Session vs World) with
|
||
separators and tooltips that show on hover for icon-only nav.
|
||
3. ✓ **Add a real loading/streaming UI** for LLM and image generation. The
|
||
"Generating…" text in a button is not enough for 5–20 s waits.
|
||
4. ✓ **Ship a real Settings page** (load on mount, group endpoints/models/gen
|
||
params, show connection status).
|
||
5. ✓ **Fix the world/nav mismatch**: `isDetailView` in `App.tsx` only shows
|
||
the back button for some views, and `view === "settings"` is treated
|
||
separately so a back arrow is missing.
|
||
|
||
---
|
||
|
||
## 2. Audit by Surface
|
||
|
||
### 2.1 Left Rail Navigation — `src/App.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- **14 icon buttons in a 56px column** with no labels. Most users will not
|
||
hover-discover what each icon does. Two buttons use `Sparkles` (Random
|
||
Tables and Quest Designer) — they are visually indistinguishable.
|
||
- The rail mixes 7 "primary" nav items, 6 "extra" nav items, and Settings,
|
||
with no visual hierarchy. The `flex-1` spacer pushes extras to the bottom
|
||
but a new user has no way to know which is which.
|
||
- `navItems` is defined in code but `initiative`, `tables`, `quest`,
|
||
`calendar`, `lore`, `image` are inlined as separate JSX blocks. The list
|
||
is split between the array and 80+ lines of copy-pasted button markup.
|
||
- Active-state styling is the same for primary and extras — there's no
|
||
indication of "category".
|
||
- Tooltips use native `title=""` — slow, ugly, and bad for keyboard users.
|
||
- The settings cog duplicates a back-to-dashboard toggle, which is
|
||
surprising. Users expect the gear to open settings, not toggle them.
|
||
- The order is: World, NPCs, Items, Encounter, Dice, Session, Sound,
|
||
Initiative, Tables, Quest, Calendar, Lore, Image, Settings. There is no
|
||
grouping by intent (in-session vs prep vs world-building).
|
||
|
||
**Recommendations:**
|
||
|
||
- **Two visual groups** with a thin gold separator and a small uppercase
|
||
label ("SESSION" / "WORLD") in `--color-text-dim`. Group the 6 most
|
||
in-combat tools together (Initiative, Dice, Sound, Encounter, NPCs,
|
||
Quest) and the 6 prep/world tools below (World, Lore, Items, Calendar,
|
||
Tables, Image).
|
||
- **Use distinct icons.** Replace the duplicate `Sparkles` (Tables) with
|
||
`Shuffle` or `Dices`. Quest Designer can keep `ScrollText` or get its
|
||
own `Flag` icon.
|
||
- **Replace native tooltips** with a Radix tooltip or a CSS-only
|
||
`data-tooltip` attribute that shows on hover and focus, with a 200ms
|
||
delay, 12px `--text-primary` on `--bg-deep` with a 1px gold border.
|
||
- **Consolidate the array and inline buttons** into a single
|
||
`navItems: { icon, label, view, group }` array, rendered with one
|
||
`NavButton` component. Removes ~60 lines of JSX and the bug risk of
|
||
drifting styles.
|
||
- **Settings as a peer**, not a toggle. Click = always open settings. Use
|
||
the title bar's chevron to return to dashboard.
|
||
- **Add keyboard shortcuts.** `1`–`9` jump to the first 9 nav items.
|
||
`Cmd+,` opens settings. This is a desktop app — shortcuts are table
|
||
stakes (Roll20, Foundry, D&D Beyond all have them).
|
||
|
||
### 2.2 Title Bar / Header — `src/App.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- The header is 40px tall with the app name, a fake "My Campaign ▾"
|
||
dropdown that does nothing, and a back button that only shows for some
|
||
views (`isDetailView` excludes `settings`).
|
||
- `isDetailView` is calculated as `view !== "dashboard" && view !== "settings"`.
|
||
That means **on the Settings page there is no back arrow**, so the only
|
||
way to leave is the back chevron that doesn't exist there. Users are
|
||
stuck — they have to click the dashboard logo in the rail.
|
||
- The Tauri drag region is set on the entire header, but the campaign
|
||
selector and back button are *inside* the drag region, so they don't
|
||
receive click events when the window is dragged from the header (this
|
||
is a Tauri gotcha — interactive elements inside a `data-tauri-drag-region`
|
||
need `data-tauri-no-drag`).
|
||
- "My Campaign" is a placeholder. There's no campaign concept in the
|
||
backend yet, but the UI pretends there is one. Either ship the campaign
|
||
picker or replace the dropdown with a real status pill (e.g. "● LLM
|
||
connected" / "○ offline").
|
||
|
||
**Recommendations:**
|
||
|
||
- Make the back button **always show when not on dashboard**, including
|
||
Settings. Either drop the `view !== "settings"` clause or invert the
|
||
logic to `view === "dashboard" ? null : <BackButton />`.
|
||
- Add `data-tauri-no-drag` to the back button and the campaign/status
|
||
pill.
|
||
- Replace the inert "My Campaign ▾" with a real status indicator:
|
||
- LLM connection state (green/gold dot, hover for endpoint)
|
||
- Image model state (macOS-gated, "ready"/"unsupported")
|
||
- Lore index size ("142 chunks indexed")
|
||
- Add a small **global search** `⌘K` that opens a palette to jump to any
|
||
tool, NPC, item, or lore source. This is the single biggest navigation
|
||
improvement you can ship.
|
||
|
||
### 2.3 Dashboard / Bento Layout — `src/components/Dashboard.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- The dashboard renders **the full component** of every tool inside a
|
||
~250×180px tile. The header inside the tile says "World Builder" but the
|
||
user sees a tiny 4-character input and a "Generate" button. This is the
|
||
core UX problem.
|
||
- Every card ends with the same "⤢ Expand" link. So 100% of the time, the
|
||
*only* useful interaction is to leave the dashboard. The dashboard is
|
||
not a dashboard — it's a launcher.
|
||
- The Lore card is a static paragraph ("Index your world bible…") with an
|
||
"Open" button, while every other card tries (and fails) to be a real
|
||
tool. Be honest about which cards are launchers and which are
|
||
at-a-glance summaries.
|
||
- The grid uses `grid-cols-1 sm:grid-cols-2 lg:grid-cols-4` with hard
|
||
spans. On a 13" laptop (1280px) you get 4 columns, but tiles then
|
||
become 280px wide and content is unreadable. There's no 3-column
|
||
breakpoint.
|
||
- The inline `import`s at the bottom of `Dashboard.tsx` are after the
|
||
component — they work due to hoisting, but it's a readability smell.
|
||
One import block at the top, sorted.
|
||
- World Builder is `col-span-2 row-span-2`. Lore is `1×1`. The "biggest
|
||
tile" is also the one with the *least* useful at-a-glance content (a
|
||
map you can't interact with at that size). Either give World a real
|
||
mini-map (region pin list) or downgrade it to `2×1`.
|
||
|
||
**Recommendations:**
|
||
|
||
- **Two dashboard modes**, user-togglable:
|
||
1. **Launcher mode (default)** — each card is an icon, title, one-line
|
||
description, primary CTA, and a "Last generated: X" footer. No
|
||
embedded input. Tap → detail view.
|
||
2. **Console mode** — embed 2–3 chosen tools as real bento cards
|
||
(e.g. Dice, Initiative, Sound) for live-session use. User picks
|
||
which 3 from a settings list. This is the *real* session screen.
|
||
- For the **at-a-glance cards**, show:
|
||
- **Dice**: big last result + count of rolls in the session
|
||
- **Initiative**: round number + active combatant + # of combatants
|
||
- **NPC**: last generated NPC's portrait + name
|
||
- **Encounter**: last generated difficulty + monster count
|
||
- **Item Forge**: last forged item's name + rarity
|
||
- **Session Log**: entry count + last entry timestamp
|
||
- **Quest**: current quest title + step X/N
|
||
- **Sound**: # of active ambients + master volume
|
||
- **Lore**: chunk count + last-added source
|
||
- **Image Generator**: model name + last prompt
|
||
- **Calendar**: today's fantasy date + next event
|
||
- **Tables**: last roll result
|
||
- Add a **3-column breakpoint** between tablet and desktop. 4 columns
|
||
only on ≥1440px. The bento should be breathable.
|
||
- Use **`react-grid-layout`** (already on your roadmap) for draggable,
|
||
resizable cards. The plan calls for this — ship it.
|
||
- Move all imports to the top of the file.
|
||
|
||
### 2.4 BentoCard — `src/components/BentoCard.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- The card is over-animated: `whileHover={{ scale: 1.005 }}` and
|
||
`whileTap={{ scale: 0.985 }}` on every card. With 12 cards on the
|
||
dashboard, the whole screen bobs slightly when you move the mouse.
|
||
This is the kind of motion that looks great in a portfolio and
|
||
terrible in a tool you use for 4 hours.
|
||
- The header divider is a `border-b border-[var(--color-border-glass)]`
|
||
with no padding below the title. The icon and title sit on the
|
||
baseline of the border, so on small cards they crowd the first row
|
||
of content.
|
||
- The `responsiveSpan` function is fragile: it does
|
||
`replace("col-span-2", "sm:col-span-2 lg:col-span-2 col-span-1")` which
|
||
means if you pass `col-span-3` it stays as `col-span-3` (no such
|
||
class). And `row-span-1` is replaced with itself, so the regex is
|
||
busy-work.
|
||
- No "drag handle" affordance. If you ship `react-grid-layout`, the
|
||
card needs a grab cursor and a small grip icon in the header.
|
||
- The body uses `flex-1 overflow-y-auto` which is correct, but nested
|
||
scrollbars inside tiny tiles make the bento feel like an ant farm.
|
||
|
||
**Recommendations:**
|
||
|
||
- **Drop the whileHover scale.** Replace with a CSS-only border-color
|
||
+ box-shadow transition (already in `.glass-card:hover`). Keep
|
||
`whileTap` only on buttons.
|
||
- Move the `BentoCard` API to a typed prop: `{ title, icon, span, draggable? }`
|
||
with a `SPAN_MAP: Record<Span, string>` const. No string regex.
|
||
- Add a `headerAction?: ReactNode` slot (for the "Open" / "Expand" button)
|
||
so the card's "what to do here" lives in one place.
|
||
- For the dashboard launcher mode, the body should be a fixed
|
||
`min-h-[120px]` content slot, not `flex-1`. Stop nesting scrollbars.
|
||
|
||
### 2.5 Detail View Shell — `src/App.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- Every detail view is wrapped in `<div className="max-w-2xl mx-auto p-6"><div className="glass-card p-6">…</div></div>`. That means every tool
|
||
is **constrained to ~672px wide** in a centered glass card. For a
|
||
desktop DM tool, this is too narrow — World Builder, Quest Designer,
|
||
Soundboard, and Image Generator all want to be wider.
|
||
- The same wrapper is used for every tool, but the tools have very
|
||
different needs:
|
||
- **Dice**: 400px is plenty
|
||
- **World Builder**: 1200px+ (map, side panel)
|
||
- **Image Generator**: needs a full preview
|
||
- **Soundboard**: a grid that benefits from width
|
||
- **LorePanel**: two-pane (add + search) wants 1000px+
|
||
- The wrapper has no breadcrumb, no tool-specific header, no actions
|
||
(save, copy, share). The back arrow is the only chrome.
|
||
- Tools like `LorePanel` and `SessionLogger` already have their own
|
||
internal two-pane layout, but they're squeezed into 672px.
|
||
|
||
**Recommendations:**
|
||
|
||
- **Drop the universal glass-card wrapper.** Let each tool own its own
|
||
page layout.
|
||
- Add a **page header** pattern: title (Cinzel, gold) + description +
|
||
primary action on the right (e.g. "Save to Lore" / "Regenerate" /
|
||
"Copy Markdown"). Reusable `<ToolHeader>` component.
|
||
- Use **viewport-width layouts** with sensible max widths per tool:
|
||
- Narrow (max-w-2xl): Dice, Settings
|
||
- Medium (max-w-4xl): NPC, Item Forge, Encounter, Quest Designer
|
||
- Wide (max-w-6xl): World Builder, Lore, Image Generator, Soundboard,
|
||
Session Logger
|
||
- Each tool should declare its preferred max width via a prop or via
|
||
its own root container.
|
||
|
||
### 2.6 Settings — `src/components/SettingsPanel.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- The panel renders an empty state ("Configure your LLM connection")
|
||
with a "Load Settings" button. **Settings should always be loaded on
|
||
mount.** The "Load Settings" gate is hostile — a user opens Settings
|
||
expecting to configure, and instead sees a button they have to click
|
||
to *unlock* the form.
|
||
- The form is a flat stack of inputs with no grouping, no validation,
|
||
no help text, no connection test, and no indication of what changed.
|
||
- "API Key" and "Model" inputs are plain text. No "show/hide" toggle on
|
||
the API key, no model-name autocomplete, no "Test connection"
|
||
button.
|
||
- After saving, the "Saved" checkmark lives in the button for 2 s and
|
||
disappears. No toast, no persistent indicator.
|
||
- Temperature and max_tokens use range sliders with no scale labels
|
||
(0 → 2 for temp is meaningless to most DMs). Either show labels
|
||
("Focused", "Balanced", "Creative") or numeric input alongside.
|
||
- No way to **reset to defaults** or **export/import config**.
|
||
- The "embed_model" and "image_model" are tucked at the bottom of the
|
||
form with no hint that changing them requires a model download.
|
||
|
||
**Recommendations:**
|
||
|
||
- Auto-load on mount, drop the gate.
|
||
- **Group settings into sections** with `<fieldset>` and visible legends:
|
||
1. **LLM Connection** — API URL, API key, connection test
|
||
2. **Text Model** — model, temperature, max tokens, top-p
|
||
3. **Image Model** — model, OS-compat note, "Open model library" link
|
||
4. **Embedding Model (Lore RAG)** — model, "Reindex all" button
|
||
5. **Danger zone** — Reset to defaults, Clear lore index
|
||
- **Connection test** button that does a `GET /api/tags` (Ollama) or
|
||
`GET /v1/models` (OpenAI) and shows ✅/❌ with the model list.
|
||
- **Model picker with autocomplete** that calls `GET /api/tags` and lets
|
||
the user select from installed models. Falls back to free text input.
|
||
- Show a **persistent status pill** in the title bar driven by the
|
||
connection test result.
|
||
- Use toasts (you already have `ToastContainer`!) for save success
|
||
instead of in-button text.
|
||
- Add **presets** for common LLM providers: Ollama local, LM Studio,
|
||
OpenAI, Anthropic, Custom. Each preset fills in the API URL pattern.
|
||
|
||
### 2.7 Dice Roller — `src/components/DiceRoller.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- The big result is a single number with no breakdown of which die
|
||
rolled what. A 4d6+3 shows `19`, not `[6, 4, 6, 3] +3 = 19`. The
|
||
inline history shows the breakdown but only for multi-die rolls.
|
||
- No **advantage/disadvantage** buttons — the single most common
|
||
d20 roll in 5e. This is table stakes.
|
||
- No **modifier input**. The user has to type `1d20+5` every time
|
||
even when rolling the same attack repeatedly. There's no concept of
|
||
"my character's attack roll" or "my save DC".
|
||
- No **3D dice** or animation. The plan calls for `react-three-fiber`
|
||
— ship even a simple CSS tumble.
|
||
- "Roll" button doesn't react to spacebar. Pressing Enter in the input
|
||
does — but most DMs will want spacebar.
|
||
- Quick dice buttons only roll `1d{n}` — no `Xd{n}` choice. A "d6"
|
||
button rolls 1d6; there's no way to roll 3d6 in one tap.
|
||
- History is capped at 50 items with no way to clear or export. No
|
||
way to copy a result to clipboard.
|
||
- No **roll templates** for common patterns ("Attack", "Save", "Check",
|
||
"Damage"). A template would let you pick "Longsword attack +5"
|
||
→ rolls 1d20+5 and labels the result.
|
||
|
||
**Recommendations:**
|
||
|
||
- Show the **roll breakdown** by default for any roll with >1 die or
|
||
any modifier. Format: `[6, 4, 6] + 3 = 19` (the per-die values
|
||
always visible inline, with the modifier and total).
|
||
- **Advantage/Disadvantage toggle** that swaps the notation to
|
||
`2d20kh1` or `2d20kl1` (or rolls twice and picks). Highlight the
|
||
chosen die.
|
||
- **Modifier input** as a persistent `+/-` field next to the notation
|
||
input. Saved per session.
|
||
- **Roll template row** with 4 buttons: Attack, Save, Check, Damage.
|
||
Each opens a tiny popover with the relevant ability/save/whatever
|
||
the DM chooses.
|
||
- **Spacebar rolls** the current notation when no input is focused.
|
||
- **Quick dice** become `Xd{n}` selectors: tap "d6" once = 1d6, tap
|
||
again = 2d6, etc., with a small "×3" indicator.
|
||
- History: add **clear** and **copy all** buttons. Show timestamps
|
||
in the history.
|
||
- Optional: ship a simple **2D dice tumble** with CSS — no 3D
|
||
library required. Rotate and scale a die SVG during the roll.
|
||
|
||
### 2.8 Initiative Tracker — `src/components/InitiativeTracker.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- Add row: name, init bonus, max HP, +. **No initiative override** —
|
||
you can't set a combatant's initiative to a fixed number after the
|
||
roll (you have to delete and re-add). Roll20 lets you click the
|
||
number and edit.
|
||
- Conditions: clicking a `+Bli` chip adds "Blinded". There's no way to
|
||
add a **custom condition** ("Concentrating", "Raging",
|
||
"Hexed"). For a 5e DM this is critical — almost every combat has at
|
||
least one non-standard condition.
|
||
- The "Add condition" row shows only 5 conditions at a time. With 13
|
||
in the list you have to click through 3 pages to find the right one.
|
||
- HP +/- is 1 HP per click. There's no **damage/heal input** — to take
|
||
27 damage, you click `-` 27 times. Roll20's tracker has a "Set HP"
|
||
input and a "Apply damage" field.
|
||
- No **death save** tracking. When HP hits 0, the combatant just
|
||
shows 0/HP with no way to roll d20s and track successes/failures.
|
||
- No **turn timer**. Combat drags when players stall. A simple
|
||
countdown per turn is standard (Foundry, Roll20 both have it).
|
||
- "Reset" wipes the whole tracker with no confirmation. Easy to
|
||
fat-finger.
|
||
- "Next Turn" cycles through combatants but doesn't reveal the
|
||
*next* combatant visually. Roll20 and Foundry both bold/highlight
|
||
who's up next.
|
||
- No **group/encounter import** — you can't say "roll initiative for
|
||
the encounter I just built".
|
||
- No **notes** per combatant ("Mage is concentrating on Hold Person",
|
||
"Bandit leader has 50ft of movement left"). Critical for table
|
||
memory.
|
||
|
||
**Recommendations:**
|
||
|
||
- **Click initiative number → edit.** Inline number input.
|
||
- **Add custom condition**: small "+" button next to the condition
|
||
row that opens a popover with text input + auto-suggest from
|
||
the 13 standard conditions.
|
||
- **Show all 13 conditions** in a small popover instead of the
|
||
"slice(0, 5)" loop.
|
||
- **HP delta input**: click the HP number → type "27" → press Enter
|
||
to take 27 damage. Click the +/- buttons for ±1.
|
||
- **Death saves**: when HP=0, show three ✓ and three ✗ toggles.
|
||
Roll 1d20 with a button.
|
||
- **Turn timer**: optional 60s countdown that turns red at 0. Reset
|
||
on "Next Turn".
|
||
- **Confirm before Reset** with a small modal.
|
||
- **Notes per combatant**: a small `…` menu that opens a textarea
|
||
popover.
|
||
- **Import from encounter**: button that pulls monsters from the
|
||
last generated Encounter into the tracker.
|
||
- **Visual highlight of next-up combatant** (gold left-border) so
|
||
the DM is primed for "OK, you're up next."
|
||
|
||
### 2.9 Session Logger — `src/components/SessionLogger.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- Two-column layout (notes + AI summary) gets squashed to 672px
|
||
in the detail view, making the textarea ~280px wide. Painful.
|
||
- Notes are plain text, not Markdown. A "**bold** the BBEG's name" is
|
||
shown as asterisks. The plan mentions `@uiw/react-md-editor` — ship it.
|
||
- No **session concept**. Every note is in one big unfiltered list.
|
||
Where's "Session 4, May 12th"? A DM runs multiple sessions and
|
||
needs to switch between them.
|
||
- No **timestamp granularity** — `toLocaleTimeString()` shows
|
||
`3:42:11 PM`. Add the date too.
|
||
- No **export**. After a session, the DM needs to share notes with
|
||
players, paste into Discord, or save to a Markdown file.
|
||
- "AI Summary" is a one-shot button. There's no **streaming** —
|
||
even though your backend supports it via Channels. You're shipping
|
||
the full token list at once. This is a huge UX miss for 5–20s waits.
|
||
- Notes are stored only in component state. **Reload = data loss.**
|
||
This is the #1 risk in the app right now.
|
||
- No **entry editing** — once added, you can only remove (and the
|
||
remove doesn't exist; you can't delete individual entries at all).
|
||
- No **tags** or **category** (combat, roleplay, loot, etc.).
|
||
|
||
**Recommendations:**
|
||
|
||
- **Persist notes to disk** via `tauri-plugin-store` or `tauri-plugin-fs`.
|
||
Load on mount, save on every change (debounced). This is critical.
|
||
- **Markdown editor** with a live preview toggle.
|
||
- **Sessions** as a first-class concept. Top-bar dropdown lists
|
||
sessions; click to switch; "New session" button; archive old ones.
|
||
- **Streaming AI summary** using your existing Channel infrastructure.
|
||
Show tokens as they arrive, with a gold cursor.
|
||
- **Export** to Markdown file (Save As) and Copy to Clipboard.
|
||
- **Per-entry actions**: edit, delete, mark as "highlight" (gold
|
||
left border).
|
||
- **Tags** with auto-suggest: #combat #roleplay #loot #quest.
|
||
- **Timestamps** include date.
|
||
|
||
### 2.10 Encounter Builder — `src/components/EncounterBuilder.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- Generates a *narrative* encounter, not a *balanced* one. There's no
|
||
CR calculation, no XP budget check, no party-vs-encounter
|
||
difficulty math. Kobold Fight Club and the DMG have well-known
|
||
formulas. DMs will expect this from a tool called "Encounter
|
||
Builder."
|
||
- Monsters are listed as strings ("3x Goblin Scouts") with no
|
||
stat block. The DM has to flip to the MM. Pull from SRD or your
|
||
own DB.
|
||
- "Difficulty" is a string the LLM generated — "Easy" or
|
||
"Hard" or sometimes "Moderate" (not in your enum). Normalize and
|
||
color-code.
|
||
- No **initiative setup**. After generating, you can't push the
|
||
monsters straight into the Initiative Tracker.
|
||
- No **save** — generated encounters vanish on refresh.
|
||
- "Terrain" select only has 8 options and is free-form from the
|
||
LLM's side. No "tavern brawl", "ship deck", "fey crossing"
|
||
presets.
|
||
|
||
**Recommendations:**
|
||
|
||
- **Add XP budget math.** Inputs: party size, party level (already
|
||
have both), desired difficulty (Easy/Medium/Hard/Deadly). Output
|
||
table from DMG p.82. Show budget and current XP live.
|
||
- **Stat block integration.** Either inline (parse from a local
|
||
monsters.json) or a "View stat block" popover with SRD content.
|
||
- **Color-coded difficulty** pill (green / blue / orange / red).
|
||
- **"Send to Initiative Tracker"** button. Builds the combatant list
|
||
with monsters' CR-derived max HP.
|
||
- **"Send to Soundboard"** button — picks ambient based on terrain
|
||
(forest → 🌲 Forest ambient).
|
||
- **Save to campaign** (long-term) or **save to lore** (so future
|
||
encounters reference this one).
|
||
- **Terrain presets** with icons: 🏰 dungeon, 🌲 forest, ⛰ mountain,
|
||
🏛 urban, 🌊 coastal, 🕳 underdark, 🏜 desert, 🌫 swamp. Add 4 more:
|
||
✈ planar, 🏚 haunted, 🚢 ship, 🏰 siege.
|
||
|
||
### 2.11 NPC Generator — `src/components/NpcGenerator.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- "Class" is a free-form text input labeled "Class" but it accepts
|
||
any string — "Artisan", "Blacksmith", "Diplomat", "Pickpocket".
|
||
These are *backgrounds*, not classes. The 5e concept of "class"
|
||
(Fighter, Wizard) is not enforced. Either rename the field to
|
||
"Background/Occupation" or show a class dropdown alongside.
|
||
- 8 races, 9 alignments — these are fine but a DM who wants a
|
||
half-orc or a goliath can't. Expand to the SRD 9 races.
|
||
- Portrait is a *single* image. No way to view full-size. No
|
||
"save portrait" button.
|
||
- Personality and goals are flat lists. No way to **edit** them
|
||
after generation.
|
||
- No **save NPC to roster**. Generated NPCs vanish on refresh.
|
||
- No **NPC card** concept — the result is a one-off. A DM accumulates
|
||
dozens of NPCs and needs a roster.
|
||
- The "🎲 random name" placeholder in the Name field is just a
|
||
placeholder — there's no actual "randomize name" button.
|
||
- No **stats**. 5e NPCs have AC, HP, ability scores, saves, skills.
|
||
The generator produces flavor, not a usable stat block.
|
||
|
||
**Recommendations:**
|
||
|
||
- Rename "Class" to "Background" with a curated list (Acolyte,
|
||
Criminal, Folk Hero, Noble, Sage, Soldier, plus 13 more from PHB
|
||
backgrounds). Add a free-text "Class" dropdown for Fighter/Wizard/…
|
||
separately.
|
||
- Expand race list to 9 (add Half-Orc, Goliath) and add a
|
||
"Subrace" optional dropdown.
|
||
- **NPC Roster** as a new view: grid of all generated NPCs with
|
||
portrait, name, race. Click to open full sheet.
|
||
- **Stat block** section: AC, HP, ability scores, saves. Either
|
||
generated by LLM or hand-entered.
|
||
- **Random name** button with a dice icon.
|
||
- **Save portrait** (right-click → save image).
|
||
- **Edit personality/goals** in-place.
|
||
- **Copy as Markdown** for the DM's notes.
|
||
|
||
### 2.12 Item Forge — `src/components/ItemForge.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- Rarity is colored, but the colors are 6 hard-coded hex values.
|
||
If a future rarity is added, the color falls back to gold. Use a
|
||
rarity→color map.
|
||
- "Type" is a free-form text input labeled "Type" but the dropdown
|
||
is "Weapon", "Armor", "Potion", "Scroll", "Wondrous Item",
|
||
"Ring", "Wand", "Staff" — that's 8 of 12 standard magic item
|
||
types. Missing: Rod, Staff ✓, Wondrous ✓, Adventuring Gear,
|
||
Tacked.
|
||
- Art is a single 1024×1024 in a 96px frame. The DM can never see
|
||
the full image without exporting.
|
||
- "mechanical" is a string. 5e items have *attunement*, *rarity
|
||
tag*, *value*, *weight*. The LLM prompt asks for a single
|
||
paragraph — you get a paragraph.
|
||
- No **save to inventory**. Items vanish.
|
||
- No **random item** button (one-click "surprise me").
|
||
|
||
**Recommendations:**
|
||
|
||
- **Click art to expand** to full-size in a modal.
|
||
- **Structured mechanics** field: attunement (yes/no/condition),
|
||
rarity tag (already have), charges, value, weight, single-line
|
||
effect description. LLM returns these as a small object.
|
||
- **Save to inventory** → list view of all forged items with
|
||
rarity-coded borders. Click to view full sheet.
|
||
- **Random item** dice button.
|
||
- **Export to Markdown** / copy to clipboard.
|
||
|
||
### 2.13 World Builder — `src/components/WorldBuilder.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- **Critical syntax bug**: lines 132, 147, 161 have `})` instead of `}`
|
||
(extra `)`). The component renders but with broken JSX. Confirmed
|
||
by reading the file. *Fix this first.*
|
||
- Generates text (name, description, regions, landmarks) but **no
|
||
map**. The plan calls for `react-konva` map rendering. The 2×2
|
||
dashboard tile is wasted on a text blob.
|
||
- "Theme" is a single text input. No presets (high fantasy, dark
|
||
fantasy, sword & sorcery, steampunk, post-apocalyptic, sci-fi,
|
||
horror).
|
||
- "Regions" is a flat list. No **hierarchy** (continent → country
|
||
→ region → city) and no **map pins**.
|
||
- No **save world**. Generated worlds vanish.
|
||
- No **edit**. Generated content is read-only.
|
||
- The Dashboard tile embeds the *full* WorldBuilder component, so
|
||
you see a 280px text input that says "high fantasy" with a
|
||
"Generate" button. Unusable at that size.
|
||
|
||
**Recommendations:**
|
||
|
||
- **Fix the syntax bug** in `WorldBuilder.tsx` (lines 132, 147, 161).
|
||
- **Map canvas**: `react-konva` or `fabric.js` with a parchment
|
||
background, region pin overlays, click-to-edit. This is the
|
||
"wow" feature of the app — invest here.
|
||
- **Theme presets** as a 4×2 grid of clickable chips.
|
||
- **Hierarchy tree**: collapsible tree of continent → country →
|
||
region → city, on the left. Each node has its own generated
|
||
detail.
|
||
- **Persist** the world to disk, reload on mount.
|
||
|
||
### 2.14 Random Tables — `src/components/RandomTables.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- Only 4 built-in tables. DMs want hundreds. The 5e DMG alone has
|
||
~30. Add: NPC Names (fantasy, by race), Traps, Dreams, Treasure
|
||
Hoards, Dungeon Encounters, Wilderness Encounters, Urban
|
||
Encounters, Caravan Cargo, Inn Names, Ship Names, etc.
|
||
- No **user-defined tables**. DMs will want to roll on their own
|
||
table of "weird things found in the dungeon."
|
||
- No **table import** (paste a table from a PDF/book).
|
||
- "Roll 1d20" button always uses the table's dice — no override.
|
||
- History is capped at 30, with no clear, no copy.
|
||
- Result is shown in the table preview (highlighted gold row) AND
|
||
in the big number box, which is redundant.
|
||
- No **weighted tables** — all entries are `min–max` ranges. Some
|
||
tables should weight "1: 10%, 2: 30%, 3: 60%".
|
||
|
||
**Recommendations:**
|
||
|
||
- **Add 10 more built-in tables** (Traps, Dreams, NPC Names, etc.)
|
||
or ship a community table pack.
|
||
- **User-defined tables**: create / edit / delete. List view.
|
||
- **Table import**: paste a markdown list, parse to entries.
|
||
- **Weighted tables**: when entries don't cover 100% of the dice
|
||
range, show the weights visually.
|
||
- **Result**: keep just the big number, highlight the row in the
|
||
table, no separate "result" block.
|
||
- **Export table** to JSON / Markdown.
|
||
|
||
### 2.15 Calendar — `src/components/CalendarWidget.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- Uses the Forgotten Realms calendar (Hammer, Alturiak, …) but
|
||
there's no way to **switch to a custom calendar**. Half the
|
||
campaigns are homebrew with different month names.
|
||
- "Today" is hard-coded to `day: 15, month: 5`. The DM has to
|
||
manually navigate to "today."
|
||
- No **weather** on calendar days (the plan calls for weather
|
||
generation — not implemented).
|
||
- No **moon phases** (essential for lycanthropes, druids).
|
||
- Events are color-cycled through 4 colors. No way to assign a
|
||
color or category.
|
||
- No **recurring events** ("The Festival of Masks happens every
|
||
year on 12 Eleint").
|
||
- The day grid shows 30 days for every month. The Forgotten
|
||
Realms calendar has 30-day months, but a custom calendar might
|
||
not.
|
||
- No **agenda view** ("next 5 events") — useful at session start.
|
||
|
||
**Recommendations:**
|
||
|
||
- **Custom calendar editor**: months, days per month, year length,
|
||
weekday names. Save as part of the campaign.
|
||
- **Current campaign date** as a "Today" indicator the DM can
|
||
advance day-by-day with a "Next day" button.
|
||
- **Weather generator** that rolls based on season/terrain, shown
|
||
as an icon on the day.
|
||
- **Moon phase** per day (8 phases cycling).
|
||
- **Event categories** with colors: Festival, Quest, NPC Birthday,
|
||
Political, Custom.
|
||
- **Recurring events** (yearly/monthly).
|
||
- **Agenda view** as an alternate layout.
|
||
|
||
### 2.16 Soundboard — `src/components/Soundboard.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- All sounds are **synthesized** with Web Audio. Rain, fire, ocean
|
||
are not bad, but a DM running a session wants *real* recordings:
|
||
actual rain, actual crackling fire, actual ambient music. The
|
||
synthesis is clever but sounds thin next to a real ambience pack.
|
||
- No way to **import custom sounds** (drag an MP3 into a tile).
|
||
- No **fade-out** when stopping an ambient — only a 0.5s ramp.
|
||
Sometimes you want a 5s crossfade to a new ambient.
|
||
- No **scenes** ("Tavern" = tavern ambient + chatter + dice SFX +
|
||
fire; "Combat" = wind + drums + sword). One-click scene
|
||
switching.
|
||
- No **master mute** for the whole board.
|
||
- The "Stop All" button is below the SFX grid, easy to miss.
|
||
- No way to **save the current setup** ("This is what I use for
|
||
the dungeon delve").
|
||
- No way to **loop count** for one-shots (e.g. dice rattle twice
|
||
for a crit).
|
||
- `AudioContext` is created on first play, but the browser
|
||
autoplay policy can block it until a user gesture. The first
|
||
ambient may not start. Use `await ctx.resume()` if suspended.
|
||
|
||
**Recommendations:**
|
||
|
||
- **Real sound packs**: ship 2–3 royalty-free ambience loops
|
||
(Pixabay, Freesound.org CC0). Synthesized stays as fallback.
|
||
- **Custom sound import**: drag an MP3/WAV onto a tile to assign.
|
||
- **Scenes**: a named collection of "on" sounds with per-scene
|
||
volume. "Tavern" / "Combat" / "Dungeon" / "Wilderness" / Custom.
|
||
- **Master mute** and **per-sound volume** (currently global
|
||
volume only).
|
||
- **Always-visible Stop All** as a fixed footer button, not
|
||
conditionally rendered.
|
||
- **Save/load board state**.
|
||
- **Pre-warm AudioContext** on first user gesture (any click).
|
||
|
||
### 2.17 Quest Designer — `src/components/QuestDesigner.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- Quests are linear steps in a carousel. DMs want **branching**
|
||
outcomes ("If they spare the bandit, go to step 3; if they kill
|
||
him, go to step 5"). The plan calls for `react-flow` — ship it.
|
||
- No **save quest**. Generated quests vanish.
|
||
- No **player-facing view** (a quest the DM shows to players
|
||
sanitized of twists and secret goals). D&D Beyond has this.
|
||
- No **status** on each step (Pending / Active / Done). The
|
||
stepper is just a viewer.
|
||
- No **reward distribution** — rewards are flavor text, not
|
||
split-by-character XP/gold/items.
|
||
- "Theme" is one text field. No presets.
|
||
- No **quests list / roster**.
|
||
|
||
**Recommendations:**
|
||
|
||
- **Branching quest graph** with `react-flow`. Gold edges for main
|
||
path, blue for side branches, red for "if villain wins."
|
||
- **Step status** (Pending / Active / Done) with click-to-advance.
|
||
- **Player view** toggle: hides Twist, shows flavor only.
|
||
- **Reward breakdown**: XP (per character), gold, items.
|
||
- **Quest roster**: list of all generated quests with status
|
||
badges.
|
||
- **Save to lore** so generated quests feed back into the world.
|
||
|
||
### 2.18 Image Generator — `src/components/ImageGenerator.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- Prompt textarea is one big block. No **negative prompt** field
|
||
(no "blurry, low quality, watermark").
|
||
- No **seed** field for reproducibility.
|
||
- No **aspect ratio selector** — always 1024×1024. A DM wants
|
||
16:9 for a battle map, 1:1 for a portrait, 9:16 for a phone
|
||
wallpaper.
|
||
- No **steps / guidance scale** controls.
|
||
- No **batch** (generate 4 variants, pick the best).
|
||
- No **gallery** — all generated images are ephemeral. DMs will
|
||
want a folder of "this campaign's portraits."
|
||
- "Save PNG" downloads a single image. No "save all", no
|
||
"copy to clipboard."
|
||
- "Regenerate" only fires one variant. No seed/parameter changes.
|
||
- The unsupported-platform error is shown but the ✨ buttons
|
||
elsewhere (NPC, Item) silently fall back. Inconsistent.
|
||
|
||
**Recommendations:**
|
||
|
||
- **Negative prompt** + **seed** + **aspect ratio** + **steps** +
|
||
**guidance** controls. Collapsible "Advanced" section.
|
||
- **Batch mode**: 2×2 grid of variants, click the one you like.
|
||
- **Gallery**: thumbnails of all generated images in a grid.
|
||
Click to view full size. "Save all" / "Delete".
|
||
- **Persist** to a campaign-scoped folder.
|
||
- **Copy to clipboard** in addition to download.
|
||
- **Consistent macOS gating**: when unsupported, the ✨ buttons
|
||
in NPC/Item should also show a "macOS only" inline notice, not
|
||
silent fallback.
|
||
|
||
### 2.19 Lore Panel (RAG) — `src/components/LorePanel.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- Three sections stacked vertically: Add, Indexed, Test. With a
|
||
672px-wide detail view, the textarea for "paste your world bible"
|
||
is unusable.
|
||
- No **drag-and-drop file upload** (.md, .txt, .pdf).
|
||
- No **directory import** ("add every .md in /Users/me/dnd/world").
|
||
- No **chunk preview** (what text was actually chunked and
|
||
embedded?).
|
||
- No **embedding visualization** (t-SNE or UMAP of the corpus).
|
||
- Search results show score with 3 decimal places (0.873). Most
|
||
DMs don't know what that means. Show "★ 4/5" or a relevance
|
||
bar.
|
||
- No **search history**.
|
||
- No **way to attach lore to a generation** — the `ragQuery`
|
||
field on the LLM command exists, but the user has no UI to
|
||
pick which lore sources to query against.
|
||
- "Clear all" is a one-click nuke. Confirm first.
|
||
|
||
**Recommendations:**
|
||
|
||
- **Two-pane layout**: left = add/import, right = search/explore.
|
||
- **Drag-and-drop file upload** with file-type validation.
|
||
- **Directory picker** via Tauri dialog.
|
||
- **Chunk preview** (first 200 chars of each chunk) when a source
|
||
is expanded.
|
||
- **Source filter** in the search input — multi-select chips.
|
||
- **Visualize as "★ relevance"** not raw cosine distance.
|
||
- **Search history**.
|
||
- **Confirm before "Clear all"** with a modal.
|
||
|
||
### 2.20 GeneratedImage — `src/components/GeneratedImage.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- Used in NPC and Item Forge. The component fetches on every mount
|
||
*and* every `nonce` change. If the parent re-renders for any
|
||
reason (state update, etc.), the nonce is unchanged so the
|
||
same image is fetched — fine. But the **prompt** changes when
|
||
the NPC changes, and there's no `AbortController` to cancel an
|
||
in-flight image gen when the user changes inputs mid-flight.
|
||
- No **progress** for image generation (the backend streams
|
||
`step`/`total` lines). Show a progress bar.
|
||
- No **placeholder** while loading — just a tiny `Sparkles`
|
||
icon, which is invisible in a 96px tile. Show a skeleton with
|
||
a shimmer.
|
||
- On error (not macOS), shows a "macOS-only" placeholder. The
|
||
message is small and grey. A DM on Windows might not realize
|
||
*why* nothing is happening.
|
||
- On regular error, shows the error string inline. Could be a
|
||
10-line stack trace.
|
||
|
||
**Recommendations:**
|
||
|
||
- **AbortController** in the useEffect cleanup so stale requests
|
||
don't overwrite the latest.
|
||
- **Progress bar** driven by the NDJSON `step`/`total` lines from
|
||
the backend. Pass through a `Channel`.
|
||
- **Skeleton with shimmer** during load.
|
||
- **Friendly error messages** for known error codes.
|
||
|
||
### 2.21 Toast — `src/components/Toast.tsx`
|
||
|
||
**Current state (problems):**
|
||
|
||
- The component is solid. But: only `Soundboard` uses it. Every
|
||
other tool uses inline error boxes that duplicate the toast's
|
||
job. **Use toasts everywhere** — they're a unified feedback
|
||
channel.
|
||
- Auto-dismiss is 4s with no way to extend. Power users may want
|
||
sticky toasts.
|
||
- The 4 toast types have gold-tinted styles, but only `info`
|
||
uses gold. Make `success` green-tinted to be more
|
||
distinguishable.
|
||
- The `nextId` module-level counter is shared across imports.
|
||
Fine in dev, brittle in tests. Use a UUID or `crypto.randomUUID()`.
|
||
- Stacking: 5 toasts push each other off-screen. Cap at 3 visible
|
||
at once.
|
||
|
||
**Recommendations:**
|
||
|
||
- **Standardize on toasts** for save success, generate complete,
|
||
generation error, etc. Delete inline error boxes from
|
||
NPC/Encounter/Quest/etc.
|
||
- **Sticky option**: `addToast(msg, type, { sticky: true })`.
|
||
- **Cap visible** at 3; older ones auto-dismiss sooner.
|
||
- **UUIDs** for IDs.
|
||
|
||
### 2.22 Cross-Cutting Issues
|
||
|
||
#### Accessibility
|
||
|
||
- **No keyboard nav** between nav items, or only the default
|
||
tab order. The left rail buttons are focusable but there's no
|
||
visible focus ring style (the global `*:focus-visible` is set
|
||
to a 2px gold outline, but it gets clipped on the 14px-wide
|
||
rail buttons).
|
||
- **No aria-labels** on icon-only buttons. Screen readers
|
||
announce "button" with no name.
|
||
- **Color-only state** in Initiative (HP bar green→red) and Item
|
||
Forge (rarity colors). Add a label or icon.
|
||
- **No skip-to-content** link. Tab through the rail to get to
|
||
the dashboard.
|
||
- **Modal dialogs** (when added) need focus traps.
|
||
- **Text contrast**: `--color-text-dim` (#4a5568) on `--color-bg-deep`
|
||
(#0a0e1a) is ~3.2:1 — below WCAG AA (4.5:1 for small text).
|
||
Lighten it.
|
||
- **No reduced-motion support**. `framer-motion` animations should
|
||
respect `prefers-reduced-motion`.
|
||
|
||
#### Internationalization / i18n
|
||
|
||
- All strings are hard-coded English. If you ever ship to non-EN
|
||
markets, you'll need a copy pass. Not a v1 blocker.
|
||
|
||
#### Offline-First
|
||
|
||
- Fonts are loaded from Google Fonts in `index.css` — the
|
||
comments admit "TODO: download woff2 files to public/fonts/
|
||
for offline-first." This is a desktop app meant to run offline.
|
||
Ship the fonts locally.
|
||
|
||
#### Error Boundaries
|
||
|
||
- **No React error boundaries**. If the LLM returns malformed
|
||
JSON that crashes a parser, the whole app blanks to a white
|
||
screen. Add an `<ErrorBoundary>` around the main content area
|
||
with a "Reload" button.
|
||
|
||
#### Loading Skeletons
|
||
|
||
- No skeleton states anywhere. Every async tool shows a static
|
||
"Generating…" button or a tiny spinner. Add skeleton cards for
|
||
the bento, skeletons for AI generations (shimmer placeholders
|
||
that match the final layout).
|
||
|
||
#### Empty States
|
||
|
||
- The Lore panel has one ("No lore indexed yet"). The Initiative
|
||
tracker has none (empty list looks broken). The Encounter
|
||
builder has none. The Quest designer has none. **Every tool
|
||
needs an empty state** with a 1-line explanation and a primary
|
||
action.
|
||
|
||
#### Keyboard Shortcuts
|
||
|
||
- `⌘K` command palette (mentioned above)
|
||
- `1`–`9` jump to nav items
|
||
- `Space` rolls dice
|
||
- `⌘,` opens settings
|
||
- `⌘S` saves current tool's state
|
||
- `Esc` closes modals / goes back to dashboard
|
||
- `?` shows shortcut help
|
||
|
||
#### Persistence
|
||
|
||
- **Almost nothing persists.** Generated NPCs, encounters,
|
||
items, quests, dice history, initiative, calendar events —
|
||
all component state. **Reload = data loss.** This is the
|
||
single biggest functional gap.
|
||
- Use `tauri-plugin-store` for user preferences and small
|
||
lists.
|
||
- Use `tauri-plugin-fs` + JSON files in `$APPDATA/dm-pal/`
|
||
for generated content.
|
||
- Auto-save on every change (debounced).
|
||
|
||
#### Dark Mode
|
||
|
||
- App is dark-only (intentional). But the macOS light title bar
|
||
is jarring. Set `titleBarStyle: "Overlay"` or
|
||
`Transparent` in `tauri.conf.json`.
|
||
|
||
---
|
||
|
||
## 3. DM-Specific UX Notes (External Research)
|
||
|
||
Sources consulted: Roll20 Turn Tracker docs, Roll20 VTT Redesign
|
||
research post (Brittany Vick, 2023), Roll20 D&D 5e Character
|
||
Builder case study, D&D Beyond product walkthroughs (general
|
||
familiarity).
|
||
|
||
### 3.1 Roll20's design lessons that apply
|
||
|
||
- **"Less Menus, More Suggestions."** DM-Pal's bento is menu-
|
||
heavy. Surface suggestions: "Roll initiative for this encounter"
|
||
one-click button on a generated encounter. "Add this NPC to
|
||
the lore" after generation.
|
||
- **"Simplification."** Reduce repetitive actions. Today, after
|
||
generating an encounter, the DM has to: copy monster names →
|
||
open initiative tracker → re-type names → set HP → roll
|
||
initiative. Auto-push instead.
|
||
- **"Automation."** Auto-save, auto-import, auto-reroll.
|
||
Encounter → Initiative push. Item → Inventory push. NPC → Roster
|
||
push. Quest → Lore push.
|
||
- **"Player view vs GM view."** DM-Pal is GM-only, but the
|
||
concept of "what does the DM see vs. what's projected" still
|
||
applies. A session-view mode (large Initiative, large Dice,
|
||
large Soundboard, no edit chrome) for when the DM is sharing
|
||
the screen.
|
||
|
||
### 3.2 Combat tracker patterns (Roll20, Foundry, D&D Beyond)
|
||
|
||
- Foundry: HP and AC always visible; conditions as small icons.
|
||
- D&D Beyond: turn order vertical; "your turn" glow; HP and
|
||
AC prominent.
|
||
- Roll20: drag-and-drop reordering; round calculation for
|
||
custom items ("-1 per round" simulates a countdown).
|
||
- **All three** show: round number prominently, current actor
|
||
highlighted, next actor visible.
|
||
- **None** force you to click +/- 27 times for damage. All have
|
||
a numeric input.
|
||
|
||
### 3.3 Image generation in DM tools
|
||
|
||
- **Foundry** has no AI image gen (yet).
|
||
- **D&D Beyond** has AI art via the partner artist program.
|
||
- **NovelAI**, **Stable Diffusion WebUI** are the open-source
|
||
references — they all have: negative prompt, seed, aspect
|
||
ratio, batch, gallery. **Ship these in DM-Pal's image gen.**
|
||
|
||
### 3.4 Random tables reference
|
||
|
||
- **Kobold Press**, **Donjon**, **Seventh Sphere** all have
|
||
online random tables. **Donjon's** UI is the gold standard:
|
||
big "Roll" button, immediate result, table visible below with
|
||
the result highlighted. DM-Pal's RandomTables is close — just
|
||
needs more tables and user-defined tables.
|
||
|
||
### 3.5 Calendar reference
|
||
|
||
- **Fantasy Calendar** (5e calendar tool) is the de-facto
|
||
standard. Features: custom calendars, recurring events, moon
|
||
phases, weather, agenda view, export to ICS. DM-Pal's calendar
|
||
is a sketch — there's a 10× feature gap.
|
||
|
||
### 3.6 Soundboard reference
|
||
|
||
- **Syrinscape**, **Tabletop Audio** are the reference points.
|
||
Both ship **real recordings** (synthesized doesn't cut it for
|
||
immersion). Both have **scenes** (one-click combinations).
|
||
DM-Pal's synthesis is clever but a real ambience pack is the
|
||
obvious upgrade.
|
||
|
||
---
|
||
|
||
## 4. Prioritized Checklist
|
||
|
||
### P0 — Critical (fix this week)
|
||
|
||
These are blockers or bugs that make the app feel broken.
|
||
|
||
- [x] **Fix `WorldBuilder.tsx` syntax bug** (lines 132, 147, 161:
|
||
`})` should be `}`). Currently renders broken JSX.
|
||
- [x] **Auto-load Settings on mount.** Remove the "Load Settings"
|
||
gate.
|
||
- [x] **Add `data-tauri-no-drag` to interactive elements in the
|
||
header** (back button, status pill).
|
||
- [x] **Show back button on Settings** (drop the
|
||
`view !== "settings"` exclusion in `isDetailView`).
|
||
- [x] **Add a React error boundary** around the main content
|
||
area.
|
||
- [x] **Self-host the fonts** in `public/fonts/` (offline-first).
|
||
- [x] **Standardize errors via the Toast system** — delete inline
|
||
error boxes from NPC, Encounter, Quest, World, Item, Image.
|
||
- [x] **Replace native `title=""` tooltips** with a styled
|
||
`data-tooltip` (visible on hover and focus).
|
||
- [x] **Add `aria-label`** to every icon-only button.
|
||
|
||
### P1 — High impact (next sprint)
|
||
|
||
- [x] **Reorganize the left rail** into 2 visual groups
|
||
(Session / World) with a thin gold separator and small
|
||
uppercase labels.
|
||
- [x] **Replace the duplicate `Sparkles` icon** on Random Tables
|
||
(use `Shuffle` or `Dices`).
|
||
- [x] **Add a global ⌘K command palette** for navigation.
|
||
- [x] **Implement persistence** for Initiative, Dice history,
|
||
Calendar events, Generated NPCs/Encounters/Items/Quests.
|
||
Use `tauri-plugin-store` for small state, `tauri-plugin-fs`
|
||
for generated content.
|
||
- [x] **Ship a loading skeleton + streaming UI** for LLM and
|
||
image generation. Use the existing Tauri Channel
|
||
infrastructure.
|
||
- [x] **Add roll breakdown** to Dice Roller for any roll with
|
||
>1 die or any modifier.
|
||
- [x] **Add Advantage/Disadvantage buttons** to Dice Roller.
|
||
- [x] **Make the dashboard a launcher** — replace embedded
|
||
mini-tools with at-a-glance summaries + primary CTA.
|
||
- [x] **Drop the universal `max-w-2xl glass-card` wrapper** in
|
||
the detail view. Let each tool set its own max-width.
|
||
- [x] **Add a real loading state** to Settings: connection test
|
||
with model list.
|
||
- [x] **Replace "My Campaign" placeholder** with a real status
|
||
pill (LLM connection, image model, lore chunk count).
|
||
- [x] **Lighten `--color-text-dim`** to meet WCAG AA (4.5:1)
|
||
against `--color-bg-deep`.
|
||
|
||
### P2 — Important (next month)
|
||
|
||
- [ ] **Initiative Tracker**:
|
||
- [x] Click-to-edit initiative number
|
||
- [x] Custom conditions
|
||
- [x] Damage input (numeric, not +/- only)
|
||
- [x] Death saves
|
||
- [x] Turn timer
|
||
- [x] Notes per combatant
|
||
- [x] Highlight next-up combatant
|
||
- [x] Confirm before Reset
|
||
- [x] "Import from Encounter" button
|
||
- [ ] **Dice Roller**:
|
||
- [x] Modifier input (saved per session)
|
||
- [x] Spacebar rolls
|
||
- [x] Roll templates (Attack, Save, Check, Damage)
|
||
- [x] Clear/copy history
|
||
- [ ] **Encounter Builder**:
|
||
- [x] XP budget math (party size, level, difficulty)
|
||
- [x] Color-coded difficulty
|
||
- [x] Send to Initiative Tracker
|
||
- [x] Send to Soundboard (terrain-based)
|
||
- [x] Save to lore
|
||
- [x] 4 more terrain presets (planar, haunted, ship, siege)
|
||
- [ ] **NPC Generator**:
|
||
- [x] Rename "Class" to "Background" with curated list
|
||
- [x] Expand races to 9 (Half-Orc, Goliath)
|
||
- [ ] NPC Roster view
|
||
- [ ] Stat block section (AC, HP, ability scores)
|
||
- [x] Random name button
|
||
- [ ] Save portrait (right-click)
|
||
- [x] Edit personality/goals in-place
|
||
- [ ] **Item Forge**:
|
||
- [x] Click art to expand
|
||
- [ ] Structured mechanics (attunement, charges, value, weight)
|
||
- [ ] Save to inventory
|
||
- [x] Random item button
|
||
- [ ] **Quest Designer**:
|
||
- [ ] Branching quest graph (react-flow)
|
||
- [x] Step status (Pending/Active/Done)
|
||
- [x] Player-facing view toggle (hide Twist)
|
||
- [ ] Reward breakdown (XP per character, gold, items)
|
||
- [ ] Quest roster
|
||
- [ ] **Image Generator**:
|
||
- [ ] Negative prompt, seed, aspect ratio, steps, guidance
|
||
- [ ] Batch mode (2×2 variants)
|
||
- [ ] Gallery view with thumbnails
|
||
- [x] Persist to campaign-scoped folder
|
||
- [x] Copy to clipboard
|
||
- [ ] **Session Logger**:
|
||
- [ ] Markdown editor + live preview
|
||
- [ ] Sessions as first-class concept (multiple sessions)
|
||
- [x] Streaming AI summary
|
||
- [x] Export to Markdown
|
||
- [x] Per-entry actions (edit, delete, highlight)
|
||
- [ ] Tags
|
||
- [ ] **Calendar**:
|
||
- [ ] Custom calendar editor
|
||
- [x] Current campaign date with "Next day" button
|
||
- [x] Weather generator
|
||
- [x] Moon phases
|
||
- [ ] Event categories with colors
|
||
- [ ] Recurring events
|
||
- [ ] Agenda view
|
||
- [ ] **Soundboard**:
|
||
- [ ] Real ambience pack (2–3 royalty-free loops)
|
||
- [ ] Custom sound import (drag-drop)
|
||
- [ ] Scenes (one-click combinations)
|
||
- [x] Master mute + per-sound volume
|
||
- [x] Always-visible Stop All
|
||
- [x] Save/load board state
|
||
- [x] Pre-warm AudioContext
|
||
- [ ] **Random Tables**:
|
||
- [x] 10+ more built-in tables
|
||
- [ ] User-defined tables (create/edit/delete)
|
||
- [ ] Table import (paste Markdown)
|
||
- [ ] Weighted tables
|
||
- [x] Export table
|
||
- [ ] **World Builder**:
|
||
- [ ] Map canvas (react-konva)
|
||
- [x] Theme presets
|
||
- [ ] Hierarchy tree
|
||
- [x] Persist world
|
||
- [ ] **Lore Panel**:
|
||
- [ ] Two-pane layout
|
||
- [ ] Drag-drop file upload
|
||
- [ ] Directory picker
|
||
- [ ] Chunk preview
|
||
- [ ] Source filter
|
||
- [x] ★ relevance (not raw cosine)
|
||
- [x] Confirm before "Clear all"
|
||
- [ ] **Settings**:
|
||
- [x] Group into fieldsets (Connection, Text, Image, Embedding, Danger)
|
||
- [x] Connection test with model list
|
||
- [x] Model picker with autocomplete (`GET /api/tags`)
|
||
- [x] Provider presets (Ollama, LM Studio, OpenAI, Anthropic, Custom)
|
||
- [x] Reset to defaults
|
||
- [ ] Export/import config
|
||
- [ ] **Cross-cutting**:
|
||
- [x] Keyboard shortcuts: ⌘K, 1–9, Space, ⌘,, ⌘S, Esc, ?
|
||
- [x] Skeleton loaders for all async states
|
||
- [x] Empty states for all tools
|
||
- [x] `prefers-reduced-motion` respect on framer-motion
|
||
- [x] Skip-to-content link
|
||
- [x] Color + label for state (not color alone)
|
||
|
||
### P3 — Nice to have
|
||
|
||
- [ ] **Draggable bento layout** (react-grid-layout) with
|
||
persisted layout per campaign.
|
||
- [ ] **Console mode** for the dashboard — embed 2–3 chosen
|
||
tools as live-session cards.
|
||
- [ ] **Tablet/phone player view** — a web app that shows the
|
||
DM's screen (initiative, dice) to players on their
|
||
phones via the local network.
|
||
- [ ] **Compendium integration** — pull monster stat blocks from
|
||
a local SRD JSON.
|
||
- [ ] **i18n** — extract strings to a t() helper.
|
||
- [ ] **Session replay** — record dice rolls, initiative
|
||
changes, soundboard state, and replay the session.
|
||
- [ ] **Macros** — let DMs define "1d20+5 attack roll" as a
|
||
named button on the dice roller.
|
||
- [ ] **Voice-to-text** for session notes (Whisper).
|
||
|
||
---
|
||
|
||
## 5. Quick-Win Sprint (suggested first PR)
|
||
|
||
If you want a single PR that lands a lot of value, here are 8
|
||
items that take <1 day combined and touch mostly `App.tsx` and
|
||
small CSS. **All 10 below are now shipped.**
|
||
|
||
1. Fix the `WorldBuilder.tsx` syntax bug (5 min)
|
||
2. Auto-load Settings on mount (10 min)
|
||
3. Drop the `view !== "settings"` exclusion on the back button
|
||
(2 min)
|
||
4. Add `aria-label` to every icon-only button (30 min)
|
||
5. Add `data-tooltip` styled tooltips (1 hr)
|
||
6. ✓ Group the left rail into Session / World (30 min)
|
||
7. ✓ Replace the duplicate `Sparkles` icon (2 min)
|
||
8. ✓ Lighten `--color-text-dim` to meet WCAG AA (2 min)
|
||
9. ✓ Add the ⌘K command palette (3 hrs, the biggest of the bunch)
|
||
10. ✓ Self-host the fonts in `public/fonts/` (1 hr)
|
||
|
||
Total: ~6–8 hours. Lands a much more polished, accessible
|
||
baseline that the rest of the checklist builds on.
|
||
|
||
---
|
||
|
||
## 6. References
|
||
|
||
- [Roll20 Turn Tracker docs](https://help.roll20.net/hc/en-us/articles/360039178634-Turn-Tracker)
|
||
- [Roll20 VTT Redesign research (2023)](https://blog.roll20.net/posts/roll20-virtual-tabletop-redesign-our-research/) — Brittany Vick, UX Lead
|
||
- [Roll20 Design validation process (2024)](https://blog.roll20.net/posts/validating-digital-ttrpg-designs-at-roll20/)
|
||
- [Roll20 InitiativeTrackerPlus script](https://wiki.roll20.net/Script:InitiativeTrackerPlus) — community patterns
|
||
- [Tome of Tips: Turn Tracker](https://bloghub.roll20.net/posts/tome-of-tips-turn-tracker/) — round calculation patterns
|
||
- [WCAG 2.1 contrast minimums](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html) — 4.5:1 for small text
|
||
- [DMG encounter math (p.82)](https://www.dndbeyond.com/sources/basic-rules/building-combat-encounters) — XP thresholds by level
|
||
- [D&D 5e SRD races](https://dnd.wizards.com/resources/systems-reference-document) — 9 races (Human, Elf, Dwarf, Halfling, Gnome, Dragonborn, Tiefling, Half-Orc, Goliath)
|
||
- [Tauri v2 IPC and Channels](https://v2.tauri.app/develop/calling-rust/)
|
||
- Internal: `docs/plan.md` — original feature plan and milestones
|