chore(release): v3.10.0 — GM Workbench #225

Merged
claude-bot merged 28 commits from feat/v3.10-gm-workbench into main 2026-07-22 02:17:13 +00:00
Contributor

Release PR for the v3.10.0 "GM Workbench" milestone — all 11 issues built on the feat/v3.10-gm-workbench integration branch via per-issue PRs (#216–#224). Awaiting your review + dev testing before merge — I have not merged this.

What ships

GM Planning becomes a GM Workbench: a unified surface of campaign-grounded AI tools on one generalized generation engine (#136) with an extracted LLM transport (#134) and a persistent per-campaign scratchpad/history (#138).

Tool Issue
Read-aloud description (boxed text) #141
Rumor mill (true/false/distorted → plot thread) #144
Random table (d6/d12/d20 → wiki document) #147
Loot parcel (→ artifact lore / loot ledger) #149
Session prep sheet (async; → beat notes) #158
Instant NPC (prefetch pool) + NPC voice cues #155
Recurring-series title suggestions #191
Two-pane Workbench layout (palette + history rail) #153

Release metadata

  • APP_VERSION 3.9.1 → 3.10.0; BOT_CONTRACT_VERSION stays 1 (no bot API changes).
  • No new migrations beyond the Phase-0 generation_results table (#138, head f0a1b2c3d4e5) already on this branch.
  • Changelog updated.

Verification

Every issue was merged green: backend 735 passed, frontend 397 passed (42 files), ruff check + ruff format --check clean, eslint 0 errors, prod build OK. CI runs on this PR for the authoritative gate.

⚠️ Not yet dev-tested against a live LLM

Per your note (away, can't test on dev yet): the generative tools are unit-tested with the LLM mocked, but none has been exercised against a live Ollama/API endpoint or the Redis prefetch pool on dev. Recommend deploying this branch to dev and smoke-testing the tools (esp. session prep's async polling and the instant-NPC pool) before merging to main.

Open follow-up for your call

#153 delivered the two-pane shell (palette + dispatcher + history rail); collapsing the 8 bespoke panels into a single config-driven GeneratorPanel with one uniform action row was deferred to avoid rewriting tested code you can't yet validate — say the word and I'll open a follow-up issue.

Closes the v3.10.0 milestone (11/11 issues).

🤖 Generated with Claude Code

Release PR for the **v3.10.0 "GM Workbench"** milestone — all 11 issues built on the `feat/v3.10-gm-workbench` integration branch via per-issue PRs (#216–#224). **Awaiting your review + dev testing before merge — I have not merged this.** ## What ships GM Planning becomes a **GM Workbench**: a unified surface of campaign-grounded AI tools on one generalized generation engine (#136) with an extracted LLM transport (#134) and a persistent per-campaign scratchpad/history (#138). | Tool | Issue | |---|---| | Read-aloud description (boxed text) | #141 | | Rumor mill (true/false/distorted → plot thread) | #144 | | Random table (d6/d12/d20 → wiki document) | #147 | | Loot parcel (→ artifact lore / loot ledger) | #149 | | Session prep sheet (async; → beat notes) | #158 | | Instant NPC (prefetch pool) + NPC voice cues | #155 | | Recurring-series title suggestions | #191 | | Two-pane Workbench layout (palette + history rail) | #153 | ## Release metadata - `APP_VERSION` 3.9.1 → **3.10.0**; `BOT_CONTRACT_VERSION` stays **1** (no bot API changes). - **No new migrations** beyond the Phase-0 `generation_results` table (#138, head `f0a1b2c3d4e5`) already on this branch. - Changelog updated. ## Verification Every issue was merged green: backend **735 passed**, frontend **397 passed** (42 files), `ruff check` + `ruff format --check` clean, eslint 0 errors, prod build OK. CI runs on this PR for the authoritative gate. ## ⚠️ Not yet dev-tested against a live LLM Per your note (away, can't test on dev yet): the generative tools are **unit-tested with the LLM mocked**, but **none has been exercised against a live Ollama/API endpoint or the Redis prefetch pool on dev**. Recommend deploying this branch to dev and smoke-testing the tools (esp. session prep's async polling and the instant-NPC pool) before merging to main. ## Open follow-up for your call #153 delivered the two-pane **shell** (palette + dispatcher + history rail); collapsing the 8 bespoke panels into a single config-driven `GeneratorPanel` with one uniform action row was **deferred** to avoid rewriting tested code you can't yet validate — say the word and I'll open a follow-up issue. Closes the v3.10.0 milestone (11/11 issues). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Move the provider-agnostic LLM transport out of the overgrown audio_service:
generate_structured_text, the four _structured_* provider adapters
(Ollama/llama.cpp/Anthropic/OpenAI), extract_json_object, and the constants they
own (_LLM_TIMEOUT, _is_ollama_url, _LORE_PROPOSAL_SYSTEM_PROMPT) now live in
services/llm_service.py. audio_service re-exports them so every existing call
site (reminder_tasks, lore_service, audio_service's own domain functions, and
the LLM-mocking tests that patch audio_service.generate_structured_text) keeps
working unchanged. No circular import; llm_service is the low-level layer.

Foundation for the v3.10.0 GM Workbench: the generalized generation endpoint
(#136) and every Workbench tool sit on llm_service. Pure mechanical move — no
behaviour change, no new endpoints/schemas/prompts.

Verified in Docker (py3.12): 659 tests pass unmodified; ruff check + format clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The persistence + async-status layer the Workbench polls: a GenerationResult
model (migration f0a1b2c3d4e5) with campaign-scoped, GM-only history/pin/delete
endpoints, lifecycle helpers (create/mark_ready/mark_failed) for #136's sync and
Celery tools, a daily retention beat task that prunes unpinned rows > 30 days
(pinned never swept), and a minimal GenerationHistory list (pin/delete/re-open).

- Model generation_results: status TEXT+CHECK, JSONB params/output/token_usage
  (none_as_null on the nullable JSONB), campaign_id CASCADE, session_id/created_by
  SET NULL. token_usage captured from day one for future metering.
- Service generation_result_service.py owns all DB access.
- Retention task prune_generation_results registered in the Celery beat schedule.
- Frontend: api/workbench.js + a minimal GenerationHistory list (pin/delete/
  re-open), surfaced in CampaignPlanning; #153 does the full right-rail layout.

Foundation for v3.10.0 GM Workbench; #136 wires the tool registry into this.

Verified in Docker: 674 backend tests (+15), 345 frontend tests (+5), build +
ruff + eslint clean; migration up/down/up against real Postgres. No bot API change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The single extension point for the GM Workbench: a GENERATOR_TOOLS registry
(ToolDef) + POST /api/campaigns/{campaign_id}/workbench/{tool_id}/generate
(GM-only) that dispatches sync (inline) or async (Celery, run_workbench_generation)
per the tool's sync_allowed flag, writing every run into the #138 GenerationResult
scratchpad. Adding a tool = adding one registry entry.

- generation_service.py: ToolDef registry, ToolResult, run_tool generic executor
  (build declared context -> prompt -> llm_service.generate_structured_text ->
  parse) + context builders campaign_context (folds in #137 system context) /
  lore_context / threads_context / recent_sessions_context. Eager-loads
  game_system_ref and guards unloaded refs (sa_inspect().unloaded) against async
  lazy loads.
- Names migrated as the first tool (tool_id="names"): a custom runner delegating
  to audio_service.generate_name_options so output — incl. #137 per-system hints —
  stays byte-identical and existing name tests pass unmodified. Custom-prompt
  names no longer require an active session (campaign-scoped).
- Old endpoints kept as thin wrappers into the registry (sessions name-options,
  planning names cache-miss); planning prefetch left delegating to the same name
  generator with prefetchable=True as the declarative hook (full generalization
  folds into #155, the second prefetchable consumer).

Foundation for the six generator tools. No migration (uses #138's GenerationResult);
no bot API change. Verified in Docker: 684 backend tests (+10), ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First generic-path GM Workbench tool. Adds the `description` generator
(read-aloud one-liner + paragraph, tone-aware, optionally grounded in a
lore entry) plus the reusable result-landing actions every later generator
reuses:

- Save as lore entry (reuses the lore-create endpoint)
- Append to entry — POST /lore/{entry_id}/append-draft creates a READY
  review draft (no LLM call) reviewable/approvable in the wiki draft rail;
  GM-only, one draft per entry
- Copy to clipboard

Threads generation params into the context builders so entry-scoped tools
can fold the picked lore entry into the prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three campaign-grounded GM Workbench generators on the #136 generic engine,
each a single ToolDef in GENERATOR_TOOLS (sync, JSON-mode), persisting a
GenerationResult (#138):

- rumor (#144): a batch of tavern-ready hooks, each tagged true/false/distorted,
  grounded in open plot threads + picked wiki lore. Lands via "Add as plot
  thread" → a rumored, GM-private PlotThread.
- random_table (#147): a d6/d12/d20 roll table for any prompt, optionally
  lore-grounded. Lands via "Save as lore entry" (document) through the reusable
  WorkbenchResultActions bar with a rendered markdown table.
- loot_parcel (#149): an itemized parcel scaled to source/party, item fields
  shaped to LootEntry. Lands per item via "Save as artifact" (LoreEntry) or
  "Add to ledger" (#112 loot ledger).

All landing actions reuse existing endpoints — no new API surface, models, or
migrations. Tolerant parsers degrade gracefully rather than raise. Panels are
surfaced on the GM-gated planning page alongside the #141 description tool.

Backend 712 passed; frontend 372 passed; ruff + eslint clean; build OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The highest-differentiation Workbench tool: it reads *this* campaign's state
— the upcoming session's beat notes, recent session summaries, open plot
threads, and the wiki — to draft a structured prep sheet. Prose-heavy and
multi-source, so it runs async (sync_allowed=False) via the #136 Celery path.

Backend:
- session_prep ToolDef in GENERATOR_TOOLS: SessionPrepParams(session_id),
  context builders for beat_notes + recent_sessions + threads + lore, tolerant
  parser → {strong_start, scenes[], secrets_and_clues[], npcs[{name,note}],
  loose_ends[]}. session_id is a str param so it flows through row.params to
  the Celery worker.
- New GET /workbench/history/{result_id} — the single-result polling primitive
  for async tools.

Frontend:
- SessionPrepGenerator panel: pick an upcoming session, generate (pending),
  poll fetchWorkbenchResult until ready/failed, render the sheet with NPCs
  linked to matching wiki entries. Save via append-or-replace into beat_notes
  (existing PATCH) after explicit GM review — never auto-writes.
- fetchWorkbenchResult uses cache:false so polling isn't served the stale
  pending body by the client's 15s GET cache.

No new models or migrations. Backend 719 passed; frontend 379 passed;
ruff + eslint clean; build OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two at-the-table improv tools on the #136 engine, plus the prefetch
generalization deferred from #136.

Generalized prefetch:
- ToolDef.pool_field names the output list a prefetchable tool draws from.
- workbench_pool_service: a per-campaign+tool Redis list (LPOP draw, RPUSH
  fill, capped, TTL) — the generalized counterpart of the name cache.
- prefetch_workbench_pool Celery task (lock + TTL, mirrors prefetch_name_options).
- POST /workbench/{tool_id}/draw (pop one, refill when low, cold-pool inline
  fallback) + POST /workbench/{tool_id}/prefetch (warm in background), GM-only.

C1 instant_npc (prefetchable, pool_field="npcs"): a fully-formed NPC (name,
pronouns, occupation, want, secret, voice cue) drawn with zero visible latency;
frontend InstantNpcGenerator warms the pool on mount, "Conjure an NPC" draws,
Save as npc lore entry.

C2 npc_voice: performable voice/mannerism cues for an existing npc entry
(scoped to cues only — backstory/statblocks stay with #130); frontend
NpcVoiceGenerator lands via the shared WorkbenchResultActions (save / append to
the npc entry as a review draft / copy).

No new models or migrations. Backend 730 passed; frontend 389 passed;
ruff + eslint clean; build OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Suggest titles for the next occurrence of a recurring session series, sourced
from the previous occurrence's summary, with an always-available manual entry.

- series_titles workbench tool: params {session_id}; a context builder folds
  the most recent *prior* summarised occurrence in the same series into the
  prompt; output {suggestions: [{title, description}]}. Tolerant parser.
- SessionResponse now exposes series_id + series_occurrence_date (additive;
  not the bot contract) so the planning UI can identify series occurrences.
- SeriesTitleSuggester panel: pick an upcoming series occurrence, suggest
  titles, "Use this title" writes title+description onto the occurrence via the
  existing session PATCH — or type your own. Degrades to manual with no error
  when there's no prior summary or the LLM is unconfigured.

Per-occurrence titles already survive series edits: materialize_session_series
inserts occurrences with on_conflict_do_nothing, so it never clobbers an edited
title (criterion 1). No new models or migrations.

Backend 735 passed; frontend 397 passed; ruff + eslint clean; build OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace GM Planning's stacked max-w-2xl column with a responsive two-pane
workbench:

- Left rail: a tool palette grouped by category (Campaign structure: Arc Board
  / Beat Planner / Thread Tracker; Entities: Names / Instant NPC / NPC Voice;
  Story: Description / Rumor / Random Table / Session Prep / Series Titles;
  Loot: Loot Parcel). The active tool is highlighted.
- Center: the selected tool's panel (a WORKBENCH_TOOLS registry dispatches to
  the existing panel components — no panel internals changed).
- Right rail: the persistent GenerationHistory scratchpad (#138); its "Re-open"
  switches the active tool to the matching generator.
- Responsive: 3 columns at lg (palette | center | history), 2 at md (history
  drops full-width below), and a single column at mobile with the palette
  collapsed to a grouped <select>. Widened to max-w-7xl.

Scope note: this pass delivers the palette + dispatcher + history-rail shell
(the coherent-workbench UX). Collapsing the eight tested bespoke panels into a
single config-driven GeneratorPanel is intentionally deferred — they already
share the WorkbenchResultActions landing bar.

Frontend 397 passed; eslint 0 errors; build OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chore(release): v3.10.0 — GM Workbench
All checks were successful
CI / Backend lint (ruff) (pull_request) Successful in 49s
CI / Docker image build (pull_request) Successful in 21s
CI / Frontend tests, audit, and build (pull_request) Successful in 2m0s
CI / Bot tests and audit (pull_request) Successful in 2m11s
CI / Backend migration, tests, and audit (pull_request) Successful in 5m29s
8721dcc72c
Bump APP_VERSION 3.9.1 → 3.10.0 and add the v3.10.0 changelog entry for the
GM Workbench milestone (#134, #136, #138, #141, #144, #147, #149, #155, #158,
#191, #153). BOT_CONTRACT_VERSION stays 1 — no bot API changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Contributor

Deployed to Dev (docker-test / ai-dev-server) for smoke testing — 2026-07-21

Branch feat/v3.10-gm-workbench @ 8721dcc built and recreated via docker compose up -d --build (prod-safe stack). Verified:

  • APP_VERSION 3.10.0, BOT_CONTRACT_VERSION 1
  • Alembic head f0a1b2c3d4e5 — migrate one-shot applied e9f0a1b2c3d4 → f0a1b2c3d4e5 (#138 generation_results)
  • /ready → ok; all 7 services healthy

⚠️ LLM connectivity finding — blocks live Workbench testing with the current Dev model

Dev LLM is configured to http://10.3.0.28:8090, model qwen3.5 (OpenAI-compatible / llama.cpp path). Endpoint is reachable and the private IP passes the URL policy (ALLOW_PRIVATE_SERVICE_URLS is set on Dev). But qwen3.5 is a reasoning model that emits hidden reasoning_content, and that breaks structured generation:

  • Admin → Bot Settings "Test LLM" returns empty. _test_llm_llamacpp caps at max_tokens=16; the model spends all 16 on reasoning and never reaches visible content. Misleading but harmless.
  • Real transport (generate_structured_text, json_mode, 2048 cap): small outputs parse fine, but a large output (d20 table) failsfinish_reason='length', completion_tokens=2048, empty content_structured_llamacpp raises "empty response". Reasoning tokens exhaust the JSON budget before any answer is produced.

This is a model-compat issue, not a v3.10 regression — it hits existing json_mode features (lore proposals, stat blocks) the same way. To smoke-test the Workbench, Dev needs a non-reasoning model (or the same model served with thinking disabled).

Follow-up worth considering (separate from this release): graceful reasoning-model support — send a disable-thinking hint where the server supports it, and/or detect finish_reason='length' and surface a clear "model exhausted its token budget on reasoning" error instead of the generic empty-response message; also bump the max_tokens=16 in the Test button so the Admin test is meaningful for reasoning models.

🤖 Generated with Claude Code

**Deployed to Dev (`docker-test` / ai-dev-server) for smoke testing — 2026-07-21** Branch `feat/v3.10-gm-workbench` @ `8721dcc` built and recreated via `docker compose up -d --build` (prod-safe stack). Verified: - APP_VERSION **3.10.0**, BOT_CONTRACT_VERSION **1** - Alembic head **`f0a1b2c3d4e5`** — migrate one-shot applied `e9f0a1b2c3d4 → f0a1b2c3d4e5` (#138 `generation_results`) - `/ready` → ok; all 7 services healthy **⚠️ LLM connectivity finding — blocks live Workbench testing with the current Dev model** Dev LLM is configured to `http://10.3.0.28:8090`, model `qwen3.5` (OpenAI-compatible / llama.cpp path). Endpoint is reachable and the private IP passes the URL policy (`ALLOW_PRIVATE_SERVICE_URLS` is set on Dev). But `qwen3.5` is a **reasoning model** that emits hidden `reasoning_content`, and that breaks structured generation: - **Admin → Bot Settings "Test LLM" returns empty.** `_test_llm_llamacpp` caps at `max_tokens=16`; the model spends all 16 on reasoning and never reaches visible `content`. Misleading but harmless. - **Real transport (`generate_structured_text`, json_mode, 2048 cap):** small outputs parse fine, but a **large output (d20 table) fails** — `finish_reason='length'`, `completion_tokens=2048`, empty `content` → `_structured_llamacpp` raises "empty response". Reasoning tokens exhaust the JSON budget before any answer is produced. This is a **model-compat issue, not a v3.10 regression** — it hits existing json_mode features (lore proposals, stat blocks) the same way. To smoke-test the Workbench, Dev needs a **non-reasoning model** (or the same model served with thinking disabled). Follow-up worth considering (separate from this release): graceful reasoning-model support — send a disable-thinking hint where the server supports it, and/or detect `finish_reason='length'` and surface a clear "model exhausted its token budget on reasoning" error instead of the generic empty-response message; also bump the `max_tokens=16` in the Test button so the Admin test is meaningful for reasoning models. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(backend): disable model thinking on json-mode llama.cpp calls
Some checks failed
CI / Backend lint (ruff) (pull_request) Failing after 40s
CI / Docker image build (pull_request) Successful in 1m3s
CI / Bot tests and audit (pull_request) Successful in 2m11s
CI / Frontend tests, audit, and build (pull_request) Successful in 5m6s
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
07552a32fc
Reasoning models served via llama.cpp/vLLM (e.g. Qwen3) spend the entire
max_tokens budget on hidden reasoning_content before emitting any JSON, so
every non-trivial Workbench/structured generation returned an empty or
truncated response (finish_reason='length', empty content). Send
chat_template_kwargs={"enable_thinking": false} on json_mode llama.cpp
requests to turn the think block off; it is silently ignored by models that
don't support it, and structured output never benefits from reasoning tokens.

Adds transport-level tests asserting the json-mode vs prose request body.
Surfaced while dev-testing the v3.10 GM Workbench against a local qwen3.5
llama.cpp server (#136).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
style(backend): ruff format test_llm_transport
All checks were successful
CI / Backend lint (ruff) (pull_request) Successful in 51s
CI / Frontend tests, audit, and build (pull_request) Successful in 2m5s
CI / Bot tests and audit (pull_request) Successful in 2m5s
CI / Docker image build (pull_request) Successful in 3m25s
CI / Backend migration, tests, and audit (pull_request) Successful in 4m57s
ebd5b7edb0
Collapse the mock payload literal onto one line to satisfy
`ruff format --check` (ruff 0.4.4).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Contributor

Reasoning-model fix committed — Workbench now works with the Dev qwen3.5 server

Root cause: the Dev LLM is llama.cpp serving the reasoning model qwen3.5. Empirically, this build honors only chat_template_kwargs.enable_thinking=false to turn thinking off — top-level enable_thinking, the /no_think soft-switch, and reasoning_effort were all ignored (each still burned the full 2048-token budget on reasoning_content and returned empty/truncated JSON).

Fix (07552a3; format-only follow-up ebd5b7e): _structured_llamacpp now sends chat_template_kwargs={"enable_thinking": false} whenever json_mode is on. Scoped to structured output (which never benefits from reasoning tokens); silently ignored by models that don't support the switch. Backend-only, no contract bump. Added tests/test_llm_transport.py asserting the json-mode vs prose request body; CHANGELOG updated under [3.10.0] → Fixed.

Live validation on Dev (real generate_structured_text transport → real endpoint): the previously-failing d20 table now returns full valid JSON — title + all 20 rows, finish=stop. Both small and large Workbench-style outputs succeed. Dev is running the functional fix (07552a3); ebd5b7e is test-formatting only and doesn't change runtime behavior.

CI is re-running on ebd5b7e (the first run was cancelled by this push; its only failure was the ruff-format check now fixed). Ready for live Workbench smoke-testing against qwen3.5.

🤖 Generated with Claude Code

**Reasoning-model fix committed — Workbench now works with the Dev `qwen3.5` server** **Root cause:** the Dev LLM is `llama.cpp` serving the reasoning model `qwen3.5`. Empirically, this build honors only `chat_template_kwargs.enable_thinking=false` to turn thinking off — top-level `enable_thinking`, the `/no_think` soft-switch, and `reasoning_effort` were all ignored (each still burned the full 2048-token budget on `reasoning_content` and returned empty/truncated JSON). **Fix** (`07552a3`; format-only follow-up `ebd5b7e`): `_structured_llamacpp` now sends `chat_template_kwargs={"enable_thinking": false}` whenever `json_mode` is on. Scoped to structured output (which never benefits from reasoning tokens); silently ignored by models that don't support the switch. Backend-only, **no contract bump**. Added `tests/test_llm_transport.py` asserting the json-mode vs prose request body; CHANGELOG updated under `[3.10.0] → Fixed`. **Live validation on Dev** (real `generate_structured_text` transport → real endpoint): the previously-failing d20 table now returns full valid JSON — title + all 20 rows, `finish=stop`. Both small and large Workbench-style outputs succeed. Dev is running the functional fix (`07552a3`); `ebd5b7e` is test-formatting only and doesn't change runtime behavior. CI is re-running on `ebd5b7e` (the first run was cancelled by this push; its only failure was the ruff-format check now fixed). Ready for live Workbench smoke-testing against `qwen3.5`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Contributor

Live Workbench smoke sweep on Dev — all tools + async core + prefetch pool

Ran every registered tool through the real service layer (generation_service.run_tool) against the live qwen3.5 llama.cpp endpoint, on campaign A Merry Band of Thieves (15 lore entries, real NPC + session). CI #4268 is green (backend incl. new test_llm_transport.py, frontend, ruff).

Tool Result Latency
names 3 options, grounded, pronouns honored 4.4s
description one_liner + paragraph 2.9s
rumor 4 rumors (campaign-referencing) 3.9s
random_table d6, 6 entries 5.5s
loot_parcel 6 items, LootEntry-shaped fields 7.1s
instant_npc 3 NPCs, all 6 fields 7.2s
npc_voice 4 cues (existing NPC entry) 2.0s
series_titles 3 suggestions 2.6s
session_prep (async Celery core) ready, all 5 sections populated, grounded 9.5s
instant_npc (Redis prefetch pool) fill 3 → draw → 2 left, valid item

No failures. Outputs are genuinely campaign-grounded (reference Aramore, the Old Quarter, the party's dream figure). Both the async dispatch path (run_workbench_generation_core) and the pool round-trip work end-to-end. The sweep left one session_prep GenerationResult row in history and filled the instant_npc pool (2h TTL) — both benign/self-cleaning.

v3.10 is functionally validated end-to-end against a live reasoning model. Remaining before merge is your own UI pass + review.

🤖 Generated with Claude Code

**Live Workbench smoke sweep on Dev — all tools + async core + prefetch pool ✅** Ran every registered tool through the real service layer (`generation_service.run_tool`) against the live `qwen3.5` llama.cpp endpoint, on campaign *A Merry Band of Thieves* (15 lore entries, real NPC + session). CI #4268 is green (backend incl. new `test_llm_transport.py`, frontend, ruff). | Tool | Result | Latency | |---|---|---| | names | ✅ 3 options, grounded, pronouns honored | 4.4s | | description | ✅ one_liner + paragraph | 2.9s | | rumor | ✅ 4 rumors (campaign-referencing) | 3.9s | | random_table | ✅ d6, 6 entries | 5.5s | | loot_parcel | ✅ 6 items, `LootEntry`-shaped fields | 7.1s | | instant_npc | ✅ 3 NPCs, all 6 fields | 7.2s | | npc_voice | ✅ 4 cues (existing NPC entry) | 2.0s | | series_titles | ✅ 3 suggestions | 2.6s | | **session_prep** (async Celery core) | ✅ `ready`, all 5 sections populated, grounded | 9.5s | | **instant_npc** (Redis prefetch pool) | ✅ fill 3 → draw → 2 left, valid item | — | No failures. Outputs are genuinely campaign-grounded (reference Aramore, the Old Quarter, the party's dream figure). Both the async dispatch path (`run_workbench_generation_core`) and the pool round-trip work end-to-end. The sweep left one `session_prep` GenerationResult row in history and filled the `instant_npc` pool (2h TTL) — both benign/self-cleaning. **v3.10 is functionally validated end-to-end against a live reasoning model.** Remaining before merge is your own UI pass + review. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
refactor(frontend): config-driven GeneratorPanel + shared LorePicker (#234)
All checks were successful
CI / Backend lint (ruff) (pull_request) Successful in 3m28s
CI / Frontend tests, audit, and build (pull_request) Successful in 3m58s
CI / Docker image build (pull_request) Successful in 2m35s
CI / Bot tests and audit (pull_request) Successful in 6m40s
CI / Backend migration, tests, and audit (pull_request) Successful in 9m8s
df26d0bb59
Collapse the eight bespoke GM Workbench generator panels onto one config-driven
GeneratorPanel (sync / async-poll / instant-draw) reading a per-tool config in
workbenchTools.jsx, plus a shared searchable LorePicker (grouped chips,
server-side search) replacing the flat lore checkbox lists that did not scale.
Rename the page "GM Planning" -> "GM Workbench" and fix the header overflow.

The old *Generator.jsx panels are superseded by the shell wiring here and will
be removed alongside their ported tests in the follow-up commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
refactor(frontend): remove bespoke Workbench panels, port tests to config (#234)
Some checks failed
CI / Docker image build (pull_request) Successful in 35s
CI / Backend lint (ruff) (pull_request) Successful in 44s
CI / Bot tests and audit (pull_request) Successful in 2m15s
CI / Frontend tests, audit, and build (pull_request) Failing after 3m24s
CI / Backend migration, tests, and audit (pull_request) Successful in 5m19s
9225cde56e
Delete the eight superseded *Generator.jsx / *Suggester.jsx panels now that the
config-driven GeneratorPanel + workbenchTools configs drive every tool, and
replace their per-panel tests with LorePicker.test.jsx, GeneratorPanel.test.jsx,
and workbenchTools.test.jsx — covering the shared shell, the searchable picker,
and each tool's params/actions against the new architecture.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A cleared number input reads as "" (Number("") === 0, not NaN), so the count
field could land on an invalid 0; guard the empty case to snap back to the
field default. Also key LorePicker's selectedIds memo on value+multiple so it
stops recomputing every render.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(frontend): gentle "nothing to convert" state for stat conversion (#233)
Some checks failed
CI / Backend lint (ruff) (pull_request) Successful in 1m40s
CI / Frontend tests, audit, and build (pull_request) Failing after 1m55s
CI / Bot tests and audit (pull_request) Successful in 2m25s
CI / Docker image build (pull_request) Successful in 3m5s
CI / Backend migration, tests, and audit (pull_request) Successful in 5m37s
55c9bcfd32
Converting an entry whose text has no mappable stats (e.g. a flavor NPC) is an
expected empty result, not an LLM failure — surface it as an amber informational
state that steers the GM to generate a stat block from scratch, instead of the
red "The LLM failed to produce a draft" banner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
test(frontend): raise vitest testTimeout to 15s for CI-load headroom
All checks were successful
CI / Backend lint (ruff) (pull_request) Successful in 2m26s
CI / Frontend tests, audit, and build (pull_request) Successful in 2m58s
CI / Docker image build (pull_request) Successful in 1m53s
CI / Bot tests and audit (pull_request) Successful in 4m36s
CI / Backend migration, tests, and audit (pull_request) Successful in 6m59s
22e3671b7a
The suite grew with the GM Workbench tests; the 5s default intermittently timed
out heavy pages (e.g. Profile) that pass comfortably in isolation. Test-config
only — does not affect the built app bundle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Contributor

v3.10 scope expanded from dev testing — fully green, ready for review

Since this release PR opened, the branch grew beyond the original 11-issue milestone based on live dev testing. Full current scope of feat/v3.10-gm-workbench:

Added since the original PR:

  • Reasoning-model support (07552a3) — json-mode llama.cpp calls now disable the model's think block (chat_template_kwargs.enable_thinking=false), so Qwen3 / DeepSeek-style reasoning models work with the Workbench. This is what made the dev qwen3.5 server usable end-to-end.
  • GeneratorPanel refactor (#234) — the 8 bespoke tool panels collapsed onto one config-driven GeneratorPanel (sync / async-poll / instant-draw) + a per-tool config module (workbenchTools.jsx). The flat lore-checkbox lists are replaced by a shared searchable, grouped, chip-based LorePicker (server-side search — scales to large campaigns). Page renamed "GM Planning" → "GM Workbench" and the header overflow fixed. Old panels removed; tests ported to LorePicker/GeneratorPanel/workbenchTools specs.
  • convert-stats UX (#233) — converting a stat-less entry now shows a gentle amber "Nothing to convert" state that steers the GM to generate a stat block from scratch, instead of the red "The LLM failed to produce a draft" banner.

Verification:

  • CI green — all 5 jobs on 22e3671 (backend / bot / ruff / docker + frontend build + eslint + 387 vitest tests).
  • Deployed + healthy on dev (version 3.10.0); the served frontend bundle confirmed to carry the rename, the lore picker, and the convert-stats state.
  • All 10 Workbench tools were validated live against qwen3.5 earlier in this thread; the refactor is frontend-only, so the tool runners are unchanged.

Closes #233 and #234 in addition to the original 11 issues.

Awaiting your UI click-through on dev + the merge call. (Reload past the PWA service worker to pick up the new bundle.)

🤖 Generated with Claude Code

**v3.10 scope expanded from dev testing — fully green, ready for review** Since this release PR opened, the branch grew beyond the original 11-issue milestone based on live dev testing. Full current scope of `feat/v3.10-gm-workbench`: **Added since the original PR:** - **Reasoning-model support** (`07552a3`) — json-mode llama.cpp calls now disable the model's think block (`chat_template_kwargs.enable_thinking=false`), so Qwen3 / DeepSeek-style reasoning models work with the Workbench. This is what made the dev `qwen3.5` server usable end-to-end. - **GeneratorPanel refactor (#234)** — the 8 bespoke tool panels collapsed onto one config-driven `GeneratorPanel` (sync / async-poll / instant-draw) + a per-tool config module (`workbenchTools.jsx`). The flat lore-checkbox lists are replaced by a shared **searchable, grouped, chip-based `LorePicker`** (server-side search — scales to large campaigns). Page renamed **"GM Planning" → "GM Workbench"** and the header overflow fixed. Old panels removed; tests ported to `LorePicker`/`GeneratorPanel`/`workbenchTools` specs. - **convert-stats UX (#233)** — converting a stat-less entry now shows a gentle amber "Nothing to convert" state that steers the GM to *generate a stat block from scratch*, instead of the red "The LLM failed to produce a draft" banner. **Verification:** - **CI green — all 5 jobs** on `22e3671` (backend / bot / ruff / docker + frontend build + eslint + **387** vitest tests). - **Deployed + healthy on dev** (version 3.10.0); the served frontend bundle confirmed to carry the rename, the lore picker, and the convert-stats state. - All 10 Workbench tools were validated live against `qwen3.5` earlier in this thread; the refactor is frontend-only, so the tool runners are unchanged. Closes #233 and #234 in addition to the original 11 issues. **Awaiting your UI click-through on dev + the merge call.** (Reload past the PWA service worker to pick up the new bundle.) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(frontend): put NavBar inside the GM Workbench header (#234)
All checks were successful
CI / Docker image build (pull_request) Successful in 31s
CI / Backend lint (ruff) (pull_request) Successful in 52s
CI / Frontend tests, audit, and build (pull_request) Successful in 2m2s
CI / Bot tests and audit (pull_request) Successful in 2m11s
CI / Backend migration, tests, and audit (pull_request) Successful in 5m12s
078e0d67c7
CampaignPlanning was the only page rendering <NavBar /> as a separate element
below the <header> instead of inside it — so the user nav floated on its own
line under the breadcrumb. Move it into the header's justify-between row to
match every other page, and tuck the prefetch status into the left group
(hidden below lg) so it never crowds the bar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs(changelog): round out v3.10.0 — rename, lore picker, convert-stats; release date
All checks were successful
CI / Docker image build (pull_request) Successful in 28s
CI / Backend lint (ruff) (pull_request) Successful in 2m22s
CI / Frontend tests, audit, and build (pull_request) Successful in 3m13s
CI / Bot tests and audit (pull_request) Successful in 4m47s
CI / Backend migration, tests, and audit (pull_request) Successful in 6m37s
4dcf98d764
Add the GM Workbench rename, the searchable lore-grounding picker (#234), and
the convert-stats "nothing to convert" fix (#233) to the v3.10.0 changelog, and
set the release date to 2026-07-21.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude-bot deleted branch feat/v3.10-gm-workbench 2026-07-22 02:17:14 +00:00
Sign in to join this conversation.
No description provided.