fix(turn): log addParticipants + updateParticipant (logging gaps)

addParticipants + updateParticipant returned log:null → invisible in
combat log. Contract test caught them. Now both return
log:{ message, undo:{ participants }}.

App.js handlers wired (handleAddAllCharacters, handleUpdateParticipant,
handleInlineInitiative) — call logAction on logged mutations.

Logging contract now complete: every mutating combat op produces a log
entry. No gaps. turn.logging.test.js proves it.

238 tests green.
This commit is contained in:
david raistrick
2026-07-04 17:25:22 -04:00
parent ab9f8fa2e7
commit 32b8dfd8a1
3 changed files with 70 additions and 15 deletions
+25 -3
View File
@@ -333,8 +333,16 @@ function addParticipant(encounter, participant) {
// ADD_PARTICIPANTS — bulk add (e.g. "add all campaign characters").
function addParticipants(encounter, newParticipants) {
const undo = { participants: encounter.participants || [] };
const updatedParticipants = [...(encounter.participants || []), ...newParticipants];
return { patch: { participants: updatedParticipants }, log: null };
const names = newParticipants.map(p => p.name).join(', ');
return {
patch: { participants: updatedParticipants },
log: {
message: `Added ${newParticipants.length} participant${newParticipants.length === 1 ? '' : 's'}: ${names}`,
undo,
},
};
}
// UPDATE_PARTICIPANT — edit modal save (name/initiative/hp/isNpc).
@@ -346,14 +354,28 @@ function updateParticipant(encounter, participantId, updatedData) {
// SLOT only if initiative changed. Other edits (name/hp/notes/isNpc) = no
// position change (design doc). Re-slots on unrelated edits shuffle the
// list mid-round → rotation dupes.
const undo = { participants: encounter.participants || [] };
if (!('initiative' in updatedData) || updatedData.initiative === target.initiative) {
const sameSlot = existing.map(p => p.id === participantId ? merged : p);
return { patch: { participants: sameSlot, ...syncTurnOrder(sameSlot) }, log: null };
const fields = Object.keys(updatedData).join(', ');
return {
patch: { participants: sameSlot, ...syncTurnOrder(sameSlot) },
log: {
message: `${target.name} updated (${fields})`,
undo,
},
};
}
const without = existing.filter(p => p.id !== participantId);
const idx = slotIndexForInit(without, merged.initiative);
without.splice(idx, 0, merged);
return { patch: { participants: without, ...syncTurnOrder(without) }, log: null };
return {
patch: { participants: without, ...syncTurnOrder(without) },
log: {
message: `${target.name} updated (initiative: ${merged.initiative})`,
undo,
},
};
}
// REMOVE_PARTICIPANT — verbatim from ParticipantManager.confirmDeleteParticipant