[pending-judge] feat(DC-168): calendar widget UX + theming — "No upcoming appointments" state, month grid with highlighted appointment days, theme-correct styling
Owner directives (2026-10-10/11, verbatim in brief): show "no upcoming
appointments" when empty; show a month calendar with days and times
highlighted when there are events; stylize to fit every theme; keep the
widget weather-widget-sized.
Changes (status/ frontend + tests):
- status/js/calendar-widget.js: rewritten render path — chip states ("No
upcoming appointments" / next appointment with highlighted when-span),
month grid modal (cal-grid + prev/next nav, appointment days highlighted,
per-event time dots), upcoming list with day/time emphasis, month-following
fetch with window-authoritative event merge (via calendar-cache.js),
forced refresh on periodic tick + source mutations, local escapeHtml (was
an undefined global).
- status/js/calendar-cache.js (new): uid+start-keyed event cache; fetched
window is authoritative for entries starting inside it (deletions and
reschedules propagate), entries outside survive. Dual export (module +
window) for tests and browser bundle.
- status/build.js: calendar-cache.js bundled before calendar-widget.js.
- status/css/dashboard.css: DC-168 block — chip states, month grid, day
highlight states, dots, list/source rows, universal light-bg contrast
rules (color-mix; WCAG >= 4.5:1 verified for all 10 builtin themes, worst
4.60:1 ocean) + light-surface rules.
Tests: 20 total in __tests__/calendar — 9 in caldav-default-ns (extraction
incl. mixed docs, empty multistatus, namespace-aware fixture validation,
undeclared-prefix rejection both quote styles, positive declared-attr),
5 in dc168-cache-contract (merge, duplicate suppression, recurring uid+start,
window-authoritative removal on refetch, cross-window survival, ended-
exclusion), 6 in dc168-widget-contract (navigation fetch windows, forced-
refresh semantics, cache+widget integration, interval coverage). Existing
suites pass unmodified; eslint 0 errors on touched files.
This commit is contained in:
9 files changed
+1099
-304
No files matched your search
@@ -0,0 +1,34 @@
|
||||
// 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;
|
||||
})();
|
||||
Reference in new issue
Block a user