Files
dm-pal/plan.md
T
itsamejms f30a92ddeb v0.1.0-skeleton: Tauri v2 + React + TS scaffold with design system
- Tauri v2 project with greet command (invoke round-trip verified)
- React + TypeScript + Vite front-end
- Tailwind CSS v4 with dark blue + gold design tokens
- Glassmorphism .glass-card component with backdrop-filter
- BentoCard component (framer-motion, lucide-react icons)
- App shell: left rail nav (56px) + title bar + bento grid dashboard
- Cinzel / Inter / JetBrains Mono font stack
- Dark-only design: #0a0e1a deep, #d4af37 gold accents
- All design tokens as CSS custom properties
- Rust backend: greet command, tauri-plugin-log
- Builds successfully: cargo check, tsc, vite build, tauri build (macOS .app + .dmg)
2026-06-28 22:30:28 +01:00

842 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AI-Powered Dungeon Master Toolkit
*A local, offline-first desktop app built with **Tauri v2** + **Rust** that gives you every tool a DM needs — all powered by a local LLM.*
> **TL;DR**
> 1. Scaffold a Tauri v2 project.
> 2. Add Rust bindings to a local LLM (`llama-cpp-2`, `llama_cpp`, or Candle).
> 3. Build modular "utilities" (World Builder, NPC Generator, Encounter Designer, etc.).
> 4. Wire them up in the front-end via `tauri.invoke` + Tauri Channels for streaming.
> 5. Package and ship — no internet required after first run.
---
## Table of Contents
1. [What a DM Needs](#1-what-a-dm-needs)
2. [System Architecture](#2-system-architecture)
3. [Quick Start — Boilerplate](#3-quick-start--boilerplate)
4. [Local LLM Integration](#4-local-llm-integration)
5. [Core Utilities & AI Features](#5-core-utilities--ai-features)
6. [Front-End Architecture (React/TS)](#6-front-end-architecture-reactts)
7. [Design System — Visual Language](#7-design-system--visual-language)
8. [UI/UX Feature Design](#8-uiux-feature-design)
9. [Data Persistence](#9-data-persistence)
10. [Model Management UX](#10-model-management-ux)
11. [Lore Search / RAG](#11-lore-search--rag)
12. [Security & Legal Considerations](#12-security--legal-considerations)
13. [Optional Enhancements](#13-optional-enhancements)
14. [Resources & Quick Links](#14-resources--quick-links)
15. [Development Checkpoints](#15-development-checkpoints)
16. [Final Checklist](#16-final-checklist)
---
## 1. What a DM Needs
| Category | Typical Items | AI-Powered Utility |
|----------|---------------|--------------------|
| **World & Setting** | Campaign bible, maps, lore notes | Auto-generate continents, history timelines, religions, backstory snippets |
| **NPCs & Factions** | Sheets for each character, relationships | Create NPC bios, personality traits, dialogue hooks, agenda charts, relationship webs |
| **Plot / Quests** | Story arcs, side quests, beats | Draft story outlines, branching choices, plot twists |
| **Encounters** | Enemy lists, terrain descriptions | Generate balanced combat encounters, environmental modifiers, loot tables |
| **Magic & Items** | Item descriptions, stats, rarity | Forge unique items with lore, backstory, and mechanical effects |
| **Maps** | Tile sets, hand-drawn maps | Render vector/raster terrain, city layouts; fog-of-war overlays |
| **Player Materials** | Character sheets, handouts | Auto-populate sheets from templates; generate handouts/flashcards |
| **Session Management** | Logbook, notes, schedule | Track session logs, note highlights, future hook suggestions |
| **Initiative / Combat Tracker** | Turn order, HP, conditions, round timer | Visual combat tracker with condition tracking |
| **Dice & RNG** | Physical dice | Simulate dice rolls, record outcomes; random table roller |
| **Rule Reference** | PHB, DMG, 5e SRD | Lookup rules (SRD/CC-BY content only), convert stats across editions |
| **Calendar & Weather** | Fantasy calendar, seasonal events | Custom calendar system with weather generation |
| **Soundboard / Ambience** | Audio clips | Local audio playback for tavern, combat, forest ambience |
| **Name Generators** | Fantasy, sci-fi, historical name banks | AI-powered or procedural name generation |
| **Puzzle / Trap Designer** | Skill-check flows, trap DCs, hints | Generate puzzle mechanics, trap stat blocks |
| **Homebrew Importer** | Custom classes, spells, monsters | Import from Markdown/JSON templates |
---
## 2. System Architecture
```
┌────────────────────────────────────┐
│ Front-End (TS / React) │
│ Tauri v2 WebView · Capabilities │
│ UI · Map Viewer · Dialog Editor │
│ Drag-and-Drop · File Explorer │
└───────────────▲────────────────────┘
invoke() / Channel
┌───────────────┴────────────────────┐
│ Rust Back-End (Tauri API) │
│ Business Logic · LLM Inference │
│ File I/O · Session Storage │
├───────────────▲────────────────────┤
│ │ │
│ llama-cpp-2 │ SQLite / JSON │
│ (local GGUF)│ Storage Layer │
└───────────────┴────────────────────┘
```
### Key Points
- **Offline-first**: Core LLM runs locally via GGUF. Remote API is optional for higher-capacity models.
- **Tauri v2 security**: UI runs in a sandboxed WebView; all file/system access gated by capability permissions.
- **Plug-in-style utilities**: Each tool is a separate feature flag — start with "World Builder", add "Encounter Designer" later.
- **Recommended Tauri plugins**:
| Plugin | Purpose |
|--------|---------|
| `tauri-plugin-store` | User settings, UI state, recent campaigns |
| `tauri-plugin-fs` | Read/write campaign JSON/Markdown files |
| `tauri-plugin-dialog` | Open/save dialogs for campaigns and maps |
| `tauri-plugin-sql` | SQLite for indexed NPC/location/loot lookup |
| `tauri-plugin-log` | Structured logs from Rust and TS |
| `tauri-plugin-updater` | Auto-updates for app and model packs |
| `tauri-plugin-single-instance` | Prevent multiple app windows |
| `tauri-plugin-window-state` | Remember window size/position |
### Tauri v2 Capabilities
Create `src-tauri/capabilities/default.json`:
```json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Main DM toolkit capability",
"windows": ["main"],
"permissions": [
"core:default",
"store:default",
"fs:default",
"dialog:default",
"shell:allow-open",
"http:default"
]
}
```
For scoped file access:
```json
{
"identifier": "fs:allow-read-text-file",
"allow": [{ "path": "$APPDATA/dm-toolkit/**" }]
}
```
---
## 3. Quick Start — Boilerplate
### Prerequisites
```bash
# Rust & Cargo
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
# Node (for front-end tooling)
brew install node # macOS / Linux; or use nvm
# Tauri v2 CLI
npm install -D @tauri-apps/cli@latest
```
### Scaffold
```bash
npm create tauri-app@latest dm-toolkit -- --template react-ts
cd dm-toolkit
npm install
npm run tauri dev
```
> **Note**: Tauri v2 scaffolds both `src-tauri/src/lib.rs` and `src-tauri/src/main.rs` (mobile/desktop share the lib). Import commands from `@tauri-apps/api/core`, not the old v1 path.
### Add Dependencies
```bash
# LLM binding — pick one:
cargo add llama-cpp-2 # mature, tracks upstream llama.cpp
# cargo add llama_cpp # higher-level, easier to start
# cargo add candle-core # pure-Rust alternative (no C++ build deps)
# Remote API fallback
cargo add reqwest
# Storage
cargo add serde_json
cargo add sqlx # or use tauri-plugin-sql
```
---
## 4. Local LLM Integration
### 4.1 Using `llama-cpp-2` (Recommended)
`llama-cpp-2` is a mature Rust binding that tracks the upstream llama.cpp project. The real API requires: init backend → load GGUF → create context → tokenize prompt → `decode()` batch → sampling loop → decode token-by-token.
```rust
// src-tauri/src/llm.rs
use std::path::PathBuf;
use std::sync::Arc;
use llama_cpp_2::llama_backend::LlamaBackend;
use llama_cpp_2::model::LlamaModel;
use llama_cpp_2::model::params::LlamaModelParams;
use llama_cpp_2::context::params::LlamaContextParams;
pub struct LlmState {
model: Arc<LlamaModel>,
backend: LlamaBackend,
}
impl LlmState {
pub fn new(model_path: &str) -> anyhow::Result<Self> {
let backend = LlamaBackend::init()?;
let model = LlamaModel::load_from_file(
&backend,
model_path,
&LlamaModelParams::default(),
)?;
Ok(Self { model: Arc::new(model), backend })
}
pub fn generate(&self, prompt: &str, max_tokens: usize, temperature: f32) -> anyhow::Result<String> {
let mut ctx = self.model.new_context(&self.backend, LlamaContextParams::default())?;
let tokens = self.model.str_to_token(prompt, llama_cpp_2::model::AddBos::Always)?;
// ... decode batch, sampling loop, collect output tokens ...
// (Full implementation in llama-cpp-2 examples)
todo!("Implement sampling loop")
}
}
```
> **Tip** — Keep model files in `$APPDATA/dm-toolkit/models/`. On first run, ship a small base model and let the user download heavier ones later.
### 4.2 Streaming Tokens via Tauri Channels
`invoke()` returns a single promise, **not** a stream. For streaming LLM tokens, use **Tauri Channels** (`Channel<T>`) or the **event system** (`app.emit()` + frontend `listen()`).
#### Rust — Streaming Command
```rust
use tauri::ipc::Channel;
use serde::Serialize;
#[derive(Clone, Serialize)]
#[serde(tag = "type", content = "payload", rename_all = "camelCase")]
enum LlmEvent {
Token(String),
Error(String),
Done,
}
#[tauri::command]
async fn generate_stream(
state: tauri::State<'_, LlmState>,
prompt: String,
max_tokens: i32,
channel: Channel<LlmEvent>,
) -> Result<(), String> {
tauri::async_runtime::spawn(async move {
// Init backend, load model, create context, tokenize...
// For each decoded token:
// channel.send(LlmEvent::Token(piece)).ok();
// When done:
// channel.send(LlmEvent::Done).ok();
});
Ok(())
}
```
#### Front-End — Consuming the Stream
```tsx
import { invoke, Channel } from "@tauri-apps/api/core";
async function streamGenerate(prompt: string) {
const onToken = new Channel<LlmEvent>();
onToken.onmessage = (event) => {
switch (event.type) {
case "Token": appendText(event.payload); break;
case "Error": console.error(event.payload); break;
case "Done": setStreaming(false); break;
}
};
await invoke("generate_stream", { prompt, maxTokens: 512, channel: onToken });
}
```
### 4.3 Optional Remote API Fallback
```rust
// src-tauri/src/api.rs
use reqwest::Client;
use serde_json::json;
pub async fn query_openai(prompt: &str) -> anyhow::Result<String> {
let client = Client::new();
let res = client.post("https://api.openai.com/v1/chat/completions")
.bearer_auth(std::env::var("OPENAI_API_KEY")?)
.json(&json!({
"model": "gpt-4o-mini",
"messages": [{ "role": "user", "content": prompt }],
"max_tokens": 512
}))
.send()
.await?
.json::<serde_json::Value>()
.await?;
Ok(res["choices"][0]["message"]["content"].as_str().unwrap_or("").to_string())
}
```
> **Important**: GPT-4o-mini is a **remote API model**, not local. Keep local inference via GGUF and use remote APIs only as an optional fallback.
---
## 5. Core Utilities & AI Features
Each module exposes a **Tauri command** (`#[tauri::command]`) that the front-end calls via `invoke`.
| Module | Key AI Tasks | Sample Prompt |
|--------|-------------|---------------|
| **World Builder** | Generate continent names, climate zones, major cities, mythic legends | *"Create a fantasy continent with 3 climates and one legendary creature."* |
| **NPC Generator** | Personality traits, background story, NPC goals, relationship webs | *"Generate a charismatic dwarf blacksmith who despises elves."* |
| **Quest Designer** | Multi-step plot arcs, branching outcomes, hooks | *"Outline a 4-chapter quest where the party seeks the lost crown of Rithor."* |
| **Encounter Builder** | Balanced monster groups, terrain modifiers, loot tables | *"Design an urban ambush for level-5 PCs with 3 bandits and a surprise mob."* |
| **Item Forge** | Stat tables, lore sentences, rarity levels | *"Create a +2 longsword of fire resistance that grants invisibility once per day."* |
| **Initiative Tracker** | Turn order, HP/conditions, round timer | Manage combat state, auto-sort by initiative |
| **Random Tables** | Roll on customizable tables | Treasure, encounters, names, weather |
| **Calendar & Weather** | Fantasy calendar, seasonal events | Custom calendar system with weather |
| **Soundboard** | Local audio clips | Tavern, combat, forest ambience |
| **Map Generator** | Procedural terrain, fog-of-war overlay | Use `react-konva` or `fabric.js` with offline vector tiles |
| **Dice Roller** | 1-to-6 dice + custom pools | *"Roll 4d8+2."* |
| **Session Logger** | Summarize actions, suggest next hooks | *"Summarize last session and propose a hook for the next meeting."* |
| **Puzzle / Trap Designer** | Skill-check flows, trap DCs, hints | Generate puzzle mechanics and trap stat blocks |
| **Name Generator** | Fantasy, sci-fi, historical name banks | Procedural or AI-powered name generation |
| **Homebrew Importer** | Import custom content | Markdown/JSON templates for classes, spells, monsters |
| **Handout Renderer** | Markdown → styled handout → export PNG/PDF | Generate player handouts |
### Example: NPC Generator Command
```rust
// src-tauri/src/commands/npc.rs
use tauri::command;
#[derive(serde::Deserialize)]
pub struct NpcRequest {
pub name: Option<String>,
pub race: String,
pub class: String,
pub alignment: String,
}
#[derive(serde::Serialize)]
pub struct NpcResponse {
pub bio: String,
pub personality: Vec<String>,
pub goals: Vec<String>,
}
#[command]
pub fn generate_npc(req: NpcRequest) -> anyhow::Result<NpcResponse> {
let prompt = format!(
"Create a detailed NPC profile:\n\
Name: {}\nRace: {}\nClass: {}\nAlignment: {}\n\n\
Output in JSON with keys bio, personality (array), goals (array).",
req.name.unwrap_or_else(|| "Unnamed".into()),
req.race, req.class, req.alignment
);
let raw = crate::llm::generate(&prompt, 512)?;
let parsed: NpcResponse = serde_json::from_str(&raw)?;
Ok(parsed)
}
```
---
## 6. Front-End Architecture (React/TS)
### State Management
| Concern | Recommended Tool |
|---------|------------------|
| UI state | **Zustand** or **Jotai** |
| Rust command data | **TanStack Query** (React Query) |
| Type-safe bindings | **`tauri-specta`** — generates TS types from Rust commands |
### Key Libraries
| Need | Library |
|------|---------|
| Map canvas, tokens, fog-of-war | `react-konva` or `fabric.js` |
| Quest / relationship graphs | `react-flow` |
| Markdown editing | `@uiw/react-md-editor` |
| Dice 3D animation | Three.js / `react-three-fiber` (optional) |
| Layout & styling | **`tailwindcss`** + CSS Grid — bento layouts, design tokens |
| Glassmorphism & motion | **`framer-motion`** (hover/press animations), native `backdrop-filter` |
| Icons | **`lucide-react`** — clean, consistent line icons |
| Scrollable panels | **`@radix-ui/react-scroll-area`** — for overflow in bento cards |
---
## 7. Design System — Visual Language
The app should feel like a *magical interface* — a DM's arcane console. The aesthetic is **dark, luminous, and tactile**: deep navy backgrounds with gold accents, glass cards that glow softly, and bento layouts that put every tool within reach without clutter.
### 7.1 Color Palette
| Token | Hex | Usage |
|-------|-----|-------|
| `--bg-deep` | `#0a0e1a` | App background, outer chrome |
| `--bg-surface` | `#111827` | Sidebar, navigation panels |
| `--bg-card` | `#1a2236` | Bento card surfaces |
| `--bg-card-hover` | `#1f2a42` | Hover state on cards |
| `--border-glass` | `rgba(212, 175, 55, 0.12)` | Glass card borders (gold-tinted) |
| `--border-subtle` | `rgba(255, 255, 255, 0.06)` | Dividers, separators |
| `--gold-bright` | `#d4af37` | Primary accent — CTAs, active states, headings |
| `--gold-muted` | `#a8872a` | Secondary accent — secondary buttons, icons |
| `--gold-glow` | `rgba(212, 175, 55, 0.25)` | Glow halos, focus rings, box-shadows |
| `--text-primary` | `#e8e0d0` | Main body text (warm off-white) |
| `--text-secondary` | `#8b9bb4` | Muted text, labels, descriptions |
| `--text-dim` | `#4a5568` | Disabled / placeholder text |
| `--danger` | `#ef4444` | Error, delete, HP-zero |
| `--success` | `#22c55e` | Success, save, HP-full |
| `--info` | `#3b82f6` | Informational highlights |
### 7.2 Glassmorphism
Every bento card uses the same glass treatment:
```css
.glass-card {
background: rgba(26, 34, 54, 0.55); /* --bg-card at 55% opacity */
backdrop-filter: blur(18px) saturate(1.3);
-webkit-backdrop-filter: blur(18px) saturate(1.3);
border: 1px solid rgba(212, 175, 55, 0.12); /* --border-glass */
border-radius: 16px;
box-shadow:
0 0 0 1px rgba(255, 255, 255, 0.03),
0 2px 8px rgba(0, 0, 0, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.04);
transition: box-shadow 0.2s, border-color 0.2s;
}
.glass-card:hover {
border-color: rgba(212, 175, 55, 0.25); /* --border-glass brightened */
box-shadow:
0 0 20px rgba(212, 175, 55, 0.08), /* --gold-glow halo */
0 4px 16px rgba(0, 0, 0, 0.5),
inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
```
**Rules of thumb:**
- Glass cards sit on top of a subtle radial gradient or a dim background image (map, parchment texture). The blur lets the background bleed through just enough to feel *alive*.
- On hover, a soft gold glow appears — like candlelight catching the edge of a scroll.
- Active/selected cards get a brighter gold border (`--gold-bright`) and a stronger glow.
- Never use full opacity backgrounds inside bento cards — the glass effect is the identity.
### 7.3 Bento Grid Layout
The main dashboard is a **CSS Grid bento** — asymmetric tiles that pack related tools together. No sidebars-within-sidebars; everything is a card on the grid.
```
┌──────────────┬─────────┬─────────┐
│ │ NPC │ Dice │
│ World Map │ Sheet │ Roller │
│ (2×2) │ (1×1) │ (1×1) │
│ ├─────────┴─────────┤
│ │ │
├──────────────┤ Session Log │
│ Encounter │ (2×1) │
│ Builder │ │
│ (1×1) ├────────┬────────┤
│ │Initi- │ Random │
├──────────────┤ative │ Tables │
│ Quest │ Tracker│ (1×1) │
│ Designer │ (1×1) │ │
│ (2×1) │ │ │
└──────────────┴────────┴────────┘
```
**Implementation** (Tailwind + CSS Grid):
```tsx
// Each bento card is a <BentoCard> wrapper around the real component.
function BentoCard({
children,
className,
span = "col-span-1 row-span-1",
}: Props) {
return (
<div className={`glass-card p-4 ${span} ${className ?? ""}`}>
{children}
</div>
);
}
// Dashboard grid
<div className="grid grid-cols-4 grid-rows-4 gap-3 p-4 h-screen bg-bg-deep">
<BentoCard span="col-span-2 row-span-2"><WorldMap /></BentoCard>
<BentoCard span="col-span-1 row-span-1"><NpcSheet /></BentoCard>
<BentoCard span="col-span-1 row-span-1"><DiceRoller /></BentoCard>
<BentoCard span="col-span-1 row-span-1"><EncounterBuilder /></BentoCard>
<BentoCard span="col-span-2 row-span-1"><SessionLog /></BentoCard>
<BentoCard span="col-span-2 row-span-1"><QuestDesigner /></BentoCard>
<BentoCard span="col-span-1 row-span-1"><InitiativeTracker /></BentoCard>
<BentoCard span="col-span-1 row-span-1"><RandomTables /></BentoCard>
</div>
```
**Bento rules:**
- Cards are **draggable/resizable** — DMs can reorganize their workspace per session.
- Layout preferences persist to `tauri-plugin-store`.
- On narrow windows (laptop), the grid collapses to a 2-column flow.
- Each card header has a subtle gold underline and a `lucide-react` icon.
### 7.4 Typography
| Element | Font | Weight | Size |
|---------|------|--------|------|
| App title / H1 | *Cinzel* (serif display) | 700 | 28px |
| Card headings / H2 | *Cinzel* | 600 | 18px |
| Body text | *Inter* (sans-serif) | 400 | 14px |
| Code / stat blocks | *JetBrains Mono* | 400 | 13px |
| Dice results / numbers | *JetBrains Mono* | 700 | 24px |
Self-host the fonts (offline-first!):
```css
@font-face { font-family: 'Cinzel'; src: url('/fonts/Cinzel-SemiBold.woff2'); font-weight: 600; }
@font-face { font-family: 'Cinzel'; src: url('/fonts/Cinzel-Bold.woff2'); font-weight: 700; }
@font-face { font-family: 'Inter'; src: url('/fonts/Inter-Regular.woff2'); font-weight: 400; }
@font-face { font-family: 'Inter'; src: url('/fonts/Inter-Medium.woff2'); font-weight: 500; }
@font-face { font-family: 'Inter'; src: url('/fonts/Inter-SemiBold.woff2'); font-weight: 600; }
@font-face { font-family: 'JetBrains Mono'; src: url('/fonts/JetBrainsMono-Regular.woff2'); font-weight: 400; }
@font-face { font-family: 'JetBrains Mono'; src: url('/fonts/JetBrainsMono-Bold.woff2'); font-weight: 700; }
```
### 7.5 Micro-interactions & Motion
| Interaction | Effect |
|-------------|--------|
| Card hover | Gold border brightens; subtle gold `box-shadow` glow fades in (0.2s) |
| Card press / active | Scale down to `0.985`; inner shadow deepens |
| Dice roll | Dice tumbles with `framer-motion` spring; result number scales in with a gold flash |
| AI generation typing | Streamed tokens fade in character-by-character with a gold cursor |
| Tab / panel switch | Crossfade (150ms) — no hard jumps |
| Drag bento card | Card lifts (scale 1.03, shadow deepens); drop zone outlines pulse gold |
| HP bar change | Smooth width transition (300ms); color shifts from `--success``--danger` as HP drops |
| Notification / toast | Slides in from top-right with gold left border; auto-dismisses after 4s |
### 7.6 Component Sketch — BentoCard
```tsx
// BentoCard.tsx — the building block of the entire UI
interface BentoCardProps {
title: string;
icon: React.ReactNode; // lucide-react icon
span?: string; // tailwind grid span classes
children: React.ReactNode;
}
export function BentoCard({ title, icon, span = "col-span-1 row-span-1", children }: BentoCardProps) {
return (
<motion.div
className={`glass-card flex flex-col overflow-hidden ${span}`}
whileHover={{ scale: 1.005 }}
whileTap={{ scale: 0.985 }}
transition={{ type: "spring", stiffness: 400, damping: 25 }}
>
{/* Header */}
<div className="flex items-center gap-2 pb-2 mb-3 border-b border-[var(--border-glass)]">
<span className="text-[var(--gold-bright)]">{icon}</span>
<h2 className="font-cinzel text-sm font-semibold tracking-wide text-[var(--text-primary)]">
{title}
</h2>
</div>
{/* Body */}
<div className="flex-1 overflow-y-auto scrollbar-thin">
{children}
</div>
</motion.div>
);
}
```
### 7.7 App Shell Layout
```
┌────────────────────────────────────────────────────────────┐
│ ⚔ DM-Pal [Campaign ▾] [⚙] [_ □ ✕] │ ← Title bar (Tauri custom)
├────┬───────────────────────────────────────────────────────┤
│ 🗺 │ │
│ 👤 │ Bento Grid Dashboard │
│ ⚔ │ │
│ 🎲 │ ┌──────────┬─────────┬─────────┐ │
│ 📜 │ │ WorldMap │ NPC │ Dice │ │
│ 🎵 │ │ 2×2 │ Sheet │ Roller │ │
│ ⚙ │ │ ├─────────┴─────────┤ │
│ │ │ │ Session Log │ │
│ │ ├──────────┤ 2×1 │ │
│ │ │Encounter ├────────┬─────────┤ │
│ │ │Builder │Init. │ Random │ │
│ │ └──────────┴────────┴─────────┘ │
└────┴───────────────────────────────────────────────────────┘
```
- **Left rail** (56px): icon-only nav, dark surface (`--bg-surface`), gold active indicator.
- **Top bar**: Tauri custom title bar with app name in Cinzel, campaign dropdown, settings gear.
- **Main area**: the bento grid, padded, on `--bg-deep` with a subtle radial gradient center for depth.
- Cards are user-rearrangeable; layout saves to local store.
### 7.8 Dark Mode Is the Only Mode
This app is **dark-only**. The dark blue + gold palette *is* the brand — it evokes candlelit tavern tables and starlit skies. There is no light mode toggle. All design tokens above are tuned for dark backgrounds:
- Text contrast ratios meet WCAG AA on `--bg-card`.
- Gold accents provide vibrancy without neon.
- Glass blur prevents the UI from feeling flat while keeping readability.
- Error/success states use bright red/green that pop against navy.
---
## 8. UI/UX Feature Design
How each feature surfaces in the bento dashboard:
| Feature | Bento Card Design |
|---------|-------------------|
| **World Map** | Large 2×2 card; interactive canvas with zoom, draggable tokens (SVG/Canvas), overlaid lore pins. Background layer is the map image; glass overlay panels for region info on hover. |
| **NPC Sheet** | 1×1 card; gold-bordered portrait area at top; scrollable stats/bio below. AI-generated text streams in with a typing cursor. Tap the ✨ icon to regenerate. |
| **Encounter Board** | 1×1 card; mini monster tokens dragged onto a terrain thumbnail; difficulty badge (color from `--success``--danger`). Tap to expand into a full-screen overlay. |
| **Session Log** | Wide 2×1 card; Markdown editor on the left, AI summary panel on the right (glass panel). Timeline of dice rolls and events with gold timestamps. |
| **Dice Roller Panel** | 1×1 card; big result number in JetBrains Mono 700/24px. Roll history as a subtle scrolling list below. 3D tumble animation on roll. |
| **Initiative Tracker** | 1×1 card; vertical list of combatants, gold border on active turn. HP bars animate from green → red. Condition badges are small glass pills. |
| **Relationship Web** | Full-screen overlay; `react-flow` graph on dark background. Edges colored by sentiment (gold = ally, red = hostile, blue = neutral). |
| **Soundboard** | 1×1 card; grid of small glass buttons with waveform icons. Active ambience glows gold. Volume slider on each tile. |
| **Quest Designer** | 2×1 card; branching flowchart (`react-flow`) with gold connector lines. Nodes are glass cards. AI-suggest button pulses gold when ready. |
| **Calendar & Weather** | 1×1 card; mini calendar grid with gold today-marker. Weather icon per day. Tap to see forecast details in a glass overlay. |
---
## 9. Data Persistence
Two complementary storage layers:
| Layer | What it stores | Implementation |
|-------|----------------|----------------|
| **JSON / Markdown files** | World bible, session logs, character sheets — human-editable, shareable | `tauri-plugin-fs`, `$APPDATA/dm-toolkit/data/` |
| **SQLite** | Indexed search across NPCs, locations, items, tags, relationships | `tauri-plugin-sql` (sqlite feature) or `sqlx` with WAL mode + migrations |
### SQLite Migrations
```rust
use tauri_plugin_sql::{Migration, MigrationKind, Builder};
fn main() {
let migrations = vec![Migration {
version: 1,
description: "create_campaign_tables",
sql: "CREATE TABLE npcs (
id INTEGER PRIMARY KEY,
name TEXT,
race TEXT,
bio TEXT,
tags TEXT
);",
kind: MigrationKind::Up,
}];
tauri::Builder::default()
.plugin(
Builder::default()
.add_migrations("sqlite:campaign.db", migrations)
.build(),
)
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
```
### File Persistence Command
```rust
use tauri::path::BaseDirectory;
#[tauri::command]
async fn save_campaign(
app: tauri::AppHandle,
name: String,
data: serde_json::Value,
) -> Result<(), String> {
let path = app
.path()
.resolve(format!("campaigns/{}.json", name), BaseDirectory::AppData)
.map_err(|e| e.to_string())?;
std::fs::create_dir_all(path.parent().unwrap()).map_err(|e| e.to_string())?;
std::fs::write(&path, serde_json::to_string_pretty(&data).unwrap())
.map_err(|e| e.to_string())?;
Ok(())
}
```
> **Tip**: Use WAL mode for concurrent reads, run migrations on startup, and periodic `VACUUM INTO` for backups.
---
## 10. Model Management UX
Local LLMs are not "fire and forget." Plan for:
| Concern | Implementation |
|---------|---------------|
| **First-run wizard** | Detect RAM/VRAM → recommend model size → download GGUF from HuggingFace |
| **Model library UI** | List installed models, quant level, file size, license, hardware fit |
| **Loading state** | Model load can take 530 s on HDD/CPU. Show progress bar and cancel button |
| **GPU offloading** | Expose `n_gpu_layers` slider per model |
| **Context length** | 2k/4k/8k selector with memory warning |
| **Generation controls** | Streaming toggle, temperature, top-p, repeat-penalty per tool |
| **License acceptance** | First-run "model license + download" wizard; don't silently bundle 4 GB models |
---
## 11. Lore Search / RAG
To keep AI-generated content consistent with your world:
1. **Chunk** your world bible into embeddings (local embedding model or llama.cpp embeddings).
2. **Store** vectors in `sqlite-vec` extension or Qdrant-lite for semantic recall.
3. **Inject** relevant lore into LLM prompts before generating content so outputs stay consistent.
---
## 12. Security & Legal Considerations
- **Rule content**: Use **5e SRD / OGL / CC-BY** content only for built-in rule lookups. Full PHB/DMG text is copyrighted.
- **AI output**: AI-generated names, places, and stat blocks can still resemble Wizards IP — add a disclaimer and content moderation toggle.
- **Model licenses**: Model files ship under their own licenses (e.g., Llama 3 requires accepting Meta's license). Build a first-run license acceptance flow.
- **Tauri v2 security**: All file/system access goes through Rust and capability permissions — no arbitrary code execution from the WebView.
---
## 13. Optional Enhancements
| Feature | Implementation Hint |
|---------|----------------------|
| **Plugin System** | Load dynamic libraries (`.so/.dll`) that expose additional commands; register via Tauri's IPC at runtime |
| **Community Share Hub** | Sync campaigns via P2P or Git repo (DMs can clone and pull) |
| **Multiplayer Mode** | Use `tauri::ipc` over WebSocket to synchronize DM/player states in real time |
| **Voice-to-Text** | Integrate Whisper for dictating notes or NPC dialogues |
| **Custom Fine-Tuning** | Store prompt libraries locally; fine-tune the local model on your campaign data |
---
## 14. Resources & Quick Links
- **Tauri v2 Docs** — https://tauri.app/
- **Rust Book** — https://doc.rust-lang.org/book/
- **llama-cpp-2 (Rust bindings)** — https://github.com/utilityai/llama-cpp-rs
- **OpenAI API** — https://platform.openai.com/docs/api-reference/chat/create
- **React Flow** — https://reactflow.dev/
- **SQLite + SQLx** — https://docs.rs/sqlx/latest/sqlx/
- **tauri-specta** — https://github.com/specta-rs/tauri-specta
- **react-konva** — https://github.com/konvajs/react-konva
- **Tailwind CSS** — https://tailwindcss.com/
- **Framer Motion** — https://www.framer.com/motion/
- **Lucide Icons** — https://lucide.dev/
- **Cinzel Font** — https://fonts.google.com/specimen/Cinzel
- **Inter Font** — https://fonts.google.com/specimen/Inter
---
## 15. Development Checkpoints
### Milestone 1 — Skeleton & Hello World
- [ ] Scaffold with `npm create tauri-app@latest` (react-ts template)
- [ ] Configure Tauri v2 capabilities (`src-tauri/capabilities/default.json`)
- [ ] Verify `invoke()` round-trip works with a simple `greet` command
- [ ] Add `tauri-plugin-store`, `tauri-plugin-fs`, `tauri-plugin-log`
- [ ] Commit and tag `v0.1.0-skeleton`
### Milestone 2 — LLM Integration
- [ ] Integrate `llama-cpp-2` (or chosen binding) — load a small GGUF model
- [ ] Implement basic `generate(prompt, max_tokens)` command
- [ ] Implement streaming via Tauri Channels (`Channel<LlmEvent>`)
- [ ] Build first-run model download/license wizard UI
- [ ] Add generation controls (temperature, top-p, context length)
- [ ] Commit and tag `v0.2.0-llm`
### Milestone 3 — Persistence Layer
- [ ] Set up `tauri-plugin-sql` with SQLite + migrations
- [ ] Implement `save_campaign` / `load_campaign` commands
- [ ] Build JSON/Markdown file import/export
- [ ] Commit and tag `v0.3.0-storage`
### Milestone 4 — Core Utilities & Design System (First Pass)
- [ ] NPC Generator command + UI
- [ ] World Builder command + UI
- [ ] Encounter Builder command + UI
- [ ] Session Logger command + UI
- [ ] Dice Roller command + UI
- [ ] Implement design system: `BentoCard`, glassmorphism, dark blue + gold tokens
- [ ] Build bento grid dashboard shell with left rail nav
- [ ] Commit and tag `v0.4.0-utilities`
### Milestone 5 — DM-Specific Tools
- [ ] Initiative / Combat Tracker
- [ ] Random Tables roller
- [ ] Map viewer with fog-of-war (`react-konva`)
- [ ] Calendar & Weather generator
- [ ] Relationship web (`react-flow`)
- [ ] Commit and tag `v0.5.0-dm-tools`
### Milestone 6 — Lore RAG & Polish
- [ ] Add lore chunking + embedding storage (`sqlite-vec`)
- [ ] Inject retrieved lore into LLM prompts
- [ ] Soundboard / ambience module
- [ ] Handout renderer (Markdown → PDF)
- [ ] Polish all glassmorphism effects, micro-interactions, and gold glow states
- [ ] Ensure WCAG AA contrast on all text over glass cards
- [ ] Responsive bento collapse (2-column on narrow windows)
- [ ] Accessibility pass (keyboard nav, screen reader labels, focus rings in `--gold-bright`)
- [ ] Commit and tag `v0.6.0-lore-polish`
### Milestone 7 — Testing, CI, Distribution
- [ ] Unit tests for dice math, encounter balance, storage migrations
- [ ] Cross-platform CI with `tauri-apps/tauri-action`
- [ ] Code signing for macOS and Windows
- [ ] Auto-updater with `tauri-plugin-updater` + delta updates for model packs
- [ ] Package with `npm run tauri build`
- [ ] Commit and tag `v1.0.0-release`
---
## 16. Final Checklist
A consolidated checklist covering all phases:
1. [ ] Scaffold with `npm create tauri-app@latest` (Tauri v2, react-ts)
2. [ ] Configure capabilities (`src-tauri/capabilities/default.json`)
3. [ ] Integrate local LLM binding (`llama-cpp-2`, `llama_cpp`, or Candle)
4. [ ] Stream tokens via Tauri Channels (not raw `invoke`)
5. [ ] Add persistence: `tauri-plugin-store` + `tauri-plugin-fs` + `tauri-plugin-sql`
6. [ ] Implement design system: glassmorphism, bento grid, dark blue + gold palette, Cinzel/Inter/JetBrains Mono fonts
7. [ ] Implement core utilities: World Builder, NPC Generator, Encounter Builder, Item Forge, Session Logger, Dice Roller
8. [ ] Implement DM-specific tools: Initiative Tracker, Random Tables, Fog-of-War, Calendar, Relationship Web, Soundboard
9. [ ] Add lore RAG / semantic search (`sqlite-vec`)
10. [ ] Build first-run model download / license wizard
11. [ ] Add tests, CI (`tauri-action`), code signing, updater, then package with `npm run tauri build`
---
**You're now ready to spin up your own AI-powered Dungeon Master toolkit!**
Happy crafting — may your dice always land favorably! 🎲