[GM Workbench] Read-aloud description generator (A3) #141

Closed
opened 2026-07-15 22:02:15 +00:00 by claude-bot · 2 comments
Contributor

Motivation / Context

The GM Workbench catalog (docs/.internal/gm-planning-expansion-2026-07-15.md §3, Group A) identifies boxed-text/read-aloud description generation as one of the two highest value-to-effort tools with zero overlap against existing issues — the report recommends shipping it first alongside the rumor generator. GMs constantly need sensory descriptions for places, NPCs, items, and scenes to read aloud at the table; today there is no tool for this at all, only the name generator.

This is the first tool built on top of the Phase 0 foundation (#134, #136, #138) and establishes the pattern later tools (random tables, loot, rumors) follow: a registered tool, a result that can be saved as new lore or appended to an existing entry via the draft rail.

Approach

  • Register a new tool (tool_id="description" or similar) in the GENERATOR_TOOLS registry (generation_service.py, from #136).
  • Inputs: subject (free text, or a picked existing LoreEntry), tone, game_system. Uses the campaign_context and, when a lore entry is picked, lore_context builders from #136.
  • Output: two lengths — a one-liner and a full paragraph — generated via generate_structured_text in JSON mode (from llm_service.py, #134). Model the prompt/system-prompt pattern on the existing name-generator constants (_NAME_GENERATOR_SYSTEM_PROMPT, audio_service.py:220-228) and per-category hints (_NAME_CATEGORY_DESCRIPTION_HINTS, audio_service.py:163-218) — this tool is prose-shaped rather than options-shaped, but reuses the same generate_structured_text/JSON-mode call pattern.
  • Fast/small output → runs inline (sync_allowed=True), same as the cache-miss name path (campaigns.py:3119-3139).
  • Output landing, per the action row pattern (§4): result is written to GenerationResult (scratch, #138) by default. From there:
    • "Save as lore entry" creates a new LoreEntry directly, same pattern as canonical-name auto-creation (sessions.py:283-296).
    • "Append to entry" (when the subject is an existing lore entry) starts a merge/append LoreEntryDraft via draft_service.py, reusing the existing draft-review rail (start_merge_draft, draft_service.py:93) and WikiDraftReview.jsx — the same review surface #130 uses for its draft modes.
    • "Copy" — plain clipboard copy for pasting into a VTT chat window, no backend involved.
  • Frontend: for this issue, a minimal form + result card is sufficient (subject input/picker, tone select, length toggle, action row). It does not need the full two-pane workbench shell — that's #153 (Workbench UX reorganization), which will later host this tool's panel inside the unified GeneratorPanel.

Dependencies

  • #134 (Extract LLM core into llm_service) — generation calls route through it.
  • #136 (Generalized workbench endpoint + tool registry) — this tool registers into that registry and endpoint; no bespoke route.
  • #138 (Generation scratchpad/history) — results are persisted as GenerationResult rows for the scratchpad.

Out of scope

  • The full two-pane workbench UI (tool palette, unified GeneratorPanel) — #153 (Workbench UX reorganization); this issue ships its own minimal panel.
  • Image generation — D1 (portrait/token prompts) is a separate, later tool; this issue is text-only boxed descriptions.
  • Statblocks or backstory expansion for existing npc/creature entries — owned by #130's draft modes.

Acceptance criteria

  • description tool is registered and reachable via POST /api/campaigns/{campaign_id}/workbench/description/generate, GM-only.
  • Given a free-text subject or a picked lore entry, tone, and game_system context, returns both a one-liner and a full-paragraph description.
  • Result appears in the campaign's GenerationResult history (scratchpad).
  • "Save as lore entry" creates a new LoreEntry with the generated description in body.
  • "Append to entry" (existing-entry case) starts a LoreEntryDraft that surfaces in WikiDraftReview.jsx for GM review/edit/approve — never auto-writes to an existing entry's body.
  • "Copy" copies the full-paragraph text to clipboard.
## Motivation / Context The GM Workbench catalog (`docs/.internal/gm-planning-expansion-2026-07-15.md` §3, Group A) identifies boxed-text/read-aloud description generation as one of the two highest value-to-effort tools with zero overlap against existing issues — the report recommends shipping it first alongside the rumor generator. GMs constantly need sensory descriptions for places, NPCs, items, and scenes to read aloud at the table; today there is no tool for this at all, only the name generator. This is the first tool built on top of the Phase 0 foundation (#134, #136, #138) and establishes the pattern later tools (random tables, loot, rumors) follow: a registered tool, a result that can be saved as new lore or appended to an existing entry via the draft rail. ## Approach - Register a new tool (`tool_id="description"` or similar) in the `GENERATOR_TOOLS` registry (`generation_service.py`, from #136). - **Inputs**: subject (free text, or a picked existing `LoreEntry`), tone, `game_system`. Uses the `campaign_context` and, when a lore entry is picked, `lore_context` builders from #136. - **Output**: two lengths — a one-liner and a full paragraph — generated via `generate_structured_text` in JSON mode (from `llm_service.py`, #134). Model the prompt/system-prompt pattern on the existing name-generator constants (`_NAME_GENERATOR_SYSTEM_PROMPT`, `audio_service.py:220-228`) and per-category hints (`_NAME_CATEGORY_DESCRIPTION_HINTS`, `audio_service.py:163-218`) — this tool is prose-shaped rather than options-shaped, but reuses the same `generate_structured_text`/JSON-mode call pattern. - Fast/small output → runs inline (`sync_allowed=True`), same as the cache-miss name path (`campaigns.py:3119-3139`). - **Output landing**, per the action row pattern (§4): result is written to `GenerationResult` (scratch, #138) by default. From there: - "Save as lore entry" creates a new `LoreEntry` directly, same pattern as canonical-name auto-creation (`sessions.py:283-296`). - "Append to entry" (when the subject is an existing lore entry) starts a merge/append `LoreEntryDraft` via `draft_service.py`, reusing the existing draft-review rail (`start_merge_draft`, `draft_service.py:93`) and `WikiDraftReview.jsx` — the same review surface #130 uses for its draft modes. - "Copy" — plain clipboard copy for pasting into a VTT chat window, no backend involved. - Frontend: for this issue, a minimal form + result card is sufficient (subject input/picker, tone select, length toggle, action row). It does not need the full two-pane workbench shell — that's **#153** (Workbench UX reorganization), which will later host this tool's panel inside the unified `GeneratorPanel`. ## Dependencies - **#134** (Extract LLM core into `llm_service`) — generation calls route through it. - **#136** (Generalized workbench endpoint + tool registry) — this tool registers into that registry and endpoint; no bespoke route. - **#138** (Generation scratchpad/history) — results are persisted as `GenerationResult` rows for the scratchpad. ## Out of scope - The full two-pane workbench UI (tool palette, unified `GeneratorPanel`) — **#153** (Workbench UX reorganization); this issue ships its own minimal panel. - Image generation — D1 (portrait/token prompts) is a separate, later tool; this issue is text-only boxed descriptions. - Statblocks or backstory expansion for existing npc/creature entries — owned by #130's draft modes. ## Acceptance criteria - `description` tool is registered and reachable via `POST /api/campaigns/{campaign_id}/workbench/description/generate`, GM-only. - Given a free-text subject or a picked lore entry, tone, and `game_system` context, returns both a one-liner and a full-paragraph description. - Result appears in the campaign's `GenerationResult` history (scratchpad). - "Save as lore entry" creates a new `LoreEntry` with the generated description in `body`. - "Append to entry" (existing-entry case) starts a `LoreEntryDraft` that surfaces in `WikiDraftReview.jsx` for GM review/edit/approve — never auto-writes to an existing entry's body. - "Copy" copies the full-paragraph text to clipboard.
Author
Contributor

Picking this up (first Workbench tool, on the merged #134/#136/#138 foundation) on feat/141-description-generator → PR onto feat/v3.10-gm-workbench. Registers a description tool (one-liner + full paragraph, boxed/read-aloud), a minimal generate panel, and the reusable result-landing actions — save-as-new-lore, append-to-entry via a LoreEntryDraft (WikiDraftReview), copy — that the other five generators will reuse. Backend + minimal frontend; full workbench shell is #153.

Picking this up (first Workbench tool, on the merged #134/#136/#138 foundation) on `feat/141-description-generator` → PR onto `feat/v3.10-gm-workbench`. Registers a `description` tool (one-liner + full paragraph, boxed/read-aloud), a minimal generate panel, and the reusable result-landing actions — save-as-new-lore, append-to-entry via a `LoreEntryDraft` (WikiDraftReview), copy — that the other five generators will reuse. Backend + minimal frontend; full workbench shell is #153.
Author
Contributor

Done — merged to the feat/v3.10-gm-workbench integration branch via PR #219.

Delivered

  • description generator (read-aloud one-liner + paragraph, tone-aware, optionally grounded in a picked lore entry) on the generic workbench engine (#136), persisting a GenerationResult (#138).
  • Reusable WorkbenchResultActions bar (the next five generators reuse it): Save as lore entry, Append to entry (POST /lore/{entry_id}/append-draft → a READY review draft, no LLM call, GM-only, one-per-entry), Copy.
  • Context builders now receive params, so entry-scoped tools fold the picked entry into the prompt.

Verification (Docker): backend 694 passed, frontend 353 passed / 35 files, prod build OK, ruff check + ruff format --check clean, eslint 0 errors.

Two test-only issues fixed during verification: the GM-only test hit the shared get_current_user override collision (requesting both gm_client+player_client in one test) — reseeded via db, dropped gm_client; and the clipboard spy is now installed after userEvent.setup() via defineProperty (jsdom navigator.clipboard is getter-only). No bot API surface touched.

Done — merged to the `feat/v3.10-gm-workbench` integration branch via PR #219. **Delivered** - `description` generator (read-aloud one-liner + paragraph, tone-aware, optionally grounded in a picked lore entry) on the generic workbench engine (#136), persisting a `GenerationResult` (#138). - Reusable `WorkbenchResultActions` bar (the next five generators reuse it): **Save as lore entry**, **Append to entry** (`POST /lore/{entry_id}/append-draft` → a READY review draft, no LLM call, GM-only, one-per-entry), **Copy**. - Context builders now receive `params`, so entry-scoped tools fold the picked entry into the prompt. **Verification (Docker):** backend 694 passed, frontend 353 passed / 35 files, prod build OK, `ruff check` + `ruff format --check` clean, eslint 0 errors. Two test-only issues fixed during verification: the GM-only test hit the shared `get_current_user` override collision (requesting both `gm_client`+`player_client` in one test) — reseeded via `db`, dropped `gm_client`; and the clipboard spy is now installed after `userEvent.setup()` via `defineProperty` (jsdom `navigator.clipboard` is getter-only). No bot API surface touched.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Quest-Board#141
No description provided.