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

36 KiB
Raw Blame History

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
  2. System Architecture
  3. Quick Start — Boilerplate
  4. Local LLM Integration
  5. Core Utilities & AI Features
  6. Front-End Architecture (React/TS)
  7. Design System — Visual Language
  8. UI/UX Feature Design
  9. Data Persistence
  10. Model Management UX
  11. Lore Search / RAG
  12. Security & Legal Considerations
  13. Optional Enhancements
  14. Resources & Quick Links
  15. Development Checkpoints
  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:

{
  "$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:

{
  "identifier": "fs:allow-read-text-file",
  "allow": [{ "path": "$APPDATA/dm-toolkit/**" }]
}

3. Quick Start — Boilerplate

Prerequisites

# 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

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

# 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

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.

// 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

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

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

// 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

// 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:

.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):

// 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!):

@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

// 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

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

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.

  • 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


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! 🎲