fix(tasks): enable editing goals and tasks

This commit is contained in:
Codex committed 2026-10-02 10:48:13 +00:00
1 parent 139e4c5198
commit 0a68782172
23 files changed
+191 -34

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@ const meta = new URL("../dist/.openai/", import.meta.url);
await rm(dist, { recursive: true, force: true });
await Promise.all([mkdir(server, { recursive: true }), mkdir(meta, { recursive: true })]);
const [rawIndexHtml, addonsJs, bootGuardJs, i18nJs, tasksJs, calendarJs, notificationsJs, i18nRenderersJs, desktopPersonalizationJs, desktopWidgetsJs, systemNavigationJs, browserAppJs, dataActionsJs, terminalAppJs, dbAdminJs, manifest, serviceWorker, icon, localeEn, localeZh] = await Promise.all([text("index.html"), text("addons.js"), text("boot-guard.js"), text("i18n.js"), text("tasks.js"), text("calendar.js"), text("notifications.js"), text("i18n-renderers.js"), text("desktop-personalization.js"), text("desktop-widgets.js"), text("system-navigation.js"), text("browser-app.js"), text("data-actions.js"), text("terminal-app.js"), text("db-admin.js"), text("manifest.webmanifest"), text("service-worker.js"), text("life-os-icon.svg"), readFile(new URL("../public/locales/en.json", import.meta.url), "utf8"), readFile(new URL("../public/locales/zh-CN.json", import.meta.url), "utf8")]);
const release = "1.18.0";
const release = "1.18.1";
const localeEnJs = `window.LifeOSLocales=window.LifeOSLocales||{};window.LifeOSLocales.en=${localeEn};`;
const localeZhJs = `window.LifeOSLocales=window.LifeOSLocales||{};window.LifeOSLocales['zh-CN']=${localeZh};`;
const extraLocaleCodes = ['zh-HK', 'zh-MO', 'zh-TW', 'ar', 'fr', 'ru', 'es', 'ja'];
+53 -2
View File
@@ -70,6 +70,57 @@ assert.doesNotMatch(assets.indexHtml, /<script\s+src=/, "the desktop cannot sile
assert.ok(assets.indexHtml.indexOf('window.systemApp =') < assets.indexHtml.lastIndexOf("window.init()"), "locale renderers run before desktop initialization");
for (const script of shippedScripts) new vm.Script(script);
for (const script of [assets.i18nJs, assets.dataActionsJs, assets.terminalAppJs, assets.dbAdminJs, assets.tasksJs, assets.calendarJs, assets.notificationsJs, assets.i18nRenderersJs, assets.desktopPersonalizationJs, assets.desktopWidgetsJs, assets.systemNavigationJs, assets.browserAppJs, assets.addonsJs]) new vm.Script(script);
const editState = { tasksBoard: { goals: [
{ id: 'g1', title: 'Original goal', description: 'Old note', targetDate: '2026-11-01', showOnDesktop: true, done: false, createdAt: '2026-09-01T00:00:00Z' },
{ id: 'g2', title: 'Finished goal', done: true, targetDate: '', createdAt: '2026-09-01T00:00:00Z' }
], tasks: [
{ id: 't1', title: 'Original task', dueDate: '2026-10-10', goalId: 'g1', priority: 'normal', done: false, createdAt: '2026-09-02T00:00:00Z' },
{ id: 't2', title: 'Finished task', dueDate: '', goalId: 'g2', priority: 'low', done: true, completedAt: '2026-09-20T00:00:00Z', createdAt: '2026-09-02T00:00:00Z' }
] } };
const editModal = { innerHTML: '' }, editToasts = [], editContent = { innerHTML: '' };
const editContext = vm.createContext({
document: { createElement: () => ({}), head: { append() {} }, querySelector: selector => selector === '#modal' ? editModal : null },
LifeI18n: { t: key => key, locale: () => 'en-US' },
sys: () => editState, save: (key, value) => { if (key === 'lifeos_system') Object.assign(editState, structuredClone(value)); },
renderAll: () => {}, closeModal: () => { editModal.innerHTML = ''; }, toast: value => editToasts.push(value),
day: () => '2026-10-02', setInterval: () => 0, setTimeout: () => 0, crypto: globalThis.crypto, Date
});
editContext.window = editContext;
vm.runInContext(assets.dataActionsJs, editContext);
vm.runInContext(assets.tasksJs, editContext);
editContext.tasksApp(editContent);
assert.match(editContent.innerHTML, /openTaskEdit\(this\.dataset\.id\)/);
assert.match(editContent.innerHTML, /openGoalForm\(this\.dataset\.id\)/);
editContext.setTaskFilter('done');
editContext.tasksApp(editContent);
assert.match(editContent.innerHTML, /Finished task/);
assert.match(editContent.innerHTML, /openTaskEdit\(this\.dataset\.id\)/, 'completed tasks have an edit button');
editContext.openTaskEdit('t1');
assert.match(editModal.innerHTML, /value="Original task"/);
assert.match(editModal.innerHTML, /value="2026-10-10"/);
const taskFields = { '#task-edit-title-input': 'Updated task', '#task-edit-date': '2026-10-12', '#task-edit-goal': 'g2', '#task-edit-priority': 'high' };
editContext.saveTaskEdit({ preventDefault() {}, currentTarget: { dataset: { id: 't1' }, querySelector: selector => ({ value: taskFields[selector] }) } });
assert.equal(editState.tasksBoard.tasks[0].title, 'Updated task');
assert.equal(editState.tasksBoard.tasks[0].goalId, 'g2');
assert.equal(editState.tasksBoard.tasks[0].priority, 'high');
assert.equal(editState.tasksBoard.tasks[0].dueDate, '2026-10-12');
assert.equal(editState.tasksBoard.tasks[0].createdAt, '2026-09-02T00:00:00Z');
assert.equal(editState.tasksBoard.tasks[0].done, false);
assert.throws(() => editContext.LifeOSActions.updateTask('t1', { title: 'No link', goalId: 'missing' }), /notFound/);
assert.equal(editState.tasksBoard.tasks[0].goalId, 'g2', 'invalid edits do not write changes');
editContext.openGoalForm('g1');
assert.match(editModal.innerHTML, /value="Original goal"/);
assert.match(editModal.innerHTML, /checked/);
const goalFields = { '#goal-new-title': { value: 'Updated goal' }, '#goal-new-description': { value: 'New note' }, '#goal-new-date': { value: '' }, '#goal-new-desktop': { checked: true } };
editContext.saveGoal({ preventDefault() {}, currentTarget: { dataset: { id: 'g1' }, querySelector: selector => goalFields[selector] } });
assert.equal(editState.tasksBoard.goals[0].title, 'Updated goal');
assert.equal(editState.tasksBoard.goals[0].description, 'New note');
assert.equal(editState.tasksBoard.goals[0].showOnDesktop, false, 'removing the target date also removes the countdown');
assert.equal(editState.tasksBoard.goals[0].createdAt, '2026-09-01T00:00:00Z');
assert.equal(editState.tasksBoard.tasks[0].goalId, 'g2', 'editing a goal keeps task links');
editContext.openGoalForm('g2');
assert.match(editModal.innerHTML, /value="Finished goal"/, 'finished goals remain editable');
assert.deepEqual(editToasts, ['tasks.taskUpdated', 'tasks.goalUpdated']);
const reminderStorage = new Map(), delivered = [];
reminderStorage.set('lifeos_notifications_v1', JSON.stringify(Object.fromEntries(['memory', 'tasks', 'goals'].map(kind => [kind, { enabled: true, time: '12:00' }]))));
const reminderContext = vm.createContext({
@@ -143,7 +194,7 @@ assert.equal(terminalStorage.get('lifeos_system').tasksBoard.tasks[0].done, true
assert.equal(command('memory add "Experiment" --date 2026-09-28 --tag school --energy 4 --text "Finished lab"').message, 'terminal.memorySaved');
assert.equal(terminalStorage.get('lifeos_memories')[0].text, 'Finished lab');
assert.equal(terminalStorage.get('lifeos_memories')[0].tags[0], 'school');
assert.throws(() => command('memory add "Again" --text "Duplicate"'), /memoryExists/);
assert.throws(() => command('memory add "Again" --date 2026-09-28 --text "Duplicate"'), /memoryExists/);
assert.throws(() => command('goal add "Invalid date" --due 2026-02-30'), /invalidDate/);
assert.match(command('memory add "Multiline" --date 2026-09-29').message, /terminal\.wizard\.tags/);
assert.match(command('back').message, /terminal\.wizard\.date/);
@@ -218,7 +269,7 @@ assert.ok(command('date').message.length > 8);
assert.ok(command('time').message.includes(':'));
assert.ok(command('datetime').message.length > 12);
assert.equal(command('uptime').message, 'terminal.uptime');
assert.equal(command('version').message, 'Life OS v1.18.0');
assert.equal(command('version').message, 'Life OS v1.18.1');
assert.equal(command('status').message, 'terminal.status');
assert.match(command('apps').message, /calendar/);
assert.equal(command('echo "hello Life OS"').message, 'hello Life OS');