feat(settings): organize system preferences by category

This commit is contained in:
Codex committed 2026-09-28 19:58:24 +08:00
1 parent cf0327fcaf
commit a88a7f550f
16 files changed
+110 -21

No files matched your search

+4 -4
View File
@@ -7,21 +7,21 @@ 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, 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("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.15.0";
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.16.0";
const localeEnJs = `window.LifeOSLocales=window.LifeOSLocales||{};window.LifeOSLocales.en=${localeEn};`;
const localeZhJs = `window.LifeOSLocales=window.LifeOSLocales||{};window.LifeOSLocales['zh-CN']=${localeZh};`;
let indexHtml = rawIndexHtml.replace('src="addons.js"', `src="addons.js?v=${release}"`).replace('src="tasks.js"', `src="tasks.js?v=${release}"`).replace('src="calendar.js"', `src="calendar.js?v=${release}"`);
for (const [src, code] of [
['boot-guard.js', bootGuardJs], ['locales/en.js', localeEnJs], ['locales/zh-CN.js', localeZhJs],
['i18n.js', i18nJs], ['tasks.js', tasksJs], ['calendar.js', calendarJs],
['addons.js', addonsJs], ['notifications.js', notificationsJs], ['i18n-renderers.js', i18nRenderersJs], ['desktop-personalization.js', desktopPersonalizationJs], ['desktop-widgets.js', desktopWidgetsJs], ['browser-app.js', browserAppJs], ['data-actions.js', dataActionsJs], ['terminal-app.js', terminalAppJs], ['db-admin.js', dbAdminJs]
['addons.js', addonsJs], ['notifications.js', notificationsJs], ['i18n-renderers.js', i18nRenderersJs], ['desktop-personalization.js', desktopPersonalizationJs], ['desktop-widgets.js', desktopWidgetsJs], ['system-navigation.js', systemNavigationJs], ['browser-app.js', browserAppJs], ['data-actions.js', dataActionsJs], ['terminal-app.js', terminalAppJs], ['db-admin.js', dbAdminJs]
]) {
const tag = `<script src="${src}${src === 'boot-guard.js' ? '' : `?v=${release}`}"></script>`;
if (!indexHtml.includes(tag)) throw new Error(`Missing Life OS script: ${src}`);
indexHtml = indexHtml.replace(tag, `<script>${code.replace(/<\/script/gi, '<\\/script')}</script>`);
}
await writeFile(new URL("assets.js", server), `export const indexHtml=${JSON.stringify(indexHtml)};\nexport const addonsJs=${JSON.stringify(addonsJs)};\nexport const bootGuardJs=${JSON.stringify(bootGuardJs)};\nexport const i18nJs=${JSON.stringify(i18nJs)};\nexport const localeEn=JSON.parse(${JSON.stringify(localeEn)});\nexport const localeZh=JSON.parse(${JSON.stringify(localeZh)});\nexport const localeEnJs=${JSON.stringify(localeEnJs)};\nexport const localeZhJs=${JSON.stringify(localeZhJs)};\nexport const tasksJs=${JSON.stringify(tasksJs)};\nexport const calendarJs=${JSON.stringify(calendarJs)};\nexport const notificationsJs=${JSON.stringify(notificationsJs)};\nexport const i18nRenderersJs=${JSON.stringify(i18nRenderersJs)};\nexport const desktopPersonalizationJs=${JSON.stringify(desktopPersonalizationJs)};\nexport const desktopWidgetsJs=${JSON.stringify(desktopWidgetsJs)};\nexport const browserAppJs=${JSON.stringify(browserAppJs)};\nexport const dataActionsJs=${JSON.stringify(dataActionsJs)};\nexport const terminalAppJs=${JSON.stringify(terminalAppJs)};\nexport const dbAdminJs=${JSON.stringify(dbAdminJs)};\nexport const manifest=${JSON.stringify(manifest)};\nexport const serviceWorker=${JSON.stringify(serviceWorker)};\nexport const icon=${JSON.stringify(icon)};\n`);
await writeFile(new URL("assets.js", server), `export const indexHtml=${JSON.stringify(indexHtml)};\nexport const addonsJs=${JSON.stringify(addonsJs)};\nexport const bootGuardJs=${JSON.stringify(bootGuardJs)};\nexport const i18nJs=${JSON.stringify(i18nJs)};\nexport const localeEn=JSON.parse(${JSON.stringify(localeEn)});\nexport const localeZh=JSON.parse(${JSON.stringify(localeZh)});\nexport const localeEnJs=${JSON.stringify(localeEnJs)};\nexport const localeZhJs=${JSON.stringify(localeZhJs)};\nexport const tasksJs=${JSON.stringify(tasksJs)};\nexport const calendarJs=${JSON.stringify(calendarJs)};\nexport const notificationsJs=${JSON.stringify(notificationsJs)};\nexport const i18nRenderersJs=${JSON.stringify(i18nRenderersJs)};\nexport const desktopPersonalizationJs=${JSON.stringify(desktopPersonalizationJs)};\nexport const desktopWidgetsJs=${JSON.stringify(desktopWidgetsJs)};\nexport const systemNavigationJs=${JSON.stringify(systemNavigationJs)};\nexport const browserAppJs=${JSON.stringify(browserAppJs)};\nexport const dataActionsJs=${JSON.stringify(dataActionsJs)};\nexport const terminalAppJs=${JSON.stringify(terminalAppJs)};\nexport const dbAdminJs=${JSON.stringify(dbAdminJs)};\nexport const manifest=${JSON.stringify(manifest)};\nexport const serviceWorker=${JSON.stringify(serviceWorker)};\nexport const icon=${JSON.stringify(icon)};\n`);
await copyFile(new URL("../worker/index.js", import.meta.url), new URL("index.js", server));
await copyFile(new URL("../worker/cloud-state.js", import.meta.url), new URL("cloud-state.js", server));
await copyFile(new URL("../worker/db-admin.js", import.meta.url), new URL("db-admin.js", server));
+5 -3
View File
@@ -18,6 +18,8 @@ assert.match(assets.addonsJs, /api\/lifeos-sync/);
assert.match(assets.i18nRenderersJs, /dialog\.goals/);
assert.match(assets.i18nRenderersJs, /shell\.wallpaperTitle/);
assert.match(assets.i18nRenderersJs, /window\.museumApp/);
assert.ok(assets.indexHtml.indexOf('window.systemApp = container =>') < assets.indexHtml.indexOf('system-settings-shell'), 'category layout wraps existing settings renderers');
for (const group of ['appearance', 'notifications', 'language', 'data', 'about']) assert.match(assets.i18nRenderersJs, new RegExp(`data-settings-group="${group}"`));
assert.equal(assets.manifest.includes('"description"'), false);
const flatten = (value, prefix = "") => Object.entries(value).flatMap(([key, child]) => child && typeof child === "object" && !Array.isArray(child) ? flatten(child, prefix ? `${prefix}.${key}` : key) : [prefix ? `${prefix}.${key}` : key]).sort();
const enKeys = flatten(assets.localeEn), zhKeys = flatten(assets.localeZh);
@@ -50,11 +52,11 @@ assert.doesNotMatch(assets.indexHtml, /setTimeout\(applyLanguage\s*,/);
assert.doesNotMatch(assets.addonsJs, /(?<![\w.])applyLanguage\s*\(/, "add-on actions must not call a missing global locale function");
assert.match(assets.indexHtml, /window\.renderIcons=renderIcons;window\.renderAll=renderAll/, "shared renderers are explicitly exposed to locale scripts");
const shippedScripts = [...assets.indexHtml.matchAll(/<script>([\s\S]*?)<\/script>/g)].map(match => match[1]);
assert.equal(shippedScripts.length, 17, "the complete desktop script chain is present in the served HTML");
assert.equal(shippedScripts.length, 18, "the complete desktop script chain is present in the served HTML");
assert.doesNotMatch(assets.indexHtml, /<script\s+src=/, "the desktop cannot silently fall back when an app script fails to load");
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.browserAppJs, assets.addonsJs]) 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 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({
@@ -203,7 +205,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.15.0');
assert.equal(command('version').message, 'Life OS v1.16.0');
assert.equal(command('status').message, 'terminal.status');
assert.match(command('apps').message, /calendar/);
assert.equal(command('echo "hello Life OS"').message, 'hello Life OS');