fix(BUG-4): updateDoc patch on activeDisplay (not setDoc replace)

All 5 storage.setDoc(activeDisplay, {...}, {merge:true}) →
storage.updateDoc(activeDisplay, {...}).

setDoc merge:true worked in prod (firebase honors merge) but ws adapter
+ mock ignore opts arg entirely → clobbers doc. updateDoc uses PATCH
across all adapters (firebase real updateDoc, ws PATCH endpoint, mock
merge). Consistent, no clobber.

Sites fixed:
- hidePlayerHp toggle
- startEncounter (set active ids)
- endEncounter (null active ids)
- deactivate active display
- activate new display

TDD: HideHpToggle.test RED first (assert updateDoc patch, impl still
setDoc → 0 calls found). GREEN after switch.

Char tests updated: Encounter.characterization (2) + Combat.characterization
(2) assert updateDoc on activeDisplay, not setDoc.

BUG-4: prod was already fixed (merge:true), test was RED due to mock
ignoring opts. Now all 3 adapters consistent via updateDoc.
This commit is contained in:
david raistrick
2026-07-01 23:32:23 -04:00
parent d00cc104c9
commit c0998da0a7
5 changed files with 37 additions and 30 deletions
+9 -9
View File
@@ -1662,7 +1662,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
const handleToggleHidePlayerHp = async () => {
if (!db) return;
try {
await storage.setDoc(getPath.activeDisplay(), { hidePlayerHp: !hidePlayerHp }, { merge: true });
await storage.updateDoc(getPath.activeDisplay(), { hidePlayerHp: !hidePlayerHp });
} catch (err) {
console.error("Error toggling hidePlayerHp:", err);
}
@@ -1695,10 +1695,10 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
turnOrderIds: sortedParticipants.map(p => p.id)
});
await storage.setDoc(getPath.activeDisplay(), {
await storage.updateDoc(getPath.activeDisplay(), {
activeCampaignId: campaignId,
activeEncounterId: encounter.id
}, { merge: true });
});
logAction(`Combat started: "${encounter.name}" — ${sortedParticipants[0].name}'s turn (Round 1)`, { encounterName: encounter.name }, {
encounterPath,
@@ -1826,10 +1826,10 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
turnOrderIds: []
});
await storage.setDoc(getPath.activeDisplay(), {
await storage.updateDoc(getPath.activeDisplay(), {
activeCampaignId: null,
activeEncounterId: null
}, { merge: true });
});
logAction(`Combat ended: "${encounter.name}"`, { encounterName: encounter.name }, {
encounterPath,
@@ -2044,15 +2044,15 @@ function EncounterManager({ campaignId, initialActiveEncounterId, campaignCharac
const currentActiveEncounter = activeDisplayInfo?.activeEncounterId;
if (currentActiveCampaign === campaignId && currentActiveEncounter === encounterId) {
await storage.setDoc(getPath.activeDisplay(), {
await storage.updateDoc(getPath.activeDisplay(), {
activeCampaignId: null,
activeEncounterId: null,
}, { merge: true });
});
} else {
await storage.setDoc(getPath.activeDisplay(), {
await storage.updateDoc(getPath.activeDisplay(), {
activeCampaignId: campaignId,
activeEncounterId: encounterId,
}, { merge: true });
});
}
} catch (err) {
console.error("Error toggling Player Display:", err);