v0.17.0
2026-04-28
Misc
- [x] Focus Token: a function to center the camera on a token and highlight it briefly with an animation. Implement it in the following places:
- [x] Double clicking icon in initiative should focus on that token
- [x] Double clicking the portrait in CurrentTokenBar should focus on that token
- [x] Add a "focus" button to the selected and impersonated token UIs (CurrentTokenBar) that centers the camera on that token when clicked, and creates a brief animation highlighting said token on the map. It should look similar to the x and ... buttons on the portrait currently.
- [x] Keep track of which characters I view and how often. Use that to inform the sort order for characters to show in "My Characters" on the campaign home page.
- [x] In VBL existing shape edit mode, add an option to move the shape to the background token behind it. Add an icon left of the trash icon in the sub-palette. When clicked, it copies the shape to whatever background token is behind the shape (if any), in the right coordinates such that it would appear in the same place it already was, at least until the background moves. Then it deletes the shape from the VBL layer.
- [x] Hide the current layer selector in the top menu in Play mode. Instead, add a new element to the palette, rightmost, showing the icon of the current layer, and a caret indicating it's a dropdown. When clicked, the user can select the current layer; in the modal, each row has an icon and a text label (e.g. Background, Token, etc).
- [x] Selected token UI, when token is a background token:
- Hide current TokenContext that is shown only for background tokens
- Hide current element, only shown for background tokens, that is bottom-mounted and spans the map frame, with a grey background
- Show the same UI as other selected tokens: CurrentTokenBar, bottom right. However, only show:
- "portrait art" is the token art, with dark grey background in case the image is of unusual aspect ratio
- no name label, since background tokens don't have names
- special label saying "background token selected"
- edit button, with a right section for "other actions" (see next point)
- Edit VBL
- Auto-detect VBL
- Delete token
- Detect grid size
- Restore aspect ratio
- Move/Copy to Map
- Snap to Grid toggle (if possible, otherwise add to main UI)
- No actions, properties, conditions, auras, vision/light, etc
- [x] In the VBL palette, add a label (bottom center of the sub-palette) that says "Editing Map VBL. Click here to add VBL to a token"
- When that link is clicked, the user can click a token (regardless of current token layer), and if that token is on the background layer, they are switched to editing that token's VBL instead of the map VBL. The label text also changes to "Editing [token name] VBL. Click here to edit map VBL"
- [x] When I double-click on an initiative entry, it should focus the map on that token, centering the camera on them and selecting them.
Bugs
- [x] Character Sheet list and detail pages: hide the delete character option when the current user is not the owner of the character.
- [x] VBL line tool is acting like a pen. The behavior should be that each click draws a line from the previous point clicked (or starts the first line if no previous point), and the line is finalized on Escape, Enter, or changing out of line mode. Clicking the original point should connect the shape and end line mode as well.
Dice Roller
- [x] Using new 3d dice (https://github.com/3d-dice/dice-box), replacing old implementation
- [x] Speed up dice animation by 50% without changing physics, if possible
- [x] Add the themes from https://github.com/3d-dice/dice-themes and allow users to choose their dice in the chat settings. Characters can also have an assigned set of dice. All rolls made by that character use the assigned dice.
- [x] Phase 1: per-user theme picker in Chat Settings (themes loaded on demand from jsDelivr via
externalThemes);dice_themecolumn added touser_settings. - [ ] Phase 2: per-character
diceThemeassignment; rolls attributed to a character use that character's theme.
- [x] Phase 1: per-user theme picker in Chat Settings (themes loaded on demand from jsDelivr via
- [x] Whenever a dice expression appears in chat (e.g. "1d20"), the dice are rolled immediately and the result is shown in chat. No 3d dice appear. Instead, the chat message should show an animation (i.e. a d20 is rolling in place) while the 3d dice roll is triggered, and the 3d dice roll result replaces that animation when it is determined.
- [x] In the dice roller (both chat log and CurrentTokenBar, add a d100 roller button)
Chat Log
- [x] Change the fixed portion of the ChatLog as follows:
- Move the Chat Settings (gear icon) to the header of the ChatLog
- Move the Microphone icon from this area to the top menu, left of the Connected Players icon
- Add a tooltip to this icon that says "record your voice in the transcript"
- Expand the chat input to take up the space liberated by the two moved buttons
Voice Transcription
- [x] There is an existing feature using the Chrome Web Speech API, triggered by a microphone icon in the chat input area. Currently, it transcribes the user's words to the chat log. Change as follows:
- At the top of the chat log, under the header, a fixed tab bar is added with two tabs: "Chat" and "Transcript". The user can switch between these two views by clicking the tabs.
- The transcript tab shows the voice data from the Web Speech API, attributed to each user, using their chosen chat color, avatar, etc, as normal for chat log.
- The data model for chat messages is extended to include a "type" field, which can be "chat" or "transcript". This allows us to distinguish between regular chat messages and voice transcript messages.
- [x] In the Connected Players dialog, under the Bring All To This Map button, add a button that says Request Transcription, which prompts all connected users to click the microphone icon to enable voice transcription if they haven't already. This is necessary because of browser permissions around the Web Speech API.
Module System
- [x] CI
deploy-frontendsjob installs hosted modules intonode_modules(viascripts/install-hosted-modules-frontend.sh) before building the web bundle, so the Vite plugin can resolve their client entries. - [x] Vite plugin (
apps/web/vite-plugin-modules.ts) falls back tomodules.hosted.jsonwhenmodules.local.jsonis absent or empty, so hosted production builds wire up first-party module client entries. - [x] Formalized module API and registry, with support for server-side API routes and client-side UI contributions. See modules doc for detailed TODOs.
- [x] Create a new monorepo for Purple Cat first-party modules. Each module will be independent--the user can install zero, one, any, or all of them as they choose--but they are in a monorepo to avoid repo bloat. Each module should be publishable to the purple cat registry (eventually npm) independently.
- [x] Move existing modules (e.g. vtt-assets) into this new monorepo and ensure they still work.
- [x] For vtt-assets specifically, switch from a hard-coded dependency to use the module loader configured in modules.local.json instead
- [x] Create a new module in vtt-modules for System 8. All system 8 specific functionality in the current platform repo should be in this module instead, including the character sheet validation logic, the system fixtures, and logic to construct URLs to the system 8 wiki.
Map Features
Dice
- [x] Replaced current 3d physics-simulated dice roller with https://github.com/3d-dice/dice-box
VBL
- [x] Re-enabled VBL-on-token functionality
- [x] Add a lighting tab to CurrentTokenBar with the same fields as the Vision&Light section of the Edit Token modal
- [x] In the TokenContext (which is visible only for background tokens), hide the Properties, Actions, Conditions, and Auras sections.
- [x] When hovering over a token, show a visual indicator what they can see.
- If lighting is outdoor and the time is day, or if lighting is disabled, assume unlimited range.
- If lighting is outdoor and the time is night, or lighting is indoor, use existing logic that considers token vision type and lighting.
- Render as such: the whole map is shaded translucent black, except the visible area which is clear.
- Beside the cursor, show badges for:
- Token's vision setting (normal, darkvision, blindsight, etc)
- Token's light source, if any (else "no light source")
- Lighting condition (day, night, dark, dim, etc)
Movement Blocking
- [x] Make necessary data model and UI changes such that every shape in the VBL can be either vision-blocking, movement-blocking, or both.
- In the VBL sub-palette, add a dropdown to choose this mode. This affects all new shapes added thereafter.
- In select mode, if a VBL shape is selected, temporarily change this dropdown to affect the current shape only. Indicate this in the UI.
- Distinguish these modes with color:
- Vision-blocking is blue
- Movement-blocking is red
- Both is purple
- In the map logic, implement movement blocking for tokens.
- When a token tries to move through a movement-blocking shape, it is stopped at the edge of that shape and cannot pass through it. This applies to click-drag movement and to movement from actions or macros.
- If a token is already inside a movement-blocking shape, it can't move.
- The GM can always move tokens. When moving through MBL, the squares violating MBL are highlighted in red, and a notification near the GM's current cursor says "violates movement-blocking layer". But the movement proceeds as normal.
- If the user moves their token from a valid location to another valid location, but the path has MBL-violating squares, attempt to route around the MBL shape. If no valid path exists, block the movement as described above. Otherwise the movement is allowed.
- When movement is blocked, the experience is:
- Click-drag:
- The path is drawn as normal, but squares violating MBL are highlighted in red (same as the GM view).
- If a valid path can be found, draw that path as well in green (current path color) and display no notification, and allow movement normally.
- If no valid path is found, or the source/destination is in an invalid square, show a notification near the cursor saying "movement is impossible". When the user releases the mouse button, the token simply doesn't move from its original position and the pathing and notification disappear.
- Keyboard movement:
- If source and destination are valid, and there is no intervening space, move as normal.
- If the movement is blocked by MBL, show an animation of the token "wiggling" briefly, but don't move it. Show a notification near the cursor saying "movement is impossible".
- Click-drag:
- In the VBL sub-palette, add a dropdown to choose this mode. This affects all new shapes added thereafter.
Character Sheet
- [x] Currency
- Create a new trait of systems and campaigns called Currencies. Schema:
- name: string
- abbreviation: string (e.g. "gp", "sp", "cp")
- symbol: string (e.g. "£", "$", "₽") optional
- valueInBaseCurrency: number (e.g. 1 for gp, 0.1 for sp, 0.01 for cp)
- imageUrl: string optional
- link: 'campaign' | 'system' | 'character'
- link-id: string (the id of the campaign, system, or character this currency is linked to)
- Add a column to campaigns and systems for "baseCurrency".
- Add the following built-in currencies to bundled systems:
- 5e (both): Platinum Pieces (pp), Gold Pieces (gp), Silver Pieces (sp), Copper Pieces (cp). Base currency is GP
- System 8: Platinum Pieces (pp), Gold Pieces (gp), Silver Pieces (sp), Copper Pieces (cp). Base currency is CP
- Add a tab to Edit Campaign and Edit System to edit these values. Campaigns can add currencies on top of the inherited ones from the system.
- In the character sheet, above the Equipment section, add a Currency section. This is a horizontal list of the currencies defined in the system/campaign, showing the name, symbol, and an input for the current amount. The user can edit the amount directly in this section.
- In the currency section, add a button labeled 'Add Custom Currency', allowing the user to add an arbitrary currency that isn't defined in the system or campaign. This is useful for things like tracking renown, corruption, or other non-monetary resources.
- Create a new trait of systems and campaigns called Currencies. Schema:
- [x] Each item in a character's equipment now has a Quantity (default 1) which can be changed in the UI.
- [x] In the Equipment section, below the list of equipment, add a text input. When the user types, show autocomplete suggestions for equipment.
- Arrow keys navigate up and down the autocomplete suggestions
- Pressing tab completes the autocomplete suggestion currently highlighted
- Pressing enter adds the currently highlighted suggestion to the character's equipment list (or creates a new custom item if no suggestion is highlighted)
- Clicking a suggestion adds it to the character's equipment list
- When creating a new custom item, assume what the user typed was the name, and leave the description blank for now
- When equipment is added in this way, this field still exists, it's just cleared and focused again for the user to add another item if they wish.
- [x] Ledger
- New section in the Character Sheet, under Artwork
- Two tabs: Transactions and Change Log
- Whenever any value in the character changes (ability scores, equipment added/subtracted, currency changed, etc), log the change.
- Transactions shows currency and equipment changes
- Change Log shows everything else
- Intelligently squash certain types of change (e.g. clicking + 14 times to add 14 silver pieces should show as one transaction of +14 sp, not 14 transactions of +1 sp; same for editing ability scores and the like)
- Include a timestamp for each change, and which user made the change (current user should show as "you", other users as username)
- [x] The logic for how many feats, powers, and proficiency bonuses a character has at a given level is currently hard-coded into this app.
- For system 8, the platform will visit https://system8.purplecat.wiki/character/basics/advancement.json, which has metadata such as:
proficiency_per_level: 2
max_proficiency_per_level: level + 3
feat_levels: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
power_levels: [1, 3, 7, 11, 15, 19]
rank_levels: [5, 9, 13, 17]
bonus_feats_at_level_1: 3
bonus_powers_at_level_1: 1
bonus_proficiency_points_at_level_1: 6
- The character sheet will use this metadata to determine how many feats, powers, and proficiency points a character has at a given level, and when they gain new ones.
- NOTE: this url is not yet live, because System 8 v8.5 is not live yet. Here is a fixture we can use for development (only the important part):
"metadata": {
"proficiency_per_level": 2,
"max_proficiency_per_level": "level + 3",
"feat_levels": [
2,
4,
6,
8,
10,
12,
14,
16,
18,
20
],
"power_levels": [
1,
3,
7,
11,
15,
19
],
"rank_levels": [
5,
9,
13,
17
],
"bonus_feats_at_level_1": 3,
"bonus_powers_at_level_1": 1,
"bonus_proficiency_points_at_level_1": 6
},