v0.115.0 — Chat bubbles
Merged 2026-10-03 (ab701380) — built on version/0.115.0; the tag is in
the changelog. No module release.
Chat bubbles: the sender's colour on every bubble, square avatar chits, the design's
dice with the roll menu on the die, the Incoming damage tray, round separators, api.chat.brief and actions as threads;
Help and Notifications in the footer, Profile and Settings top right, Settings as two pages when narrow, and Mini-Me's
artwork and token menus.
Roadmap
- [x] Roadmap: Chat bubbles — the "Design modern chat bubbles" mockup restyled in New Retro, with a static demo (Parchment / Dungeon) to review before deciding whether to build it; ~15 tasks and eight decisions (CB-D1…D8). Bubble colours: yours iMessage blue by default or your chat colour as-is (text picked for contrast); everyone else's neutral, with their user's or character's chat colour as a thick left edge. The demo includes a threaded action: the lead message shows an authored brief that is rewritten as the thread's rolls land ("I cast Fireball at DC 19, rolled 32 damage, and hit 5 targets."), with the saves and damage folded beneath it
- [x] Roadmap: Timeline redesign — the owner's requirements for a horizontal Timeline across the bottom of the map (ticks, effect bars, token art; infinite scroll and zoom), filed as a proposal
- [x] Roadmap: Chat bubbles slotted as v0.115.0 and in flight (the decisions as taken and what was added while building are on the entry); New Retro marked shipped in v0.114.0
Chat bubbles
From the Chat bubbles design. The composer, send button and channel header are unchanged.
- [x] Bubble colours: your messages take the sender's chat colour (the speaking character's, else yours) as-is, with dark or light text picked for contrast, falling back to bright blue; everyone else's are neutral, with their colour as a thick left edge —
utils/chatBubbleColor.ts: white text unless it falls below 3:1 against the colour, then dark;--chat-sent-default(#007aff light skins, #0a84ff dark),--chat-received-bg/-borderper skin,--chat-author-coloras a 6px left edge - [x] Avatars: square-cornered with a hairline and a hard shadow, the image where there is one and initials on the sender's colour where not; your own avatar beside your messages —
ChatAvataris a 32px chit; a token's art on the map keeps its map framing; a failed image falls back to the initials; a sender with no colour gets a stable muted one by name - [x] Die indicators in the design's shapes and skin inks; several dice of one type are one indicator with a second copy stacked behind it (left and below) and the group's total, no
×N; the roll reads inline as dice + modifier = an inked total; a pending die rocks with a?—DieIndicator(shapes in a 100-unit box, the d10 a kite); inks and fills are--chat-die-dN/--chat-die-dN-fill, today's palette in every skin but New Retro, whose dice in your own bubble are parchment tiles in both modes; a queued roll is a still, faded? - [x] The roll menu opens from the die itself (the
⋯button is gone), and gains "Show detail" for the breakdown modal — the whole roll (dice and total) is the menu's target, keyboard-reachable; "Show detail" is always there, even where a roll has no other options wired - [x] Damage in chat is the design's "Incoming damage" tray: the amount (click to modify), Decline, Modify and Apply; once resolved, a one-line receipt —
DamagePendingMessage: Apply with a changed amount is a modify, unchanged an approval; the breakdown and HP on one line; anyone without the controls sees it awaiting approval; the receipt opens to the full application on click, as the card did - [x] A new initiative round is a separator ("Round 2"), not a bubble; the "
, your turn has begun!" messages are gone — utils/chatSystemLine.tsreads initiative's own "Round N has begun." (round 1 reads "Combat begins · Round 1"), so a player typing the words still gets a bubble; the old turn messages already in a campaign's history are not drawn; the Combat Log still records every turn - [x] Action API:
api.chat.brief(text)sets the summary an action's message shows in the feed, and can be rewritten as the action runs — Action API 0.7.0; stored as the doc'ssummary.headline(the server already accepted it), carried by the first post when written before it, patched after; does nothing without a live document (testAction); completions and the developer spec §3.10 - [x] Threads: an action's message is a lead bubble showing its brief, with its steps folded beneath it as a thread (each from whoever rolled it), opened by a toggle with the participants, a count and a live status —
ActionDocFrame+ChatThread.tsx/utils/actionThread.ts: an action that draws one thing is a single bubble (named before a bare roll), more is a thread; no brief falls back to the action's name and the derived summary; a thread starts folded and only the toggle moves it, with approvals and overdue asks still showing beneath it; an ask is the target token's bubble (its art and chat colour); a labelled roll says what it was for; the legacy marker-grouped frames (campaign history) are threads under their first message too
Top-level Nav (global left nav)
- [x] Move Help and Notifications to the global footer — Footer.tsx; Help goes through
useOpenHelplike the legal links (the help sidebar on /play, the route elsewhere); the bell is the sameNotificationsBell(badge, ring, arrival toast, hover list opening upward), now mounted in the footer on every route; the rail's Help sub-entries are the help page's own tabs; Support's address is on the help landing page with the policies- Desktop: to the right of AI Policy, show (separator) Help/About (separator) Notifications (notifications badge)
- Mobile: replace policy links with Help / Policies, then (separator) (bell icon with badge)
- Help/About and Help/Policies navigate to /help
- Notifications and bell navigate to /notifications
- Notifications badge shows current number of notifications
- Add prominent links to ToS, Privacy Policy, and AI Policy near the top of the /help wiki landing page
- Remove Help and Notifications (and any submenus of theirs) from the global top nav
- [x] Move Profile and Settings to the top right —
AccountCorner.tsx(ProfileButton: the avatar, opening Clerk's profile dialog); on /play asettingstab (the Settings page in the right sidebar) left of the Timeline and the avatar after it; off the map, gear + avatar fixed top right with 40px of top padding on the page; the desktop rail has no Profile row; the phone menu has Settings above Profile, no submenu; Log Out, Install and Manage account moved to Settings → Account so nothing the Profile entries held is lost. Uses our avatar, not Clerk'sUserButton(whose popover is not the profile dialog).- Desktop
- /play routes: in PlayTopBar, on the far right, show the Clerk profile icon, which opens the Clerk profile modal. Then, as we go left: the Timeline button, a gear icon which opens /settings in the right sidebar which operates just like any other tab, then the remaining chat tabs without modification
- other routes: in the top right of the main UI, same position they would appear in PlayTopBar, show the Clerk profile icon and the gear icon for settings. Ensure there's a bit of top padding if needed to make sure CTAs and the like don't overlap these buttons.
- remove Clerk profile icon from main left nav
- Mobile
- keep Profile in the menu for now
- add Settings above Profile
- remove Profile submenu...clicking Profile opens Clerk profile modal
- Desktop
Chat Log
- [x] Chat Log: LastPass thinks the main chat input text field is a password field. Please correct this notion. (If possible, indicate sitewide that there are zero password fields in the platform, as Clerk handles auth separately. The only thing close are the API Key inputs in settings). — the chat input is
name="chat-message", autofill off, with every manager's opt-out; sitewide, the themes give every TextInput / Textarea / NumberInput / Autocomplete / TagsInput / JsonInput the opt-outs (NO_PASSWORD_FIELDS_COMPONENTS— data attributes only, so spellcheck and the rest are unchanged); the API key fields stay masked with autofill off. No password manager reads a page-level switch, so per-field opt-outs from the theme are the sitewide statement - [x] There is an existing feature to wrap multiple chat bubbles in — done in this release as Threads: an action's bubbles are a thread under its brief, not a frame (a single bubble stays a single bubble), and the legacy grouped frames are threads too
Misc
- [x] Settings, narrow (a phone, or the /play sidebar): instead of the categories as a row of pills above the settings, two pages — page 1 a vertical list of the sections, each opening its page 2; page 2 that section's settings, headed by a back caret (top left) and the section name. Page 1 has no caret — still by the page's own width (the container query), so the /play sidebar gets it too;
narrowPageis read only by the narrow layout, wide is unchanged; a?section=link lands on that section's page - [x] Settings, follow-up: in the sidebar, every page-1 row as roomy as the active one was (touch-sized), no second "Settings" heading under the sidebar's own, and no darker background behind the list; full page, more padding on the category links and no lighter background behind them — the rows' padding was being zeroed by Mantine's UnstyledButton reset (it loads after the page's sheet; only the active row's extra attribute out-ranked it), so the selector is two classes now; 16/20px rows in the sidebar, 12/20px on the full page; the category column has no fill;
SettingsPage inSidebardrops the heading - [x] New Retro's body font is Noticia Text (chosen from Libre Caslon, Libre Caslon Condensed, Alegreya, Inter Tight, Instrument Serif, Noticia Text and Noto Serif with a temporary switcher, now removed); Crimson Text stays the flavour face —
--vtt-font-bodyin both New Retro token files, the Mantine theme'sBODY, and Noticia Text added to the Google Fonts import - [x] Mini-Me: the portrait's … (edit, zoom, download artwork) was not showing — built earlier in this release, but Mantine's ActionIcon
position: relativeout-ranked the menu'sposition: absolute, so it sat under the image and was clipped away - [x] Chat: typing
/wsuggests the usernames of the people connected to the campaign; ↑/↓ move the highlight, Enter or Tab picks one (Enter then does not send) — the campaign's owner and members (/api/campaigns/:id/membersnow returns usernames — public by design, still no email), less you and names already given; matched on username, then name; a further name keeps its@so the command parses; Escape puts the list away until the text changes - [x] New Retro dark: the primary is the orange #c3592b (the owner's change), with a hover to match — hover #d8703f (lifts, as the dark skin's hovers do); nav-active and the icon accent follow; the Mantine theme's primary is a new
emberfamily (crimson shade 7 on parchment, orange shade 5 on stone) sored/dangerstay crimson - [x] New Retro light: Mini-Me's name and buttons have the wrong colour for its background — the cause was the body's fixed dark scrim under text and buttons that follow the skin; fixed with the item below (the body takes the skin's sidebar colour), and the name takes
--vtt-text-primary - [x] Mini-Me (5e): the Spells menu shows the slots but none of the spells' action buttons — a 5e spell with no server-derived actions projects as a generic power roll without
spellmetadata, and Spells took only the marked ones; a system with a Spells menu and no Powers menu (5e) now puts every power action under Spells, grouped by the character's spells - [x] Mini-Me: clicking a condition / timed-effect badge turns its tooltip into a popover, with an × to close it (hovering out no longer does), Remove, and Edit for a timed effect (the edit timed-effect modal) —
MiniMeStatusChip: the hover tooltip stays until it is clicked; Remove takes a condition off the token and ENDS a timed effect (the Timeline's End, so its history stays — say if it should delete it instead); Edit opensTimedEffectModal(its pick-a-start-on-the-Timeline button does nothing from Mini-Me); effects follow the Timeline's rule (GM or whoever started it), conditions anyone not on a read-only impersonation - [x] Sidebars, follow-up: the translucent content did not match its (solid) tab button — a tab's top row (title, pop-out, close) is solid
--vtt-tab-active-bg, fading quickly below it to the 80% colour for the rest of the tab — TabChrome's header paints the solid colour (it can be taller than 40px, e.g. the chat's channel bar, so the fade hangs off the row rather than a fixed height), and a 12px gradient below it fades to transparent over the sidebar's 80% - [x] Sidebars: backdrops 20% transparent, with backdrop-filter: blur(3px) — the sidebar panel paints
--vtt-tab-active-bgat 80% with the blur, and the context panel inside it paints nothing (--vtt-context-panel-bg: transparent), or the two layers would add up to ~96%; an undocked context window keeps its solid colour - [x] Mini-Me: the body's backdrop is the sidebars' colour (--vtt-tab-active-bg), with the same transparency and blur —
color-mix(… 80%, transparent)+blur(3px), done with the item above - [x] Mini-Me: changes meant to make the old character sheet redundant
- [x] When 'full character sheet' is pressed, load into the left tab: the new character sheet (custom rendered by the system module, resembles a traditional paper sheet), not the old character sheet (system 8 doesn't have one yet, so this will only look right for 5e, but that's okay) —
MiniMeMenus.openSheetopens Me withsheetView: 'new'and sets the store's sheet view to new, so Me keeps showing it; a token with no character still opens the impersonated-token view - [x] In the portrait, add an action button in the upper right with a ... with a menu of options:
- Edit or replace this artwork
- View this artwork full size
- Download this artwork at full resolution
- (all of these exist already in the old character sheet -- just copy them here as well)
- Done:
MiniMeArtworkMenu— the sheet's portrait resolution moved toutils/sheetPortrait.tsso both share it; the editor isCharacterArtworkModal(writing the character, or the token when there is none; disabled on a read-only impersonation), the viewerArtworkLightbox, the downloaddownloadArtwork
- [x] Next to (left of) the close button in the main container, add a ... button with options:
- Focus camera on this token
- Share this character (works for impersonated token as well, but label should say 'share this token': still shows the permissions UI)
- Refresh from character (character only, not impersonated token)
- Token options: a new modal with the content of the 'Advanced Token Settings' in the old character sheet (layer, sizing mode, facing logic, etc)
- Done:
MiniMeTokenMenu—focusToken; the one PermissionsEditor on the character, or on the token (a grant entity) when it has none;useRefreshTokenFromCharacterfor a linked token;AdvancedTokenSettingsin a modal; refresh and options disabled on a read-only impersonation
- [x] When 'full character sheet' is pressed, load into the left tab: the new character sheet (custom rendered by the system module, resembles a traditional paper sheet), not the old character sheet (system 8 doesn't have one yet, so this will only look right for 5e, but that's okay) —