Skip to content

v0.25.0

2026-05-18

  • [x] Actions should be grouped in Character Context by their category
  • [x] Change "view" tab label to "play" in the Character Context
  • [x] Bug: when user clicks an ability score modifier in Character Context, the chat log message for the roll is in the context of the user, but it should be in the character's context (chat color, avatar).
  • [ ] Connected Players UI in the PlayTopBar
    • [x] It appears this only shows players who are looking at the current map (relative to the person who wants to know who is connected). It should show all players who are connected to the campaign, regardless of what map they are looking at.
    • [x] Change "Bring All To This Map" functionality. Instead of only working for players who are currently on the same map, it should work for all players, regardless of part of the UI they're looking at. However, it won't immediately bring them to the map; instead, it will send them a notification that the GM has requested they view the map, and they can click on the notification to be taken there. Spawn a sticky notification with a button to go to the map, and a button to dismiss. This notification shouldn't steal focus in case they were in a text field or something.
  • [x] During the Character Import process, attacks of type "Spellcasting Implement" in System 8 have an action generated for their token. This action rolls the spell attack and damage. However, it should prompt the user to choose, light, medium, or heavy damage before rolling damage, as these have different formulas:
    • Light: ${rank}d4 + ${spellcasting ability modifier / 2} + ${hit power * rank}
    • Medium: ${rank}d8 + ${spellcasting ability modifier} + ${hit power * rank}
    • Heavy: ${rank}d12 + ${spellcasting ability modifier * 1.5} + ${hit power * rank}
    • Round down for fractions.
  • [x] When the user types a die expression like 1d20 in chat, there is a standard UI that shows the result and allows things like adding advantage. However, the roll() macro function does not do this. When roll() is called, use this same UI, which has a spinning die while rolling, and then shows the result when done, with the same options for advantage, etc. Broken down:
    • [x] Audit the two pipelines and document the divergence: how chat-typed expressions currently flow (chat input → [DICE_ROLL_PENDING:…] token → 3D roll → [DICE_ROLL:…] token → DiceRollMessage with advantage UI) vs. how roll() inside an action currently flows (actionExecutor → roll3DAndAwait / actionDiceRunner → emits its own [DICE_ROLL:…] token, no pending spinner, no advantage UI). Capture findings in a short design note inside the commit body.
    • [x] Extract a single shared helper (e.g. rollExpressionWithChatUI(expression, { actorName? })) that emits the pending token, awaits 3D resolution, swaps in the final [DICE_ROLL:…] token, and returns the numeric total + per-die results. Both the chat-input path and the action roll() path must end up calling it. No behavior change to the chat-input path; covered by a contract test that pins token shapes.
    • [x] Route action roll() through the shared helper. actionExecutor's roll() builtin awaits the helper instead of roll3DAndAwait and stops emitting its own duplicate [DICE_ROLL:…] token. The action runtime keeps receiving the same {total, rolls, breakdown} shape it does today. Update/extend actionDiceRunner.test.ts to assert the new single-token flow and confirm no duplicate messages.
    • [x] Make the advantage / disadvantage controls available on action-issued d20 rolls. The advantage UI on the DiceRollMessage should be enabled the same way it is for chat-typed rolls (only for single-d20 expressions). The second roll generated by clicking "advantage" replaces the displayed result correctly and that any downstream action awaiting roll() is NOT re-triggered (the advantage reroll updates the message in-place only, since the action has already moved on with the original total — call this out explicitly in the commit body).
    • [x] Manual smoke + final cleanup: run a real character action with await roll('1d20+5') and confirm the spinner → advantage-capable result UI works end-to-end. Remove any now-dead duplicate code paths (roll3DAndAwait if fully orphaned, or its now-unused branches). Mark the parent TODO [x]. NOTE: legacy runActionRoll / roll3DAndAwait / actionRollResolver retained — still load-bearing for ActionExecutor.testAction (dry-run capture) and useTargetingMode.createQueuingContext (chat-queueing for → Target annotation). Both opt out of the unified path by setting sendMessageWithId: undefined. Full removal deferred — see new TODO at top of v0.26.0.
  • [x] In ChatLog, show a border around messages generated by a single invocation of an action. For instance, an action might call sendMessage() and roll(), both of which send independent chat messages, along with whatever the function returns. These should all be visually grouped together in some sort of frame, labeled with the name of the action and the name of the token it originated from.
  • [x] In the Generate Encounter UI, there's a mini token art UI that helps GMs assign images quickly to a variety of tokens. Copy this UI to the Generate Monster modal, visible after the monster's stats (once generated), on the screen where the user can insert the token or customize it. Pre-populate a Google Image Search with either the prompt (if AI) or a combination of the monster name and type (if non-AI) to help the user find an image to use for the token.
  • [x] Sometimes the 3d dice take a while to start rolling (using the dddice module). When a 3d die roll begins, show a spinner in the chat log message until the dice start rolling, so the user knows something is happening. DDDice has an onRollStart callback which can be used for this purpose.
  • [x] The tooltip when mousing over an avatar in the ChatLog is behind the ChatLog itself in terms of z-index. Increase the z-index of the tooltip so it appears on top of the ChatLog.

Character Actions

  • [x] Add a new section to the Character Detail page, below Attacks, called Actions. These are the same as Token Actions, and use the same modal to create/edit them. When a token is created from this character, the token's actions are pre-populated with the character's Actions.
  • [x] In a character Action, add a "character" variable, which exposes the character's properties, similar to the "token" variable. This is read-only, and should not expose any internal properties. Instead, it should have the following methods:
    • getProperty(propertyName:string): any
    • getCurrentTokenArtworkId(): int
    • getCurrentPortraitArtworkId(): int

Map Generator

  • [x] LLM-driven map generation feature. Open from the map palette → prompt + style → LLM proposes regions/objects/tokens with asset slots → asset-sourcing step lets the user accept library matches, swap alternates, mark slots for GenAI, or reject them → render step paints textured drawings, places objects, and spawns tokens with mapGenerationId stamped on every row so the whole batch can be removed atomically. Phase 7 status: unit tests (117), UI component tests (62), and a full-pipeline integration test (3) green; Phase 7.3 E2E (Playwright) and the deferred backend wiring (LLM POST route, slot-match endpoint, render dispatcher + realtime channel) are split out to v0.26.0 — Playwright isn't installed in the workspace and there are no real endpoints yet to MSW-mock against, so the modal currently runs against client-side stubs (MapGeneratorClient / SlotMatcherClient / RenderMapClient). Demo path: open modal → see prompt/result/assets/render flow with simulated 60ms ticks per render phase. Full spec + per-phase implementation notes in docs/todo/features/map-generator.md.