v6.12.0
[6.12.0] — 2026-08-18 — Grouped placeable-note tray, player summon & initiative permissions, the summon lifecycle, and a Foundry 14 baseline
Added
- Placeable Notes now have a grouped tray view. Notes are collected into fixed Tokens, Actors, Tiles, Drawings, Walls, Lights, Sounds, and Regions groups with viewer-safe counts and natural sorting. Token and Actor notes remain separate exact sources, and group/row expansion state lasts for the tray session. Drawing/Region labels use native text or names before coordinate fallbacks, and lifetime-owned transient/rebuild sources remain excluded.
- A summon spell can now ask which creature to conjure. Listing several creatures on a summon spell has always summoned all of them, which is right for a spell that calls a pack — but not for one written as a choice. Shadowdark's Undeath reads "it rises as a zombie or skeleton", and "you can only create one undead creature with this spell at a time"; configured with both, it summoned both. A new option on the summoning panel, When several are listed → Ask which one, prompts the caster and summons only what they pick. The default is unchanged, so spells that mean to summon a group still do. Cancelling the prompt summons nothing, rather than falling back to summoning everything.
- Summoned creatures join the encounter on their summoner's initiative. "The creature acts on your turn" is the usual shape for a summon, but the tokens simply appeared on the canvas and the GM had to add them to the tracker by hand, guessing where in the round they belonged. They are now added as combatants sharing the caster's initiative value — not a fresh roll, which would scatter them across the round. Re-casting will not produce a duplicate that acts twice, and a caster who is not in the encounter adds nothing, since there is no initiative to share. Controlled by Join combat on caster's initiative, on by default; uncheck it for summons that should roll their own.
- Summon durations now run outside combat. A duration written in rounds only means something while rounds are being counted, so a creature summoned outside an encounter was never tracked and stayed forever — and one summoned during a fight that then ended kept waiting on a round counter that would never advance again. Durations now fall back to world time when no encounter is running, at your world's own seconds-per-round, and ending a combat re-bases whatever rounds were still owed onto world time. A spell with two rounds left keeps two rounds' worth of time instead of becoming permanent.
Changed
- Foundry VTT 14 is now the minimum supported version.
- Add Pin and Pin List moved into the Pins tab. The two redundant launchers on the tray handle are gone; Add Pin now lives in the Pins toolbar and Pins remains reachable through its own tab. Persisted feature IDs are unchanged, so nothing you had configured is affected.
- The tray's Scenes tab is now labelled ToM. A label-only rename — every persisted key, view mode, action, CSS class, and setting name is untouched.
Fixed
- Journal Pins: access, opening, and privacy now agree with each other. Players could not open journals they owned from a canvas pin, pins converted from notes were exposed to players by default, the sidebar's double-click opened the wrong entry, and GM-only status overlays (the eye, slash, red tint, and forced dashed ring) leaked pin state into the player view. Opening now routes through one centralized access policy that resolves the exact permitted entry and page; converted pins default to GM-only; the status overlays are removed while configured rings and their enforcement stay; and pin lists sort naturally, so
Room 2precedesRoom 10in both list paths. - The PinStyleEditor's controls now match what they render. An audit of all 69 unique option names across the editor's 70 rendered controls found several places where the live preview and the actual pin renderer disagreed; those divergences are fixed so the preview is trustworthy. The editor can also be resized vertically again — local
80vh/70vhcaps were overriding the window's own sizing, and oversized requests remain clamped to the viewport with content scrolling. - Dungeon tiles no longer persist from a previous generation. The painter's tile cache survived across generations, so a regenerated dungeon could reuse tiles from the map before it.
- Curved Walls now shows its selected state. The option rendered identically whether or not it was active.
- A weapon bonus "vs Undead" never applied. Shadowdark 4.x stores
PlayerSD.ancestryas a document UUID, but the requirement check read.nameoff it, so every ancestry comparison ran against an empty string and silently failed — and the field's placeholder ("e.g., Undead, Humanoid") steered people straight into the dead path. Ancestry now resolves from all three stored shapes: a 4.x UUID, a bare name, or a pre-4.x object. NPCs still resolve to empty, which is correct — they have no ancestry — and the placeholder now reads "e.g., Dwarf, Elf, Human" with the reference panel noting the requirement applies to players only. The separate creature-type requirement is relabelled "Target Creature Type" and takes a dropdown of the configured types instead of free text that had to match exactly; its persisted key is unchanged. - Versatile handedness and equipped indicators were invisible on the enhanced tabs. The inventory and spells tabs painted every icon in the row white with
!important, which outranked the inline colour the system uses to signal state — so the handedness fist and the equipped shield looked identical in both states, even though switching to two-handed did correctly change the damage die. Icons that declare no colour still inherit white; tinted ones keep their tint, re-pointed to the accent colour already used elsewhere on these tabs so they read against the dark background. - Placeable-note actions and lifecycle updates are now exact and resilient. Edit, rename, sharing, delete, and pan actions target the exact source document, enforce GM and active-scene checks, and refresh safely across document lifecycle changes and overlapping asynchronous renders. A failed note enrichment no longer removes sibling rows or exposes rejection details. Drawing and Region lifecycle updates now apply label-aware differential refreshes while preserving live-center pan and explicit player sharing.
- The system's apply-damage buttons stayed live after SDX had already applied the damage. Shadowdark's roll card carries its own pair of apply-damage buttons, and SDX injects its damage card into that same chat message — so once SDX applied the damage, whether automatically or from its own APPLY DAMAGE button, those buttons still read as unused and clicking one applied the same damage a second time. The system does not fade a button on click:
ChatMessageSDsetsflags.shadowdark.damageAppliedand re-adds the faded state from that flag on every later render, so the only way to reproduce the fade is to write the flag the system reads. SDX now sets it alongside its own applied flag, which also hands the double-apply guard back to the system — a stray click raises its reapply confirmation instead of silently landing damage twice. Verified live on Foundry 14.365 / Shadowdark 4.0.6, for both GM- and player-authored attacks. - Players saw no weapon art, and lost the animation controls with it. The weapon-animation picker discovered its art by walking the assets folder with
FilePicker.browse, which needs theFILES_BROWSEpermission — not granted to the Player role by default. Every browse was rejected, the scan came back empty, and the dialog reported "No weapon images found in assets/Weapons folder": a statement about the folder for what was really a permission failure. The folder was never the problem. Worse, the offset, scale, rotation and animation-effect controls sit inside the same conditional, so a player lost the entire General tab, not just the image list. The bundled art now ships as a generated manifest and is read as a static file, which needs no permission — the same 786 images, and the full control panel, are available to every role. Art from Forgotten Adventures Nexus is another module's and still requires browsing, so it remains GM-only in practice. - Chat messages from a deleted player crashed the damage card on every render.
ChatMessage#authorresolves the stored author id against the user list and comes back null once that user no longer exists — routine in a long-running game, where removing a player leaves their messages behind. The damage-card pipeline read.idoff it directly and threw, repeatedly, for every affected message. Five separate authorship checks had the same unguarded read; the first one crashed early enough to hide the other four. The error also never reached the module's own log, because the hook wrapped an async call in a synchronoustry/catch, which cannot observe a rejected promise — the identical hazard was already documented and fixed for the neighbouring weapon-bonus call. An author that cannot be resolved is simply not the current user, so those checks now answer "no" instead of throwing, and the rejection is handled on the promise. - Casting a summon spell did nothing at all. No creature, no card, no error — the spell simply had no effect, however carefully it was configured. The summoning panel stores its creature list as JSON text, but every consumer checked that list with a test that only recognised an array or an object, so the text form read as "no creatures configured" and the whole summoning step was skipped. The code behind that check already understood the text form; the check in front of it just never let anything reach it. The list is now read in one place that accepts every shape it has been stored in, and the same broken check appeared in the NPC-feature summoning paths, which were affected identically.
- A summon spell's duration disagreed with itself. Two mechanisms independently decided when a summon ends, and derived different answers from the same spell: for an encounter created but not yet begun they were a full round apart, and outside combat one of them pinned the expiry to a round number unrelated to when the spell was cast, so the creature vanished partway through whatever fight happened to start next. Both now share one rule for when a duration ends and one way of counting what is left, and the focus tracker shows remaining time in whichever unit applies rather than always in rounds.
- Players could not summon creatures or roll their own initiative. Both require a document write a player lacks permission for: portal-lib updates the summoned world actor while creating its token, and rolling initiative updates the combatant inside the Combat document, which players don't own. Summoning now grants the caster temporary ownership of the summoned actor before the spawn and revokes it again if the placement is cancelled or the spawn fails, and the initiative die on the enhanced header routes through the GM. Verified live on Foundry 14.366 / Shadowdark 4.0.6.
- A cancelled summon left the player permanently owning creatures they never conjured. The ownership grant made to let portal-lib create the token was never undone when the caster pressed Escape, so a shared world creature kept a player's OWNER entry — visible in their actor list and enough to control every other player's live tokens of that creature. Ownership granted for a spawn is now revoked when the placement is cancelled or the spawn throws, and it targets the player actually casting rather than whoever the character is assigned to.
Security
-
Document-supplied text is now escaped everywhere it reaches HTML. A name a player controls — an item, a spell, an effect, a token, an actor — was interpolated raw into markup on chat cards, item sheets, character sheets, Theater of the Mind, canvas tools, and app windows. A spell renamed to
x" onerror="alert(1)closed thesrcattribute and left a live event handler that ran in the GM's browser the moment the card rendered. The sweep covers duration-spell and aura chat cards, damage cards and their roll-breakdown tooltips, the spell/potion/scroll/wand sheet enhancements, the summon-creature and item-give profile generators, hit-bonus and Mysterious Casting tooltips, identify/holy/cleansing/shapechanger displays, the character sheet's name field and header backgrounds, ToM condition names and overlay paths, the drawing toolbar, pin-style editor, dungeon and Maphub images, and the hex, aura, rename, and carousing dialogs.Two findings from review are worth naming, because both were mistakes about where trust lives rather than missed sites. Weapon bonus formulas and labels had been excused as "GM-authored config", but they are stored as item flags — a player who owns the weapon controls them, and the payload executes when the GM opens that sheet. That whole category was withdrawn rather than patched: who types a value is not a trust boundary, where it is stored is. Separately, a
data-actor-nameattribute on a chat card carried a raw actor name past a scanner that only knew aboutdata-tooltip.Several sites that hand-rolled
.replace(/"/g, """)now use Foundry's own escaping — the hand-rolled form stays inside the attribute but is silently wrong about&. Round-trips are unaffected in every case: the HTML parser decodes entities before any read ofinput.valueordataset, so stored values and JSON blobs are unchanged. Values that are genuinely not document-backed — document ids, slugs, numerics, and module-owned label constants — are deliberately left alone and recorded as such.
Internal
- The flag-key snapshot records a
shadowdark-scope write for the first time: SDX writesdamageAppliedinto the system's own flag namespace so the system's chat card renders as applied. The write goes through twosetFlagcalls rather than one flattenedupdate(), because the flag-scan gate matches onsetFlag/getFlagcall sites and a flattened update would be invisible to it — a second re-render in exchange for keeping the persistence channel auditable.