Skip to content

Release v2.2.0

Choose a tag to compare

@github-actions github-actions released this 12 Aug 16:45
· 1 commit to main since this release
6417b23

[2.2.0] - 2026-08-11

A correctness release. Three features that appeared to work in the UI never actually did anything at the table — saving throws, trap damage under midi-qol, and the Reset tile — and this release fixes all three, along with the dnd5e 5.3.3 and Monk's Active Tiles 14.01 integration points that had silently drifted out of date.

⚠️ Upgrade note: rebuild traps and teleports that use saving throws

Saving throws were never saving throws (see below). The fix changes what new tiles emit, but tiles created before 2.2.0 already have the old request baked into their Monk's Active Tiles action data and will keep rolling ability checks. There is no migration — Foundry has no safe way to tell an intentional ability check apart from this bug. To get real saves, delete and recreate any trap or teleport that requests one. Everything else carries forward unchanged.

Added

  • Trap damage now goes through the damage system, with damage types. Tile traps previously emitted Monk's Active Tiles' hurt/heal action, which applies a bare number — and dnd5e treats a bare number as "ignore resistances", so no resistance, vulnerability or immunity ever applied and the damage had no type at all. Tile traps now emit a purpose-built Monk's Active Tiles action registered by this module, and the trap dialog gained a damage-type selector (sourced from the system's own damage types, with an SRD fallback on other systems).
  • midi-qol is respected if you use it. When midi-qol is active, trap damage is applied through it and honours your autoApplyDamage setting rather than overriding it — so if you have midi set to hold damage for manual application, traps do the same. Without midi, damage is applied through dnd5e directly, still typed. This mirrors what Enhanced Region Behaviors already did for region traps, so the two trap kinds finally behave alike. midi-qol remains entirely optional.
    • Note for existing worlds: only newly created traps use the new action. Traps already in your world keep working exactly as before.
    • Note for heal traps: healing now runs through the same path. Under midi-qol that means it renders on a midi card and follows the same auto-apply setting.

