v0.120.0 — Chat UI improvements
Merged 2026-10-10 (43159121, tagged v0.120.0) — built on version/0.120.0.
- [x] Release bookkeeping: v0.119.1's notes move to docs/help/changelog/v0.119.1.md, with a line under Released.
Chat UI Improvements
- [x] The applyDamage() modal can overflow its contents. Simple fix: allow the buttons to break to new lines.
- [x] Bug: character-based custom chat bubble colors have stopped working. User-based colors still work.
- [x] The entire chat message, including whitespace, is the hit zone for the timestamp tooltip. Remove this tooltip from the chat message container. Add a small tabler "clock" icon next to the bubble (to the left of your bubbles; to the right of others' bubbles), which is the new hit zone for the timestamp tooltip.
- [x] Make the chat avatars clickable, opening menus with option:
- Focus camera on this token (only on a /play route with a map id)
- Open character sheet (opens /campaigns/:id/characters/:id in a new tab; only if avatar is a character)
- Chat settings (opens chat settings in the right sidebar, as if user clicked settings, then chat settings; only if user clicks their own avatar or their impersonated token's avatar)
- Direct message: opens a new channel with just you and that user (disabled if the avatar is you, with tooltip explaining why)
- [x] It appears that chat messages, including from Actions, issued from an unlinked token, are attribute to the user, using the token's art for the avatar. The bubbles use the user's color. We can keep the avatar as is, but the bubbles should use the token's chat color, falling back to default. Tokens unlinked to Characters are allowed to have a custom chat color. Only use the user's chat color when no token or character is impersonated.
- [x] The data model of Chat channels doesn't match the tabs. Combat Log and Transcript are scoped to chat channels, just like chat messages, so all three should appear to be sub-elements of the channel. But the tabs make it look like they should contain their own independent channels. The change:
- [x] Remove Transcript and Combat Log from the tab bar.
- [x] Combat Log is now a sibling channel for each map's chat channel, presented as such in the channels list.
- [x] Transcript is now a sibling channel to the campaign's chat channel, presented as such in the channels list.
- [x] Remove Timeline button from tab bar.
- [x] When the Timeline is collapsed, show a button on the bottom center (just above the Timeline's horizontal center) to show the Timeline.
- [x] Move the Search icon from the left nav bottom to the top bar right (all routes), to the left of Settings.
- [x] The campaign or map icon in the 'current channel title' element (top center of the chat view) is vertically centered between two rows of text. It should be vertically aligned with the campaign/map name.
- [x] In the 'current channel title' element (top center of the chat view), it currently shows the channel name on top, and the # online below. Change as follows:
- [x] 2nd row is now same font as 1st row, subtler font color, and is a subtitle, such as "map chat", "campaign chat", "private chat", etc (explaining the chat channel's identity)
- [x] to the left of the "popout" icon, show a green (or red, if websocket problems detected) status icon, which when clicked shows a popover showing the # of connected users in the chat room (e.g. '5 players in this chat room') followed by a list of the usernames in the room.
- [x] Default chat channels:
- [x] When the route includes a map id or slug, the default chat channel is the map chat room. (This implies map chat is the default on /play routes)
- [x] When the route doesn't include a map id or slug, but does include a campaign id or slug, the default chat channel is the campaign chat room. (This is the default for main nav > campaigns > chat)
Conditions
Revamping the display of conditions on the map.
All conditions now have either an assigned Tabler icon, or a string of text which we'll render in as close to the tabler font as possible to look like Tabler icons.
Conditions now have "severity" (low, med, high).
The way conditions render on the map will be:
-
All conditions of the highest severity currently suffered by the target will appear within the token itself. All lesser conditions will appear below the token.
-
"Within the token" means within the circle inscribed by the square containing the token (whether or not the token has tokenized art).
-
The condition display area is a circle 80% the diameter of the inscribed circle of the token's square, concentric with the token's inscribed circle.
-
In the display area, all conditions of the highest severity level will display as follows:
- One condition: takes the whole area
- Two conditions: each is half the diameter, they're side-by-side on the vertical centerline
- Three conditions: a triangle, one on top (horizontal centered), two on bottom, the center point of all 3 is the center of the circle
- Four conditions: a 2x2 square of conditions, each the same size as each other, the whole fills the available space
- Five conditions: the "X" pattern used on a standard pip-based d6, filling the available space.
- Six conditions: the standard pip pattern on a d6 indicating "6".
- 7+ conditions: just pick 6 and overflow the rest.
-
By 'highest severity level' that means if a token has 7 low, 3 med, and 0 high, it would show the 3 medium conditions in the token itself.
-
Overflow area beneath the token: matches the current conditions display before these changes are made (except the icons are different, per below), and also:
- Severity low: neutral (grey) bg color
- Severity med: yellow bg color
- Severity high: red bg color
-
[ ] Human Todo: make sure 5e conditions all have new icons and severity
-
[x] Condition background colors:
- To reconcile the positivity-based colors of Mini-Me with the severity-based colors on the map, implement these colors:
- Negative:
- Low severity: yellow
- Medium severity: orange
- High severity: red
- Neutral: grey
- Positive:
- Low severity: pale green
- Medium severity: green
- High severity: glowing green
- We haven't defined positive 'severity' yet so default to medium
- Use these background colors consistently for conditions in Mini-Me and on the map
-
[x] Render the conditions along with TimedEffects in the hover nameplate when hovering over the map tokens
- Use the chip rendering look used in Mini-Me for the condition name. The condition description will use the same text treatment as TimedEffects are using.
- Change the TimedEffect/Condition area to be left aligned
Data
(col1: name, col2: tabler icon or string, col3: severity)
Addled "-wis" med Blinded eye-off med Charmed heart med Chilled snowflake low Clumsy "-dex" med Concentrating brain low Dazed dots low Dead skull high Deafened ear-off low Dying heart-off high Exhaustion zzz med Feebleminded "-int" med Frail "-str" med Frightened ghost-3 med Frozen snowflake (more intense?) high Grappled circles-relation med Incapacitated alert-octagon high Invisible ghost-3 low Meek "-cha" low Panicked ghost-2 high Paralyzed player-pause high Petrified wall high Poisoned biohazard low Prone bed-flat low Restrained shoe-off med Rooted shoe-off med Shaken ghost-3 low Slowed multiplier-0-5x med Stunned cancel high Unbalanced fall low Unconscious zzz high Weakened "-str" med Wracked bandage high
Tasks
- [x] Conditions carry a glyph (
icon, a Tabler outline icon's name, oriconText) and aseverity(migration 0061,conditions/presentation.tsin shared). Seeding fills them from the table above; a row from before them falls back to it by name. dnd5e 0.22.0, system8 0.21.0, system5 0.10.0 bump their fixture versions so existing databases fill their rows. - [x] One glyph everywhere: the vendored Tabler icons, or the text drawn as an icon, in every condition badge; the condition editor sets glyph and severity.
- [x] The highest-severity conditions draw inside the token, in the d6 pip layouts.
- [x] The rest draw beneath the token, on grey, yellow or red by severity.
Target UI and Hover UI
Hover UI: the UI that appears when hovering over tokens on the map.
Target UI: a new UI that appears when a token is targeted.
The Target UI
The Target UI shows the user's current target: a single token, several tokens, or a targeted area. It is per map (it needs a map context) and lasts until the target changes, rather than existing only while an action runs. How a target gets chosen is out of scope here; this is the UI that shows it.
- [x] Target Mini-Me (single target): a Mini-Me for the target in the upper right corner of the map, horizontally mirrored from the impersonated token's Mini-Me.
- [x] Mirrored rendering: portrait on the right, right-aligned text, bars extending from the right, and action buttons and conditions stacking from the right.
- [x] Distinct label: labelled 'Target', with the target's name in a different text color.
- [x] Quick Facts, between the token's name and its first bar:
Range: ${rangeMeasurementBetweenImpersonatedAndTarget}. Faction: ${faction}. - [x] Action buttons: instead of the target's own actions, buttons in the same style for:
- Request Roll: the requestRoll menu the token hover UI uses today
- Heal/Harm: the heal/harm modal for the target
- Target With an Action: the 'target with an action' menu from the token hover UI
- Edit Notes: the target's notes
- Path: the path from the impersonated token to the target, drawn as if the user had click-dragged the impersonated token onto the target and not released. It uses the standard pathing, which honours the campaign's diagonal measurement rules, the movement-blocking layer, etc.
- [x] Multi-Target Mode (several tokens targeted): in the same upper-right area, a vertical list of the targets.
- [x] Each row is a Mini-Me, reduced: half-size portrait, token name at 75% size, at most 1 bar, no action buttons, no Quick Facts.
- [x] The list clips and scrolls after 3 rows.
- [x] Beneath the list, action buttons: Request Roll (from all), Heal/Harm (all), Apply/Remove Condition (to/from all).
- [x] Area Target Mode (an area of effect targeted):
- [x] In the Target Mini-Me area, the label 'Targeted Area'.
- [x] Beneath it, the Multi-Target Mode list of the tokens inside that area.
- [x] Beneath that, an action bar with:
- Target Area With Action: a menu of compatible actions (an area and range matching the targeted area). Choosing one runs that action once against the targeted area.
- Multi-Target Mode's buttons: Request Roll, Heal/Harm, Apply/Remove Condition.
The Target UI appears only when not in mobile mode.
Actions taken against targeted token are disabled if you aren't impersonating a token or character, since there is no subject to take the actions.
How to target
Ways to target a token:
- [x] Single-clicking a token on the map targets it. (unbinding single click from 'impersonate this token')
- [x] Click-dragging on the map, assuming the initial click didn't land on a token in the current layer, allows creating a selection rectangle. Tokens inside the rectangle are all selected when the click-drag ends.
- [x] A new button is added to the Palette, with a target icon.
- [x] Clicking this button enters Targeting Mode (just like with a targeted action).
- [x] By default, it's in single-target mode.
- [x] A palette of controls appears while in Targeting Mode, allows the user to change the min/max number of targets, range, and whether to select targets at all or draw an area of effect instead (and which area to use).
- [x] Range only applies when you are impersonating a token or character; otherwise disable it (with explanation tooltip) and ignore any range restrictions in targeting.
Other related changes:
- [x] Handle double-click on a token as "impersonate this token"
- [x] In the token context menu...
- [x] Add a new section at the very top.
- [x] To this section, add "Target this Token" and "Impersonate this Token".
- [x] Remove the "Impersonate this Token" from the lower section it currently resides in.
Hotkey changes
- [x] Unbind T from 'focus chat input' and give it to 'Enter Targeting Mode'. Exact same effect as clicking the Target Mode button.
- [x] While Target Mode is active and set to single-target (not multi-target or area), the Tab key cycles between targets within range (or ignores range when not impersonating). This should be true for Targeting Mode triggered by Actions as well as the new button or hotkey. Tab handling should only occur during Targeting Mode--it should delegate to browser default all other times.
- [x] Escape currently closes sidebars one at a time. When a token is targeted, the first Esc key will un-target the current token (or tokens, or area). Subsequent Esc presses will close sidebars as currently done.
- [x] If the user presses Escape when no sidebars are open and no target is targeted, show a toast saying "To stop impersonating your current token, click the x button next to the token's name."
Hover UI changes
Desktop only:
- [x] No longer show the lower section (action buttons, vision/lighting chips) on hover; they're redundant with the Target UI
Mobile Mode
Differences that already exist:
- The hover UI doesn't show on hover, since you can't hover on mobile. It shows on tap.
In a way, with a tap action required, the current Hover UI is the Target UI. It accomplishes most of the same function without using up precious real estate on the screen. I'm thinking we keep the hover (tap) UI and behavior unchanged on mobile. Tapping a token does actual target it, but the only UI indicator of that is what's already appearing on mobile today.
Mobile issues:
- [x] Impersonating token A and tapping token B, the hover UI's "use an action against the target" button showed B's actions, not A's (the desktop Target UI shows A's).
- Both read the same list (
useHoverTokenActions().impersonatedActions), so the likeliest cause is the buttons, not the data: a phone shows no tooltips, and the die's Request Roll menu lists B's own rolls. On a tapped-open hover each button now has a caption (Heal/Harm, Use action, Ask to roll, Notes), and each menu has a heading: "A's actions, at B" and "Ask B to roll".
- Both read the same list (
- [x] Double-tapping a token impersonates it. A phone's taps go to the map's touch handlers, not the token mesh's double-click, so the touch-end handler now remembers the last token tap (
utils/doubleTap.ts). A second tap on the same token within 300 ms impersonates it and closes the hover the first tap opened. - [x] When impersonating a character or token, the /play bottom bar's Me tab shows its avatar instead of the generic icon. It uses the art the Me header's switcher shows (the linked character's portrait, else the token's art), and the initials when there is none.
- [x] The tap-opened hover UI stayed where it opened when the map panned under it (a tap-drag on the map; on desktop, the keyboard camera keys). It follows the token now:
SelectedTokenProjectorre-projects the hovered token whenever the camera's pan or zoom changes, and the hover keeps the anchor it opened with until then. - [x]
?bubbleDebugcan't be set in an installed PWA (no URL bar), so the phone's Palette drawer ends with a Chat bubble debug switch (uiStore.bubbleDebug, which the URL flag still turns on at load). On the desktop, Debug Mode (?) shows the control as before.
The roadmap moves to its own repository
The platform's docs describe what ships; the roadmap describes what might. They now live apart: the roadmap is the planning repository (a sibling checkout, ../planning), so plans can be edited without touching a platform branch that may be mid-change. Publishing it is not decided yet; for now nothing in the platform links to it.
- [x]
docs/help/roadmap/(194 entries, the Near-term plan, the dashboard) moved toplanning/roadmap/, its git history with it (filter-branch --subdirectory-filter). The pages are unchanged; their links back into platform docs do not resolve there yet. - [x] The roadmap's tooling moved with it:
validateRoadmap,generateRoadmap,roadmapSchema,roadmapChecks,publishRoadmapManifestand six self-tests leaveapps/help/scripts/, andapps/help'sbuild,devandtestno longer validate, generate or publish a roadmap. Inplanning, staleness reads this repository's git log ($PLATFORM_DIR, else../platform).planningis at v0.1.0, with its own README and CHANGELOG. - [x] Help has no Roadmap tab (
/help/roadmaplands on the Guide like any unknown tab), the Help index and FAQ no longer point at one, and the help site's roadmap styles are gone. - [x] 271 links into the roadmap from 92 platform documents (mostly the per-version changelog notes, and
CHANGELOG.md) keep their text and lose the link. Code comments citing a spec now nameplanning/roadmap/…; the two migration comments that cite one are left as they are.ROADMAP.mdsays where the roadmap went.
Misc
- [x] Make the 'End Turn' button(s) the standard primary CTA color (in New Retro, it's orange). Currently it's a one-off green.
- [x] In the mobile Me tab, when a character is impersonated, show a button next to Treasury in the header, which allows the user to load the new character sheet rendered by the system module (that looks more like a paper character sheet). The same button toggles back to the old character sheet.