Audit 2026-10-27 fixes (46-finding sweep, AUDIT-2026-10-27.md): - P0-2: all 6 calendar API calls via secureFetch — raw fetch sent no X-CSRF-Token so every mutation (save/toggle/delete/test) was rejected 403 [DC-100]; GETs gain the standard 15s timeout - P0-6: refresh() catch called nonexistent errorHandler.log() — the error path itself threw; now logError(context, error) - P0-7: modal sizing off inline min-width:560px onto .cal-modal-content with 768px override (inline style beat the mobile media query) - refresh coalescing via _pendingForce (forced refreshes were dropped mid-flight); single fetch sequence on modal open; toggle/delete get resp.ok + try/catch + toast + disabled-during-flight - modal on class-based .show: click-outside, Escape, focus move/return on all three dismissal paths (judge polish applied) - chip honors Enter/Space (role=button); poll skips hidden tabs - --cal-chip-when/--cal-when/--cal-dot re-scoped to :root (were scoped to elements that are never ancestors of .cal-dot — DC-168 WCAG dot colors never rendered) - calendar-cache eventKey no longer throws RangeError on invalid start Bundles rebuilt (sw dashcaddy-shell-7fc13e7762). status suite 68/68. Judge B (polish items applied pre-commit): urn:ump:dbxmyhvr7cpkeu3mbfdevrco7utsok743udjsj7atih6qk4frdiq
40 lines
1.5 KiB
JavaScript
40 lines
1.5 KiB
JavaScript
// DC-168: calendar event-cache semantics (shared by widget + tests).
|
|
// Keyed by uid+start (recurring instances differ per occurrence); a fetched
|
|
// window is authoritative for entries starting inside it (deletions and
|
|
// reschedules propagate), entries outside it survive.
|
|
'use strict';
|
|
(function () {
|
|
function eventKey(ev) {
|
|
// Audit 2026-10-27: a null/invalid start threw RangeError out of merge()
|
|
// and flipped the whole widget offline — degrade to a string key instead.
|
|
const d = new Date(ev.start);
|
|
const startIso = isNaN(d.getTime())
|
|
? `invalid:${ev.start == null ? 'null' : String(ev.start)}`
|
|
: d.toISOString();
|
|
return ev.uid ? `${ev.uid}@${startIso}` : `${startIso}|${ev.title || ''}`;
|
|
}
|
|
|
|
function createCache() {
|
|
const map = new Map();
|
|
return {
|
|
size: () => map.size,
|
|
merge(body, range) {
|
|
if (range) {
|
|
for (const [k, ev] of map) {
|
|
const t = new Date(ev.start).getTime();
|
|
if (t >= range.start.getTime() && t <= range.end.getTime()) map.delete(k);
|
|
}
|
|
}
|
|
for (const ev of ((body && Array.isArray(body.events)) ? body.events : [])) {
|
|
map.set(eventKey(ev), ev);
|
|
}
|
|
return { events: [...map.values()].sort((a, b) => new Date(a.start) - new Date(b.start)) };
|
|
},
|
|
};
|
|
}
|
|
|
|
const api = { eventKey, createCache };
|
|
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
|
if (typeof window !== 'undefined') window.CalendarCache = api;
|
|
})();
|