Skip to content

v0.2.0

Date unknown, but sometime in early-mid February 2026

Edit Campaign Modal

  • [x] Change tabs to be a left nav (refer to the 2nd-level nav in this Mantine example: https://ui.mantine.dev/component/double-navbar/)

  • [x] Add a new type of Campaign Trait: "Functions". These are JS functions which are available when writing Actions.

    • Function types: global and token. Global functions are available in all actions, and token functions are a method of a token instance. The existing roll() and sendMessage() are global functions and should be shown here (but not editable), and the existing "token" variable is how you access token functions (e.g. token.myFunction()).
    • Code: a textarea with the entire function (e.g. function myFunction(arg1) { return 'hello world'; }, or the arrow syntax equivalent). For new functions, we can generate some boilerplate code to get them started. This must be valid JavaScript. The body can only refer to arguments and global functions/variables.
    • Description: a user-friendly description of what the function does, with examples of how to use it. This is just text, but it should be encouraged to be as clear and helpful as possible, since this is what users will see when they hover over the function in the Action editor (see Edit Action Modal TODOs below).
    • Example functions (these would be inherited from System 8, but like all inherited traits, they can be extended at the Campaign level):
      • lightDamage(whichAbilityScore): token function.
        •   const rank = Math.ceil(token.level / 4);
            const damageRoll = `${rank}d4`;
            const abilityBonus = token[whichAbilityScore] || 0;
            return `${damageRoll}${abilityBonus >= 0 ? '+' : ''}${abilityBonus}`;
          
        }```
      • Description: This function calculates the damage for a light attack, for a given token. You supply the ability score. Example, a rank 2 hero with a 20 Dex inflicts 2d4+2 with a dex-based light damage attack. Usage: token.lightDamage('dex')
  • [x] The right pane of Edit Campaign modal is not scrollable, but the content often overflows the viewport height. Make it scrollable.

  • [x] When a campaign is created with a system, it should inherit the system's traits. For instance, D&D 5e shoudl have property types like "HP" and "Strength". However, currently, no inheritance takes place. This should also happen when the system is changed; in that case, the user should be asked if they want to merge the new system's properties in and/or delete the original system's properties (both are checkboxes, not radio), and should be asked if any incoming properties should overwrite campaign-level properites (default: no).

  • [x] Sometimes, on smaller screens, this modal intersects the left/right sidebar, which overlap it. Increase the z-index of this modal, and any other modals in the Map, to make sure they're above the sidebars.

  • [x] Bug: When adding a new Campaign Property Definition, the Type dropdown should show the default types (string, number, boolean) along with the campaign's custom property types. Instead, it looks like a disabled dropdow, behaves like a text field, with "Text" as the default value. I can set any value, but I can't choose any of the actual types.

Edit Action Modal

  • [x] Make JavaScript Code textarea taller by default, and resizable
  • [x] Without changing sendMessage(), change actions so that the action is expected to return a string, and that string is emitted to chat in addition to any sendMessage() calls prior to the return. If the return type is not a string, show a user-friendly error and a console error showing the output that wasn't a string.
    • [x] When a dice expression is entered into the chat input, it is translated. For example, "1d20+5" triggers a d20 roll, and returns the die roll inside a die-specific svg background, with the total result (e.g. 17) shown next to it and some options in a ... menu including add Advantage, Re-Roll, etc. Strings returned by an action (or send via sendMessage) that include dice expressions should be parsed in the same way.
  • [x] Add Mini-Intellisense in the JS Code textarea. It recognizes contextual variables and functions such as token, roll(), and sendMessage(). Use IntelliSense standard UI, that is:
    • Show suggestions in a dropdown as the user types, with the ability to navigate with arrow keys and tab to autocomplete. Indicate which items are variables or functions. In a second panel to the right, show a description and example of the variable or function's use.
    • When hovering over a recognized variable or function, show a tooltip with the same description and example as the right panel of the dropdown.
    • Detect broken JS syntax and show user-friendly error messages, with tips on how to fix it. (Seems like there may be some off-the-shelf React components for this)
    • Campaign-defined functions and variables should also be included in the suggestions, with appropriate descriptions and examples defined by the GM when they create the function/variable.
  • [x] Add small buttons for token variable, roll() function, and sendMessage(), which insert a usable snippet of code into the JS Code textarea at the current cursor location. For example, it might insert "token." which spawns the Mini-IntelliSense dropdown with token properties and actions; or it might insert roll('1d20') or sendMessage('hello').
    • Campaign-defined functions should appear here as well.
  • [x] In the Edit Action modal, add a Test button. If the function requires no input, and no token, it executes as-is and shows the result (but doesn't put it in chat). If it needs a token and/or inputs, expand a section in the form when Test is clicked, which allows selecting an existing token from the map (if none exist, prompt user to create one in order to test), and allows inputing values for each argument. Remember these test values between modal openings for convenience. Once these inputs have values, the Test button executes with those inputs, showing the output in a modal, but not sending it to chat.
  • [x] Add a global function prompt(msg, ?jsType), which shows a prompt with the given message and an input field. When the user submits, it returns the value. If jsType is provided, it should validate that the input can be parsed into that type (e.g. number, boolean, etc) and show an error if not. This would be useful for actions that require user input at runtime, such as Action "Take Damage", implemented as token.hp = token.hp - prompt('How much damage?', Number).

Map

Token Context

  • [x] The ... button next to each action is clickable, but it doesn't do anything. It's supposed to open a context menu with options like Edit Action and Delete Action.
  • [x] Clicking the x button for Selected Token should close the Selected Token Context after successfuly deselecting the token itself (a state change). If this fails for some reason, show the user an error. Currently it just fails. (Note: the Impersonate context was also open for the same token. But this behavior still happens when that context is closed, so it's probably unrelated)
  • [x] It appears that token properties with no defined value do not appear at all in the Token Context. Every property defined in the Campaign should appear here, even if it has no value. Otherwise, the only way to even set a value is an Action.
  • [x] Upon initial load of a map, and selection of a token with known actions, no actions were shown in the context. After creating a new action, all existing actions and the new action appeared.

Token Highlighting

  • [x] On dev machine (Mac OS with 'tap to click' enabled on trackpad), tap-to-click starts a drag-to-move, but doesn't add the token highlighting ring, nor does it open the Selected Token Context. Full-click works as expected. Suspect that tap handlers intended for mobile are being listened to along with mouse click handlers. Can we detect the presence or absence of a mouse, and only listen to the appropriate handlers? Or is there some other way to fix this? Or is the root cause something else?

System UI

  • [x] Create system fixtures and seed script. Define a SystemFixture type in packages/shared/src/types/ that describes a complete system snapshot: core metadata plus all trait arrays (propertyDefinitions, propTypes, conditions, bars, lightSourceTypes, visionTypes, functions). Create fixture files for System 8, D&D 5e 2014, and 5e 2024 using this type, consolidating the existing hardcoded defaults from packages/shared/src/types/system.ts. Write a seed script that inserts these fixtures into the DB as system-level rows (systemId set, campaignId null) on fresh DB init. These same fixtures power the "Restore Default" feature (see below) and serve as templates for user-created systems.
  • [x] Add system-level CRUD API routes. Create /api/systems/:id/property-definitions, /api/systems/:id/prop-types, /api/systems/:id/conditions, /api/systems/:id/bars, /api/systems/:id/light-source-types, /api/systems/:id/vision-types, and /api/systems/:id/functions endpoints with full CRUD. Mirror the existing campaign-scoped route patterns but scoped to systemId instead of campaignId. Also add POST/PUT/DELETE to /api/systems for the systems themselves.
  • [x] Deprecate /system route. Change the SideNav "System" entry from /system to /systems. Update useRouteSync.ts to map systems context type to /systems and /systems/:id. Remove or repurpose the old SystemMain iframe component.
  • [x] Create /systems list page. Similar to /campaigns — fetch systems from API, display as cards or list items with name, description, foundation, and a count of associated campaigns. Include a "Create System" button.
  • [x] When looking at /systems, user can Create a new system, Delete a system (with confirmation), Copy a system (creates a new system with the same traits, but editable), and Rename a system. Delete, Rename, and Copy are available on /systems/:id as well.
  • [x] Create /systems/:id detail/edit page. Full-page layout (not a modal) similar to CampaignEditModal, with a left nav and tabbed sections for each trait type: Basic Info, Property Types, Properties, Conditions, Bars, Light Sources, Vision Types, Functions. Reuse or extract the existing CampaignEditModal tab components into shared components that work for both system and campaign editing contexts.
  • [x] Extract shared trait editor components from CampaignEditModal. The CampaignEditModal already has complete UIs for editing all 7 trait types + a Basic Info tab. Extract these into reusable components (e.g., PropertyDefinitionsEditor, ConditionsEditor, BarsEditor, etc.) that accept a scope parameter ({ type: 'system', systemId } or { type: 'campaign', campaignId }) and work identically in both contexts.
  • [x] In the System UI, create a 'break glass' feature for systems bundled with the app. If users wishes to edit, recommend copying the system instead. If they create a copy, they can proceed with editing that copy as normal. If they really want to, they can modify the bundled system, removing any normal restrictions. However, once that option has been exercised, there is a "Restore Default" button visible on /systems/:id-of-bundled-system which allows the user to reset that system's traits back to the original defaults from the fixture. This allows for some flexibility while still encouraging best practices and providing a safety net.

Character Sheet

  • All Modes
    • [x] Powers and Feats: when clicked, they open a modal with a full description. But this is just the short description from powers.json or feats.json. Instead, they should load the full detail page (url given in feats/powers.json, although this url is .html which needs to be swapped to .json) and show the bodyHtml inside the popup. (This description is for system 8, but the 5e API should have similar features)
  • Build Mode
    • [x] Remove edit/editing button. In Build Mode, the user can always edit, and in Play mode, they can't.
    • [x] In the expanded ability score editor, proficiency bonus should be labeled 'proficiency', not 'bonus'. The internal name for proficiency bonus should also be 'proficiency', not 'bonus'.
    • Power Picker
      • [x] Separate category filters like Assault, Control, etc. Each power can have one or more of these categories; the filter control should toggle individual values, and not include entries like 'Assault, Control, and Utility'.

Bug Fixes & Stability

  • Fix ability score racial/proficiency entanglement in System 8 (proficiency stepper must not target racial components)
  • Add racial and ancestry to System 8 recognized component types
  • Audit and fix any other component-type conflation issues across systems
  • [ ] Below a certain screen width, but above the "mobile" width, the Chat disappears and there is no way to access it. Either ensure Chat is in the left sidebar in this mode, or add a button to open it as a modal, or something else to ensure it's always accessible.