v0.20.0
TBD
- [x] Add sliders in the tokenizer drawer for fine-tuning zoom, pan x, and pan y. Be sure to change the UI with the header "Tokenization Editor", not the obsolete tokenizer.
- [x] Bug: there's an extra outline around tokens signifying faction. This should not appear. Instead, the actual token border color (if using tokenized art) should default to the faction color.
- [x] If an action uses await (e.g. await roll()), the user is in targeting mode until it returns. Exit targeting mode as soon as the action starts to execute to avoid this.
- [x] In various places where campaign and party typeaheads are shown (new character modal, character detail page, etc), the suggestions remain visible after clicking one of them. Instead, hide the suggestions until another key is pressed.
- [x] Add a "visible to players" boolean in the Token model (default true), which the campaign owner can toggle on any token via the context menu. When false, the token is hidden from all non-owner users in the UI. The GM sees the token as transparent.
- [not fixed] Bug: portraits sometimes don't fill their container, leaving vertical letterboxing (checker-pattern background visible on the sides and/or top). Repro: a portrait whose aspect ratio doesn't match the slot — e.g. the mobile/centered current-token card. Root cause: several sites render portraits with
fit='contain'(Mantine<Image>) orobjectFit: 'contain', which preserves aspect ratio and pads with transparency. Notable offenders:apps/web/src/components/UI/CurrentTokenBar/CurrentTokenBar.tsx(line ~955: mobile branch is always'contain', desktop non-portrait-art branch is'contain');apps/web/src/components/UI/FloatingTokenWindow/FloatingTokenWindow.tsxonly affects the 32×32 header thumbnail and is fine; also auditMobileContextBar.tsx,PartyPanel.tsx, and any other portrait surfaces. Fix: for portrait-display surfaces, switch tofit='cover'(orobjectFit: 'cover') AND setstyle={{ objectPosition: 'top' }}so the image is anchored at the top-left and any overflow bleeds off the bottom (per spec: "image origin = top left, overflow from the bottom"). - [partial] Add a "show on hover" toggle to the property-definition editor in both the system properties UI and the campaign properties UI. The
IPropertyDefinition.showOnHoverfield already exists end-to-end (DB columnproperty_definitions.show_on_hover, zod schemas inpackages/server-core/src/routes/propertyDefinitions.ts, dnd5e fixtures set HP/AC to true, andTokenHoverLabelalready reads it), but there is no UI to toggle it, so users can't turn the flag on for custom properties or for newly added systems. Add aSwitchin the property-definition row editor wired to the existing PATCH endpoint. (Companion data-fix scripts shipped with this version:scripts/verify-show-on-hover-hp-ac.sqlandscripts/fix-show-on-hover-hp-ac.sql.)- show-on-hover toggle appears in system properties editor, but not in campaign properties
- [x] The actions written by the Generate Monster/Encounter process are no longer working since 3d dice, saying "Larval Metroid 2 attacks against Zelena with Energy Drain: [object Promise] to hit, [object Promise] necrotic damage". They need to await the roll() function. Change the logic so they work similar to this example:
let die = await roll('1d20');
let dc = die + 10;
let crit = die > 19;
let dmg = await roll('5d8+15');
return `Tentacle attack hits a ${dc}, inflicting ${crit?'critical':''} damage of ${dmg}!`;
- [x] Bug: the hover UI when hovering over a token on the map currently shows the token's name (correct). It should also show the faction in parentheses below the name, but it doesn't currently.
- [x] In the token hover UI, add a line to the top of the section below the token (above the lighting badges) which shows HP (e.g. "HP: 32/45"), and if the token has a "show_on_hover" property set to true, also show any properties with that flag in the same format (e.g. "HP: 32/45, AC: 15, etc"). (Implementation note: the original "blocked by portal architecture" assumption was incorrect —
TokenHoverLabelalready receives the token as a prop and readstoken.metadata. The fix was making the showOnHover lookup understand range-typed properties stored across<key>+<key>_max, matchingpropKey()fromCurrentTokenBar.) - [x] column "show_on_hover" does not exist in the prod db. Is a migration missing? (Investigated v0.20.0: no migration is missing. The SQL migration
0041_add_show_on_hover.sqlexists, andpostgresApplyColumnMigrations()inpackages/server-core/src/db.tsrunsALTER TABLE property_definitions ADD COLUMN IF NOT EXISTS show_on_hover BOOLEAN DEFAULT FALSE;on every Postgres init. Prod just needs a redeploy that includes the v0.19.0 server changes for the column to appear. No code change required.) - [x] when a token's turn starts, fire off the focus-and-ping mechanic that happens when you double click their portrait
- [x] the focus-and-ping animation should be twice as long (duration) and three times as large (scale), with a more noticeable color (or combination of colors)
- [x] Add an option in the ... of CurrentTokenBar to manually refresh the token from the Character, to pick up missing actions, etc
- [x] Character import tokens have actions that return pure text with die rolls in it. Since roll() isn't used, no 3d dice are rolled, nor do they use applyDamage(), forcing the GM to manually apply damage. They should be changed to emit actions such as:
const attackRoll = await roll('1d20+strength');
const damage = await roll('1d8+strength');
return `<strong>${token.name || 'Token'}</strong> attacks!<br/> Attack: <strong>${attackRoll}</strong><br/> Damage: <strong>${damage}</strong>`;
- [x] When you press H to enter harm mode, focus the damage input
- [x] Impersonated token needs a spinning ring too, like the selected token. Let's make it green and white instead of red and white, and spin the other way. If the impersonated token is also selected, show both rings, one inside the other (selected inside, impersonated outside).
- [x] The character context in the /play route can't scroll. The Mantine ScrollArea's great-grandparent Paper element has overflow: hidden
- [x] The modals spawned by clicking on feats, powers etc in the Character Context in /play route are lower z-index than CurrentTokenBar and the context itself, making them impossible to read
- [x] The content of the modals spawned by clicking on feats, powers etc in the Character Context in /play route is incorrect for System 8. It is showing the brief description, but should show the full content, which is shown in the character sheet build mode when clicking on feats/powers/etc.
- [x] heal/harm mode is affecting the impersonated token, but should affect the selected one if any is selected
- [x] In heal/harm mode, show a label indicating the token that will be healed/harmed. Also show its thumbnail.
- [x] Generated monsters aren't populating their ability score properties, such as Strength. They're just empty.
- [x] in grouped initiative mode, End Turn should cycle to the next member of the group, not end the turn for all members of the group at once
- [x] in grouped initiative mode, add a button to the Initiative Tracker labeled End Group Turn that ends all remaining turns in the group at once and cycles to the next initiative entry
- [x] heal mode does nothing...hp are unchanged
- start with 3 hp
- heal for 100
- nothing happens
- harm for 1....and it says 103->102 in the chat log, but the token still shows 3 hp
- approve the harm, and token has 102 hp.
- deny it, hard refresh, token has 3 hp
- [x] healing a target who is dying should remove the dying condition
- [x] harm mode should auto-approve when done by the campaign owner (but not applyDamage())
- [x] Show the label of the current layer in the palette, right of the icon
- [x] Remove iniative tracker from chat log. Add an initiative tab right of transcript that shows the current intiative tracker. When not on that tab, show a "mini-initiative" pinned to the top of the chat log (fixed, top = bottom of the tab bar) that shows a horizontal set of initiative tokens, with current on the left and the rest ordered to the right. Each has an x button to remove them from initiative, and can be dragged left and right to reorder them. The Next Turn button is pinned to the right of this UI, reduced to a right facing caret icon with no text and a green background.
- All users can see mini-initiative and the initiative tab, but only the GM can reorder and remove tokens, and only the GM has the Next Turn button.
- [x] Remove the current "connected to server" banner in the chat log. Replace with a small green or red dot in the Chat context header, left of the gear icon for settings.
- [x] if not already implemented, remember tokenization border settings per user. If the user's last token had no border, the next token they create should also have no border, and vice versa.
- [x] Move undo/redo to the palette, rightmost
- [x] Every key event in Notes logs a change in the Change Log. Throttle it!
- [x] Add an action function that allows you to draw walls and such (TODO: spec this out)
- This could probably be handled by the draw() function, just add walls and any other shapes that targeting mode has but draw doesn't
- [x] Add metadata to shapes (text, default empty). When mousing over a shape, show the metadata in a tooltip. When drawing a shape, allow the user to enter metadata for it in a modal spawned by a palette button. Add an argument of the draw() function that allows you to set the shape's metadata when drawing it from an action.
- [x] Data layer: add
metadatacolumn to drawings + vbl_shapes; round-trip through hooks; newmetadataarg on draw(). - [x] UI: drawing-toolbar button + modal to edit metadata on the selected drawing.
- [x] UI: hover tooltip overlay that reads
hoveredDrawingIdand shows the drawing's metadata.
- [x] Data layer: add
- [x] Update character import and monster generator action code to use draw() for any duration-based area effects. (TODO: make sure system8 power actions describe their duration)
- [x] Character import:
createWikiActioninapps/web/src/utils/characterToToken.tsemits adraw()call when the wiki action has both anareaand a non-emptyduration. - [x] InsertMonster (D&D 5e API): emit
draw()for AoE actions/abilities/legendary actions whose description includes a parseable duration. - [x] Monster generator (
apps/web/src/utils/monsterGenerator.ts/tokenFromMonster.ts): no existing AoE+duration abilities; documented and no-op for now. (Adding new abilities witharea+durationshould follow the same draw() pattern.) - [x] system8 power-action duration metadata is content-side (wiki action docs); add a doc note in
docs/PROJECT_CONTEXT.mdpointing module authors at the newarea+duration→ draw() behaviour.
- [x] Character import:
- [x] Dice roller spawned by pressing D in the map uses a different UI than the other two dice rollers and doesn't spawn 3d dice. Use the same UI as the chat log dice roller, which already spawns 3d dice.
- [x] ChatLog: listen for a
chat-dice-rollwindow event and route it through the existingroll(sides)flow so external callers can trigger the same 3D dice pipeline as the chat log'sDiceButtons. - [x] Map dice roller: replace
DicePaletteModalUI with the sameDiceButtonscomponent used in the chat log, and dispatch the new event (opening the chat subcontext if needed). Remove the bespoke Math.randomhandleDiceRollpath inWebGLMap.tsx.
- [x] ChatLog: listen for a
- [x] Generated monster attack actions (algorithmic Generate Monster + Generate Encounter) emitted
roll('1d20+x', false)withoutawait. Sinceroll()now returns a Promise, sendMessage rendered[object Promise]andapplyDamagereceived a Promise. FixedgenerateAttackCodeinapps/web/src/utils/monsterGenerator.tsto emitawait roll(...). AI mode (MonsterGenerator.tsx + InsertMonster.tsx) and the Sneak Attack ability already usedawait roll. Character import was already correct. - [x] When a character is imported as a token, its artwork should match the character artwork. A pattern that has emerged, and should be corrected, is:
- Character has one source image, which is assigned to both token and portrait.
- Character is imported.
- Token has two copies of the source image, but neither token nor portrait is assigned to either.
- The correct behavior in this case is for the token to have the same single source image, and the same token/portrait assignment and settings, as the character. The token can then differ per user commands.
- This is probably caused by a vestigial "auto-duplicate" feature that is no longer compatible with the way artwork is handled.
- [x] Change the "Background Removal Editor" drawer as such:
- Change "remove background | unchanged" from a tab bar to a toggle (cosmetic)
- Add pan, zoom, and crop functionality, with an enforced 2:3 ratio for the crop box. Default pan/zoom/crop should match the functionality of CSS "contain" with the image top matching the container top, the image width matching the container width, and any overflow (or underflow) being at the bottom.
- New title: "Portrait Editor"
Playtesting: GM Edition
- [x] When "import map" is used from the campaign page, and an ordinary image file is provided, interpret that as "make a new make with this image as the background token". Use default textures and parameters.
- [x] When you right-click to Create Encounter, the tokens should be placed where the click was (but not stacked on each other or other tokens)
- [x] When you use the + button or other means to Create Encounter, the tokens should be placed in the camera's current position
- [x] No way to invite to campaign from the actual campaign page. Add a Share button which allows inviting players to the campaign (same modal as spawned using Invite Players in the /play route)
- [x] In the targeting info in chat log, don't say "valid" say "in range: yes/no"
- [x] bug when selecting tokens: Switch is not defined
ErrorBoundary caught an error in MapTool: ReferenceError: Switch is not defined
at CurrentTokenBar (CurrentTokenBar.tsx:1488:24)
context:
{parsedMetadata?.lightSourceType && (
<Group>
<Switch
label="Light Enabled"
checked={parsedMetadata?.lightEnabled ?? true}
onChange={(e) => handleMetadataUpdate((prev: any) => ({ ...prev, lightEnabled: e.currentTarget.checked }))}
/>
</Group>
)}
- [x] Fix the color scheme for example actions in the actions modal (right now it's light mode when set to dark mode, hard to read text against the background)
- [x] In the New Action modal there's an example of an attack action. Change it to use this format:
const attackRoll = await roll(`1d20+${getProperty('str')}`);
const damage = await roll(`1d8+${getProperty('str')}`);
target.applyDamage(damage);
return `<strong>${token.name || 'Token'}</strong> attacks!<br/> Attack: <strong>${attackRoll}</strong><br/> Damage: <strong>${damage}</strong>`;
- [x] add 'z' shortcut to zoom in on a token (with 0.5 second transition). Zooms on selected token if exists, else impersonated token if exists, else nothing. Zoomed token will be dead center in the map, taking up about 33% of the map width/height
- [x] when 'Refresh From Character' is used on a token, the notification of success or failure is on a z-index that makes it appear behind the ChatLog
- [x] in the ... menu in CurrentTokenBar, Move to Map and Copy Token are disabled. Whenever an option is disabled, a tooltip should explain why.
- [x] when the chat context is minimized, the selected token's CurrentTokenBar moves to the right edge of the map, but the context background is still visible, so the CurrentTokenBar is overlapped.
- [x] make the Chat header in the Chat Log a borderless dropdown, with Transcript and Initiative Tracker as options. Remove the chat | transcript | initiative tabs, as this new dropdown will control the visible tab.
- Remaining issues:
- The caret should appear next the Chat label in the fixed header
- Said label should match the current tab (e.g. say "Initiative Tracker" when on the initiative tracker tab)
- Options of the dropdown should always include all 3 options (chat, transcript, initiative), but the current one should be visually highlighted and unclickable
- Remaining issues:
- [x] add an option to a Map where it is hidden to campaign players who are not the campaign owner
- [x] changes to /campaigns/:id page
- The header says 'choose a map' but should be the campaign name
- There's an orphaned label between the header and the breadcrumbs that says
Campaign: ${currentCampaign.name}. Remove this label. - Above the map list, add a 2nd level header that says 'maps'
- Move the Create Map button underneath this header
- Make the Create Map button a segmented button, with the right segment being a dropdown with options for 'copy map from another campaign', 'share map', and 'import map'.
- copy map from another campaign: spawns a modal with a dropdown of the user's other campaigns. When they select one, it shows a list of maps in that campaign. When they select a map, it copies that map (including tokens and drawings) into the current campaign.
- share map: a feature being specced out elsewhere in this version, so TBD for now
- [x] characters/:id UI changes:
- Replace the input for character name with a standard header (h1) that just displays the name. Add a small edit button next to it that, when clicked, prompts the user for a new name.
- add a primary action button to the top section that is the Add to Map action
- add a segment to the right of the primary button with a caret, with options:
- Transfer Ownership
- Delete Character
- add a share icon to the left of the primary action button with options:
- Share Character
- Export to YAML
- all of the above options are pre-existing, so no new work there
- remove the existing ... button
- remove the background, border, and position: sticky from the header section
- Remaining issues:
- Add a "change name" button next to the character name that prompts the user for a new name and updates it. This is currently missing.
- [x] Generate Monster modal UI changes
- Rename to Create Monster
- Present two buttons: Generate with Algorithm and Generate with AI
- Replace the Create Monster & Edit button with two buttons:
- Insert into Map: skips the Create Token modal, uses default token settings, and just inserts the monster into the map with the current camera position
- Customize Token: same behavior as current Create Monster & Edit button
Top Menu Revamp
- [x] undo/redo were moved from PlayTopBar into the palette, but they remain in PlayTopBar. remove them
- [ ] Great menu revamp: (PlayTopBar) — broken down into atomic sub-tasks (each its own commit):
- [x] 3a. Add "Edit Grid" entry to ToolPalette (spawns the existing Edit Grid modal)
- [x] 3b. ToolPalette VBL sub-palette: add map lighting dropdown (disabled / outdoor / indoor), time-of-day slider when "outdoor", and "Add Light Source" button
- [x] 3c. ToolPalette Fog of War sub-palette: add toggle to enable/disable FOW (currently only in Map menu)
- [x] 3d. PlayTopBar center: render
${currentCampaign.name} - ${currentMap.name}as a breadcrumb with two dropdowns (campaign switcher with edit/invite … sub-actions; map switcher with new/edit/import/export/delete … sub-actions) - [x] 3e. PlayTopBar upper-right: move mic button into the ChatLog header
- [x] 3f. PlayTopBar upper-right: retain the Help menu (right-mounted)
- [x] 3g. PlayTopBar upper-right: add Share button → modal with 3 tabs (Invite User to Campaign — existing UI; Share This Map — TBD placeholder; Share This Campaign — TBD placeholder)
- [x] 3h. Remove the legacy MapMenu from PlayTopBar (only after 3a–3g land so no functionality is lost)
-
- [x] Chat context header uncrowding:
- Hide the chat icon left of the word Chat
- Hide the green connection status dot
- When Initiative Tracker tab is selected, show "Initiative" instead of "Initiative Tracker" in the header
- [x] In the modal spawned by the Share button in PlayTopBar:
- [x] Widen the modal so the tab bar doesn't wrap
- [x] In the Invite User to Campaign tab, there is another tab bar. Convert this to a vertical tab bar on the left side of the modal, since it looks weird to have two horizontal tab bars stacked on top of each other.
- [x] Move the Edit Grid modal down so it doesn't overlap the Palette. Also ensure its z-index is higher than the palette.
- [x] Hide the standalone map switcher in the upper left of the map pane, which is now redundant with the campaign/map breadcrumb in PlayTopBar.
- [x] Allow System 8 campaigns to use Insert Monster with the bundled D&D 5e monster catalog. Stopgap until System 8–native monsters land.