Fuenf Farbspalten trugen einen fest eingebauten Vorgabewert — primary_color #4285f4, accent_color #ea4335, today_color #4285f4, month_divider_color und month_label_color #7090c0. Damit hatte *jeder* Nutzer immer eine eigene Farbe, und der Fallback settings.X || baseColor(X) kam nie zum Zug. Das Admin-Versprechen "Gilt fuer alle Nutzer ohne eigene Farbe" war fuer diese fuenf unerfuellbar, und die Werte widersprachen DEFAULT_COLORS im Frontend, das laut Kommentar die einzige Quelle sein soll. Das betraf nicht nur Altbestand: UserSettings wird ohne Farbwerte angelegt, also bekam auch jeder heute neu angelegte Nutzer das Blau. - models.py: die fuenf Spalten sind nullable, NULL heisst "erbt". - settings_router: NULL wird ausgeliefert statt durch einen Standard ersetzt, und ein explizites null wird gespeichert (NULLABLE_OVERRIDES). - main.py: einmalige Migration setzt genau die alten Vorgabewerte auf NULL. Bewusst gewaehlte Farben bleiben unangetastet. - calendar.js: eine erbende Zeile merkt sich das und sendet beim Speichern null. "Zurueck" heisst wieder erben, statt den aktuellen Standard in die Nutzerzeile zu kopieren — sonst erreichen spaetere Aenderungen des Standards den Nutzer nie mehr. Verifiziert: frische Installation liefert --primary #58B900 statt #4285f4; zurueckgeschriebenes Alt-Blau wird beim Neustart geleert; eine eigene Farbe (#FF00AA) ueberlebt die Migration. Suite 129 gruen, davon 5 neue. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
83 lines
2.9 KiB
JavaScript
83 lines
2.9 KiB
JavaScript
// Calendarr Service Worker — minimal-cache strategy
|
|
//
|
|
// Strategy: network-first for everything. The cache is only used as a
|
|
// last-resort fallback when offline (so the app shell still opens). This
|
|
// means every online request hits the network and respects the
|
|
// server's Cache-Control headers (≤ 2h for static assets, no-cache for
|
|
// the entry HTML / version files). New releases take effect on the next
|
|
// reload, no manual SW unregister required.
|
|
|
|
const CACHE_VERSION = 'calendarr-v43';
|
|
const OFFLINE_SHELL = ['/', '/index.html'];
|
|
|
|
self.addEventListener('install', event => {
|
|
event.waitUntil(
|
|
caches.open(CACHE_VERSION).then(cache =>
|
|
Promise.all(OFFLINE_SHELL.map(url =>
|
|
cache.add(url).catch(err => console.warn('[SW] skip', url, err))
|
|
))
|
|
).then(() => self.skipWaiting())
|
|
);
|
|
});
|
|
|
|
self.addEventListener('activate', event => {
|
|
event.waitUntil(
|
|
caches.keys().then(keys =>
|
|
Promise.all(keys.filter(k => k !== CACHE_VERSION).map(k => caches.delete(k)))
|
|
).then(() => self.clients.claim())
|
|
);
|
|
});
|
|
|
|
self.addEventListener('fetch', event => {
|
|
const req = event.request;
|
|
if (req.method !== 'GET') return;
|
|
|
|
const url = new URL(req.url);
|
|
|
|
// SSO: let the browser handle these itself. /start and /callback are
|
|
// redirects that must be followed as a top-level navigation and carry the
|
|
// flow cookie; wrapping them in fetch() would risk rendering the JSON
|
|
// offline fallback in the address bar.
|
|
if (url.pathname.startsWith('/api/auth/oidc/')) return;
|
|
|
|
// API routes: always go to the network, no offline fallback (we'd just
|
|
// be returning stale account/event data otherwise).
|
|
if (url.pathname.startsWith('/api/')) {
|
|
event.respondWith(
|
|
fetch(req).catch(() =>
|
|
new Response(JSON.stringify({ offline: true }), {
|
|
status: 503,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
})
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Everything else: network-first. The browser's HTTP cache (driven by
|
|
// the server's Cache-Control headers) already throttles re-fetches —
|
|
// the SW just makes sure offline still works for the entry HTML.
|
|
event.respondWith(
|
|
fetch(req).then(resp => {
|
|
// Keep a fresh copy of navigation requests / index.html for offline
|
|
const isNavigation = req.mode === 'navigate'
|
|
|| url.pathname === '/'
|
|
|| url.pathname === '/index.html';
|
|
if (isNavigation && resp && resp.status === 200) {
|
|
const clone = resp.clone();
|
|
caches.open(CACHE_VERSION).then(c => c.put(req, clone)).catch(() => {});
|
|
}
|
|
return resp;
|
|
}).catch(() => {
|
|
// Offline fallback: only the HTML shell is served from cache, so the
|
|
// app at least renders and can show its own offline UI.
|
|
if (req.mode === 'navigate'
|
|
|| url.pathname === '/'
|
|
|| url.pathname === '/index.html') {
|
|
return caches.match(req).then(c => c || caches.match('/index.html'));
|
|
}
|
|
return new Response('', { status: 503 });
|
|
})
|
|
);
|
|
});
|