New pull_model command streams Ollama /api/pull NDJSON over a Channel
(status lines + completed/total bytes). The wizard's model step gains a
'type any tag to pull' input: uninstalled models download in-app with
a progress bar instead of the old 'run ollama pull yourself' dead end.
The NDJSON line-loop is factored out of the chat streaming path and
shared.
- SessionLogger listens for a new LogEntry bus event; dice rolls,
initiative turns/rounds, generated encounters and calendar day
advances land in the active session as timestamped entries — the
raw material the AI summary actually needs. Mute toggle in the
toolbar (persisted, default on).
- NPC Generator: '⚔ to initiative' button pushes the generated stat
block (HP) into the tracker via the existing AddCombatants bus event.
- ragQuery now set on Encounter, Quest and Session summary too
(NPC/Item/World already had it) — every generator is world-grounded.
generate_stream previously did a non-streaming call and emitted the
whole response as one fake token. Now reqwest bytes_stream feeds a
line buffer; every token piece goes out as it arrives via the existing
Channel. SessionLogger's progressive display lights up unchanged.
Per-line parsers are pure fns with unit tests; unparseable lines are
skipped, server error lines bubble.
LlmConfig.provider ("ollama" | "openai", empty = sniff URL so legacy
configs keep working). Settings presets set it — a custom-port Ollama
no longer falls into the OpenAI branch and fails confusingly.
Also: test_connection now accepts an optional config override — the
wizard was passing one that Rust silently ignored, so it tested the
saved config instead of the URL the user just typed.
All LLM/image HTTP goes through Rust (reqwest), so the webview needs
almost nothing: self for assets, data: for generated-image URLs,
inline styles (Vite dev + style attrs), and ws to the Vite dev server
for HMR. Verify on next dev run.
- README: image gen is sd-server (cross-platform), not macOS/Ollama;
Node 23.6+ note for self-checks; prefs store row in the data table.
- plan.md: superseded banners on the Ollama image-gen sections.
- gitea-release.sh: syncs Cargo.toml too; bumped to 0.1.3 to match.
- public/icons.svg: unreferenced — favicon.svg is the icon.
- chunk(): byte splits that landed mid multibyte char silently dropped
the whole chunk (accented/CJK lore). Back the boundary off to a char
boundary; regression test included.
- add_document(): DELETE the source first — re-adding a file no longer
doubles its chunks.
- chunks now record their embed_model (ALTER TABLE migration for old
DBs); search skips chunks from a different model; rag_list reports it;
new rag_reindex re-embeds everything, surfaced in the Lore panel as a
mismatch banner with a one-click Reindex.
set_llm_config only mutated an in-memory Mutex; lib.rs always started
from LlmConfig::default(). Config now round-trips through the same
dm-pal-prefs.json store as dataDir, with a defaults fallback so a
future shape change can't brick startup. Prefs consts now live in
lib.rs (pub) instead of being mirrored per-module.
Soundboard (Web Audio API):
- 8 ambient loops: rain, wind, fire, ocean, forest, tavern, thunder, cave
- 8 one-shot SFX: sword, bow, spell, door, coins, horn, dice, footsteps
- Brown noise generation for ambient textures
- Oscillator-based SFX with frequency sweeps
- Volume slider, active state indicators with pulsing dot
- Stop All button when ambients are playing
- Toast notifications on SFX trigger
Toast notification system:
- Zustand-based store with auto-dismiss (4s)
- 4 types: success, error, info, warning
- Slide-in animation from top-right
- Dismissible with × button
- Integrated into App shell
JSON parsing (all AI components):
- Shared lib/llm-parse.ts with extractJson, parseLlmJson, flattenValue, ensureArray
- Strips markdown code fences before parsing
- Handles nested objects where strings expected
- All 5 AI components use parseLlmJson instead of raw regex
- All prompts explicitly request raw JSON with no fences
- ItemForge shows raw response in <details> on parse failure
Full production build passing
- Created shared lib/llm-parse.ts with extractJson(), parseLlmJson(),
flattenValue(), and ensureArray() utilities
- Strips markdown code fences (\`\`\`json ... \`\`\`) before parsing
- Handles nested objects where strings were expected (flattens them)
- Handles arrays that come back as strings or objects
- Updated all AI prompts to explicitly request raw JSON with no fences
- All 5 AI components now use parseLlmJson() instead of raw regex
- ItemForge shows raw LLM response in expandable <details> when parsing fails
- Added 'IMPORTANT: Return ONLY raw JSON' instruction to all prompts
- Full production build passing
- Dashboard grid is now responsive: 1 col mobile, 2 cols tablet, 4 cols desktop
- BentoCard span classes converted to responsive variants
- Subtle radial gradient on main background for depth (as per design system §7.7)
- All production builds passing
- World Builder: AI-generated worlds with regions, landmarks, conflicts, cultures
- Item Forge: AI magic item generator with rarity-colored headers, mechanical/lore sections
- Quest Designer: AI quest generator with step-through navigation, choices, twist, reward
- Sidebar now navigates to dedicated full-screen views for every tool
- Bento dashboard cards have 'Expand' buttons opening detail views
- Back arrow (←) in title bar returns to dashboard
- Dashboard grid expanded to 5 rows to accommodate Item Forge
- All three new tools also work in compact dashboard mode
- Full production build clean
- Sidebar icons now navigate to full-screen views for each tool
- Dashboard bento cards have 'Expand' buttons that open the detail view
- Back arrow (←) in title bar returns to dashboard
- Detail views wrapped in a glass-card with max-w-2xl layout
- Settings page gets its own full layout with heading
- World Map, Quest Designer, Soundboard show placeholder screens with icons
- All nav items in sidebar: World, NPCs, Encounter, Dice, Session, Sound,
Initiative, Random Tables, Calendar, Settings