Fixed

  • Saving throws rolled ability checks, not saving throws. Monk's Token Bar namespaces roll requests as save:/ability:/skill:/misc: and resolves them against its per-system request options. Every option in this module labelled "… Save" emitted ability:<abbr>, which is an ability check — so save proficiency never applied and characters proficient in the save gained nothing from it. The teleport dialog was worse: it emitted a bare dex that matched no option in its own template, and Monk's Token Bar's findBestRequest scans groups in order and reaches ability before save, so it silently resolved to a Dexterity check. Traps, teleports and the dnd5e activity import path now all emit save:<abbr>.
  • Traps rolled damage but never applied it when midi-qol is installed. The GM saw the save resolve, saw a damage roll post to chat, saw no error — and the token kept every hit point. Monk's Active Tiles resolves a hurt/heal value through MonksActiveTiles.getValue, and a value wrapped in Foundry inline-roll brackets (-[[3d6]]) routes into inlineRolldoRollChatMessage#applyMode, which throws inside midi-qol's ChatMessageMidi subclass on v14. The throw lands after the roll is posted and before actor.applyDamage, which is why the failure was invisible. Traps now emit plain formulas (-3d6, -floor((3d6) / 2), 2d10) instead of bracketed ones.
  • The Reset tile never reset anything. Four independent defects, all confirmed against a real switch on Foundry 14.364 / Monk's Active Tiles 14.01:
    • Variables were written to the wrong tile. The set-variable action hardcoded the entity This Tile, which Monk's Active Tiles resolves to the tile running the action — so every value was stored on the Reset tile's own flags and the target switch was never touched. Reset actions now address each target tile explicitly, and variables are tracked per target rather than in one flat map that silently collided when two tiles used the same variable name.
    • A null reset value was emitted as the literal string "null", which Monk's Active Tiles evaluated and stored as a live value rather than clearing anything. It has a dedicated sentinel for this; null now clears the variable back to its never-set state.
    • The image index was off by one. Monk's Active Tiles treats a numeric image selection as 1-based, and the dialog captured a 0-based index, so a reset could only ever restore the first image and the second was unreachable.
    • Every per-tile control in the reset dialog was named after an empty string (fileindex_, var__switch_1, walldoor__0). With one selected tile this round-tripped by accident; with two, all of their controls collided. Door resets separately looked up a key the template never emitted, so every door reset submitted an undefined state, and re-rendering the dialog reverted each tile's captured image, visibility, rotation, position, variables and door states.
  • Check State branches could not target a second tile. A branch reading "Lever A is ON and Lever B is OFF" actually checked Lever A twice. Variable names are not unique across tiles — every switch defaults its variable to switch_1 — and both the condition dropdown and the tile creator matched on the variable name alone, so every condition resolved to whichever checked tile came first. Both now match on the tile itself.
  • Region traps did not apply damage. The "Apply Damage Automatically" option always rendered unchecked, so a region trap rolled its saving throw and then did nothing to the token unless you noticed the box. It now defaults on, matching Enhanced Region Behaviors' own default.
  • Combat trap trigger limits did nothing. The "trigger at most N times" counter was built from Handlebars helpers that are not registered in the context Monk's Active Tiles compiles action values in, and calling an unknown helper with arguments throws. The paired check also passed a comparison mode that is not one of the three legal values, so the filter always took the failure branch. Both are rewritten in terms Monk's Active Tiles actually evaluates.
  • Combat traps produced no attack roll. Monk's Active Tiles 14.01 gives dnd5e a dedicated attack branch in which only "roll the attack" resolves a callable; this module emitted the other mode, so execution fell through to bare targeting with fast-forward and damage both gated off and only the chat card rendered.
  • Traps imported from a DMG trap item always used Dexterity, and spell-derived traps always used DC 14. In dnd5e 5.3.3 the activity's save ability is a Set, and the code read it by index — which is always undefined on a Set — so every import fell back to Dexterity regardless of what the source item said. The DC formula field is only populated for a manually entered flat DC and is blank for spellcasting-derived DCs, so those fell back to the hardcoded 14. Both now read the fields dnd5e actually computes, and all dnd5e access sits behind a system guard so other systems degrade cleanly.
  • Traps set to affect "player tokens" hit the wrong targets, applying their status effects to every token standing on the tile instead. One copy of the target-type switch was missing the player-tokens case and fell through to "within tile".
  • Foundry v14 deprecated-global cleanup. loadTemplates, renderTemplate, FilePicker, SearchFilter, ContextMenu and TextEditor still resolve in 14.364, but only through backwards-compatibility getters marked "since 13, until 15" — they log a deprecation on every access and stop working in v15. The module is now off all of them. AudioHelper genuinely is gone, which is why teleport region sounds failed at trigger time; that call is now namespaced correctly.
  • Paired teleport cleanup never fired. Deleting one half of a linked pair was supposed to offer to delete the other, but the matching logic looked for hyphenated tag prefixes that the tag generator never produces, so the partner was silently orphaned. The deletion prompt also rendered the tile's name as an empty string, because these tiles carry their label in a module flag rather than in the document name.
  • Grid snapping used raw mode numbers against a bit field, so regions could start and end on half-square offsets and imported tiles landed half a square off.
  • Check State tiles honoured only the "equals" operator. Not-equals, greater/less-than and the AND/OR connector were silently dropped, leaving the branch to run unconditionally. All six operators now map explicitly, multi-condition branches are emitted as real control flow, and numeric operands compare numerically rather than lexicographically. Branch anchor tags are index-suffixed, so two similarly-named branches no longer collide.
  • Dialogs cleaned up twice or not at all. _onClose was wired both as the Cancel button action and as the ApplicationV2 close lifecycle callback, so it re-entered itself and never reached super. Split across all eight affected dialogs; cleanup now runs exactly once on every close path.
  • Dialog height guard was inert. The stylesheet scoped its flex layout and max-height: 90vh to the ApplicationV1 root class, which ApplicationV2 does not use — which is why the fixed dialog heights could overflow small viewports.
  • Combat traps discarded any custom tags entered in the dialog, revealed their token on the map even when configured hidden, and left an orphaned Actor, Item and Token behind if tile creation failed partway.
  • Tag uniqueness scanned only tiles and lights, so a new tag could collide with an existing region, sound or token.
  • Return teleports landed on the outbound pad's top-left corner rather than its centre.

Changed

  • Dependency floors now match what the code requires: Monk's Active Tiles 14.01, Tagger 1.6.0, Monk's Token Bar 14.01. Enhanced Region Behaviors is declared as a suggestion rather than a requirement — trap and elevation regions need it and say so with a clear error, but requiring it would make Foundry refuse to enable the module at all for GMs who only use tiles.
  • User-facing strings are localizable. 108 notification literals, every settings name and hint, the trap dialog's validation messages, the teleport deletion prompts and the variables help text now go through lang/en.json. The rendered English is unchanged. Damage type names now come from the dnd5e system's own localized list, with an SRD fallback for non-dnd5e worlds, replacing a hardcoded parallel list that would silently drift.
  • CI actually checks the code now. The job named "Lint & Type Check" ran neither: it ran npm run build, which is an esbuild transpile with no type checking. tsc had never been run against this project. The quality job now runs type check → lint → format check → build, lint fails on any warning, and a pre-merge job requires a real semver version bump with package.json and module.json in agreement. Jest now enforces a coverage floor.
  • Releases register the correct Foundry compatibility. Both release workflows hardcoded v13 in the FoundryVTT package API payload while module.json declared v14, so every release since v2.1.0 was published to the package listing with the wrong core version range. Compatibility is now read from module.json. A LICENSE file was also being zipped into every release archive without existing — zip warns and exits 0 — so releases shipped without one; the file now exists and the archive step fails loudly on a missing path.
  • ~127 KB of dead code removed: an unused base dialog class, seven orphaned templates, nine unreferenced exports, their tests, the CSS that styled the deleted templates, and 59 orphaned localization keys.
  • Test suite grown to 993 tests across 28 suites, including new coverage for the reset dialog template, the dnd5e activity parser, the localization contract, template accessibility and the stylesheet's scoping rules.

Auto-released from PR #52: Foundry v14 / dnd5e 5.3 / Monk's Active Tiles 14 maintenance pass