Files
KeepItGoingServer/templates/sw.js
T
Keith SmithandClaude Sonnet 5 922d4d1bf2 Add offline task CRUD, phase 1 (PWA)
Lets users create, edit, and complete tasks with no network connection,
queued locally in IndexedDB and replayed via the existing /api/sync/
protocol once back online -- the same protocol the native Android app
already uses, with zero backend changes. The service worker now routes
the dashboard's offline fallback to a small client-rendered task app
instead of the generic "you're offline" page; every other route keeps
the generic fallback.

Conflicts (server row touched elsewhere since last sync) auto-resolve
as "local wins" rather than surfacing a resolution UI. Scope is title/
status/priority/due-date only -- tags, subtasks, time tracking, and
recurrence editing offline are out of scope for this phase.

Also adds the first real test coverage for sync/views.py (previously
untested): new-item creation, soft-delete-bypasses-conflict-check,
conflict detection/discarding, and both resolve_conflict paths.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 23:01:47 -06:00

76 lines
2.4 KiB
JavaScript

{% load static %}const CACHE_NAME = 'keepitgoing-shell-v2';
const OFFLINE_URL = '{% url "offline" %}';
const OFFLINE_TASKS_URL = '{% url "offline-tasks" %}';
const PRECACHE_URLS = [
"{% static 'css/app.css' %}",
"{% static 'js/app.js' %}",
"{% static 'js/offline-db.js' %}",
"{% static 'js/offline-tasks.js' %}",
"{% static 'favicons/favicon.svg' %}",
"{% static 'favicons/icon-192.png' %}",
"{% static 'favicons/icon-512.png' %}",
"{% static 'favicons/icon-512-maskable.png' %}",
"{% static 'favicons/apple-touch-icon-180.png' %}",
OFFLINE_URL,
OFFLINE_TASKS_URL,
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS))
);
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)))
)
);
self.clients.claim();
});
self.addEventListener('fetch', (event) => {
const request = event.request;
// Navigations: try the network first, fall back to the offline page.
// The dashboard route falls back to the offline-capable task app instead
// of the generic offline page, since it can still show/edit cached tasks.
if (request.mode === 'navigate') {
event.respondWith(
fetch(request).catch(() => {
const path = new URL(request.url).pathname;
return caches.match(path === '/' ? OFFLINE_TASKS_URL : OFFLINE_URL);
})
);
return;
}
// Precached shell assets: serve from cache first.
const path = new URL(request.url).pathname;
if (PRECACHE_URLS.includes(path)) {
event.respondWith(
caches.match(request).then((cached) => cached || fetch(request))
);
}
});
self.addEventListener('push', (event) => {
const data = event.data ? event.data.json() : {};
event.waitUntil(
self.registration.showNotification(data.title || 'KeepItGoing', {
body: data.body || '',
icon: '{% static "favicons/icon-192.png" %}',
badge: '{% static "favicons/icon-192.png" %}',
data: { url: data.url || '/' },
})
);
});
self.addEventListener('notificationclick', (event) => {
event.notification.close();
event.waitUntil(clients.openWindow(event.notification.data.url || '/'));
});