Shared Family calendar and task list; notes default to Family; phone chat and Family card fixes
build-container / build (push) Successful in 4m14s

- Built-in Family calendar everyone with Calendar can edit (data/family)
- Family task list shared by everyone with Tasks
- New notes and lists start shared with the family
- Phone chat sized above the bottom bar and mini player so the message box is visible
- Family page cards no longer overflow on long chat previews
- Tests added and updated

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-26 12:33:23 -04:00
1 parent 0383da87f5
commit 058f410a58
11 files changed
+65 -17

No files matched your search

+14 -4
View File
@@ -10,8 +10,18 @@ const caldav = require('./caldav');
const users = require('./users');
const file = () => path.join(users.dir(), 'tasks.json'); // each person has their own
const load = () => { try { return JSON.parse(fs.readFileSync(file(), 'utf8')); } catch { return { lists: [{ id: 'inbox', name: 'Tasks' }], tasks: [] }; } };
const save = d => atomicWrite(file(), JSON.stringify(d, null, 2), { mode: 0o600 });
const famFile = () => path.join(process.env.DATA_DIR || path.join(__dirname, 'data'), 'family', 'tasks.json'); // the Family list: one for the whole household
const FAMILY = 'family';
const readJ = (f, d) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch { return d; } };
/** Your own lists and tasks, plus the shared Family list (kept in its own file, so everyone sees and edits the same one). */
const load = () => {
const mine = readJ(file(), { lists: [{ id: 'inbox', name: 'Tasks' }], tasks: [] }), fam = readJ(famFile(), { tasks: [] });
return { ...mine, lists: (l => [...l.slice(0, 1), { id: FAMILY, name: 'Family', shared: true }, ...l.slice(1)])(mine.lists.filter(l => l.id !== FAMILY)), tasks: [...mine.tasks.filter(t => t.list !== FAMILY), ...(Array.isArray(fam.tasks) ? fam.tasks : [])] };
};
const save = d => {
atomicWrite(file(), JSON.stringify({ ...d, lists: d.lists.filter(l => l.id !== FAMILY), tasks: d.tasks.filter(t => t.list !== FAMILY) }, null, 2), { mode: 0o600 });
fs.mkdirSync(path.dirname(famFile()), { recursive: true }); atomicWrite(famFile(), JSON.stringify({ tasks: d.tasks.filter(t => t.list === FAMILY) }, null, 2), { mode: 0o600 });
};
const { readBody } = require('./body');
const json = (res, code, obj) => { res.writeHead(code, { 'content-type': 'application/json' }); res.end(JSON.stringify(obj)); };
const isLocal = id => String(id || '').startsWith('local:');
@@ -78,7 +88,7 @@ async function handle(req, res) {
try {
if (p === '/api/tasks/lists' && req.method === 'GET') {
const d = load(), st = caldav.status();
return json(res, 200, { lists: [...d.lists.map(l => ({ id: 'local:' + l.id, name: l.name, local: true, ...(l.mealie ? { mealie: true } : {}) })), ...caldav.todoLists().map(l => ({ ...l, icloud: true }))],
return json(res, 200, { lists: [...d.lists.map(l => ({ id: 'local:' + l.id, name: l.name, local: true, ...(l.mealie ? { mealie: true } : {}), ...(l.shared ? { shared: true } : {}) })), ...caldav.todoLists().map(l => ({ ...l, icloud: true }))],
icloud: { connected: !!st.connected, reminderLists: st.reminderLists || 0 } });
}
if (p === '/api/tasks/lists' && req.method === 'POST') {
@@ -89,7 +99,7 @@ async function handle(req, res) {
}
if (p === '/api/tasks/lists/delete' && req.method === 'POST') {
const { id } = JSON.parse(await readBody(req)), d = load(), raw = String(id).replace(/^local:/, '');
if (raw === 'inbox') throw new Error('The main list cannot be deleted');
if (raw === 'inbox' || raw === FAMILY) throw new Error('That list cannot be deleted');
d.lists = d.lists.filter(l => l.id !== raw); d.tasks = d.tasks.filter(t => t.list !== raw); save(d);
return json(res, 200, { ok: true });
}