Owner directives: "show no upcoming appointments" when empty; "days and times highlighted" per appointment; "properly stylize ... to fit with each theme correctly"; "relatively small widget basically about the size of the weather widget". r3 judge blockers fixed: - Cache semantics extracted to status/js/calendar-cache.js (bundled ahead of the widget; module.exports for tests, window.CalendarCache for browser): entries keyed by uid+start (recurring instances kept distinct); a FETCHED WINDOW IS AUTHORITATIVE — on refetch, entries starting inside that window are replaced/purged (deletions + reschedules propagate), entries outside survive. 5 direct contract tests cover merge, duplicate suppression, recurring instances, stale removal on refetch, cross-window survival. - Chip filters by CURRENT INSTANT against event END (ongoing appointments qualify; started-long-ago with no end excluded). Covered by contract test. - Contract suite (4 cache tests) + ns suite (9) + widget-contract (4) = 18 calendar tests; smoke harness DC168-SMOKE-OK; eslint clean. r1/r2 items carried: universal light-bg contrast (objective WCAG >= 4.5:1 for all 10 builtin themes, worst 4.60 ocean), month-following fetch with refetch on navigation, weather-widget-sized chip, month grid with highlighted days.
35 lines
1.2 KiB
JavaScript
35 lines
1.2 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) {
|
|
const startIso = new Date(ev.start).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;
|
|
})();
|