From 0ae68d68426de2159288adb5968cd6f3124bf471 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 23:05:19 +0000 Subject: [PATCH] Family board: tapping a chore assigned to several kids opens the right kid's chore, and each kid has their own column Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01UaC9HmtHMdRP8dAbN4wAKj --- public/board.js | 10 +++++----- public/sw.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/public/board.js b/public/board.js index 89677f4..f2faa56 100644 --- a/public/board.js +++ b/public/board.js @@ -19,9 +19,9 @@ function draw() { $('#tiles').innerHTML = d.people.map(p => ``).join(''); $('#tiles').querySelectorAll('.tile').forEach(b => b.onclick = () => pick(d.people.find(p => p.id === b.dataset.id))); const day = (label, ds) => { const l = d.events.filter(e => e.allDay ? e.start <= ds && e.end > ds : ymd(new Date(e.start)) === ds).sort((a, b) => (b.allDay - a.allDay) || a.start - b.start); return `${label}` + (l.map(e => `
${esc(e.title)}${e.by ? ` ${esc(e.by)}` : ''}${e.allDay ? 'All day' : new Date(e.start).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })}
`).join('') || none('Nothing')); }; - const pill = (c, cls) => ``; - const pool = d.chores.filter(c => c.claimable && !c.done), per = {}; d.chores.filter(c => !(c.claimable && !c.done)).forEach(c => { (per[c.userName] = per[c.userName] || []).push(c); }); - const SHOW = 6, chores = (pool.length ? `` : '') + '
' + Object.keys(per).map(k => { const l = per[k], dn = l.filter(c => c.done).length, left = l.filter(c => !c.done); return `
${esc(k)}${dn} of ${l.length}
${left.map(c => pill(c)).join('')}${left.length ? '' : 'All done 🎉'}
`; }).join('') + '
'; + const pill = (c, cls) => ``; + const pool = d.chores.filter(c => c.claimable && !c.done), per = {}; d.chores.filter(c => !(c.claimable && !c.done)).forEach(c => { (per[c.user || c.userName] = per[c.user || c.userName] || []).push(c); }); + const SHOW = 6, chores = (pool.length ? `` : '') + '
' + Object.keys(per).map(k => { const l = per[k], dn = l.filter(c => c.done).length, left = l.filter(c => !c.done); return `
${esc(l[0].userName)}${dn} of ${l.length}
${left.map(c => pill(c)).join('')}${left.length ? '' : 'All done 🎉'}
`; }).join('') + '
'; $('#grid').innerHTML = card('Chores today', chores || none('No chores today'), '', 'wide') + card('Today and tomorrow', day('Today', t0) + day('Tomorrow', t1), 'event') + card('This week', weekCard(d.week)) + card('Shopping list', d.shopping.map(i => `
${esc(i.text)}${i.by ? `${esc(i.by)}` : ''}
`).join('') || none('Nothing to buy'), 'shopping') @@ -31,9 +31,9 @@ function draw() { } function poolAll() { const list = data.chores.filter(c => c.claimable && !c.done), { box, close } = sheet(`

Up for grabs

${list.length} chores. Tap one to pick it

${list.map(c => ``).join('')}

`); - box.querySelector('#no').onclick = close; box.querySelectorAll('.pill[data-c]').forEach(b => b.onclick = () => choreTap(data.chores.find(c => c.chore === b.dataset.c))); + box.querySelector('#no').onclick = close; box.querySelectorAll('.pill[data-c]').forEach(b => b.onclick = () => choreTap(data.chores.find(c => c.chore === b.dataset.c && (c.user || '') === (b.dataset.u || '')))); } -function wireChores() { document.querySelectorAll('[data-add]').forEach(b => b.onclick = () => addFlow(b.dataset.add)); const all = document.getElementById('poolall'); if (all) all.onclick = poolAll; document.querySelectorAll(".pill[data-c]").forEach(b => b.onclick = () => choreTap(data.chores.find(c => c.chore === b.dataset.c))); } +function wireChores() { document.querySelectorAll('[data-add]').forEach(b => b.onclick = () => addFlow(b.dataset.add)); const all = document.getElementById('poolall'); if (all) all.onclick = poolAll; document.querySelectorAll(".pill[data-c]").forEach(b => b.onclick = () => choreTap(data.chores.find(c => c.chore === b.dataset.c && (c.user || '') === (b.dataset.u || '')))); } async function load() { try { const r = await fetch('/api/board?day=' + ymd(new Date())); if (r.status === 403) { location.replace('/login'); return; } data = await r.json(); draw(); } catch {} } function pick(p) { diff --git a/public/sw.js b/public/sw.js index 38d05e2..e0eb9a2 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,5 +1,5 @@ // App-shell cache only; Seafile/API traffic always goes to the network. -const CACHE = 'hub-v134'; +const CACHE = 'hub-v135'; const KEEP = /^hub-(offline|share|who|data-)/; // caches that outlive a new version const SHARE = 'hub-share'; // files shared into the app from other apps, held until the person picks a folder const SHELL = ['/icon.svg', '/manifest.webmanifest', '/icons/icon-192.png', '/icons/apple-touch-icon.png'];