feat(terminal): add Life OS command interface
This commit is contained in:
1 parent
93d0487617
commit
8a1dee3c49
15 files changed
+321
-34
No files matched your search
+33
-3
@@ -41,6 +41,7 @@ assert.doesNotMatch(source, /localizeDynamicText|createTreeWalker|NodeFilter\.SH
|
||||
assert.match(source, /id==='system'\)window\.systemApp\(c\)/);
|
||||
assert.match(source, /id==='today'\)window\.today\(c\)/);
|
||||
assert.match(source, /id==='browser'\)window\.browserApp\(c\)/);
|
||||
assert.match(source, /id==='terminal'\)window\.terminalApp\(c\)/);
|
||||
assert.match(assets.i18nJs, /window\.setLanguage = setLanguage/);
|
||||
assert.match(assets.i18nRenderersJs, /window\.renderAll\(\)/);
|
||||
assert.match(assets.indexHtml, /setTimeout\(\(\)=>window\.applyLanguage\?\.\(\),0\)/, "missing locale UI code cannot abort desktop initialization");
|
||||
@@ -48,11 +49,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, 13, "the complete desktop script chain is present in the served HTML");
|
||||
assert.equal(shippedScripts.length, 15, "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.tasksJs, assets.calendarJs, 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.tasksJs, assets.calendarJs, assets.i18nRenderersJs, assets.desktopPersonalizationJs, assets.desktopWidgetsJs, assets.browserAppJs, assets.addonsJs]) new vm.Script(script);
|
||||
const browserNodes = Object.fromEntries(['.browser-toolbar', '.browser-address', 'iframe', '[data-action="back"]', '[data-action="forward"]', '.browser-external', '.browser-hint-link'].map(key => [key, { value: '', href: '', disabled: false, listeners: {}, addEventListener(type, handler) { this.listeners[type] = handler; }, getAttribute(name) { return this[name] ?? null; } }]));
|
||||
const browserContainer = { dataset: {}, innerHTML: '', querySelector(selector) { return selector === '.browser-shell' ? (this.innerHTML ? {} : null) : browserNodes[selector]; } };
|
||||
const browserMessages = [];
|
||||
@@ -71,6 +72,35 @@ assert.equal(browserNodes.iframe.src, 'https://example.org/path', 'unsafe scheme
|
||||
assert.equal(browserMessages.at(-1), 'browser.invalid');
|
||||
browserNodes['.browser-toolbar'].listeners.click({ target: { closest: () => ({ dataset: { action: 'back' } }) } });
|
||||
assert.equal(browserNodes.iframe.src, 'https://www.google.com/webhp?igu=1');
|
||||
const terminalStorage = new Map(), terminalRenders = [];
|
||||
const terminalContext = vm.createContext({
|
||||
document: { createElement: () => ({ textContent: '' }), head: { append() {} } },
|
||||
LifeI18n: { t: key => key }, lang: () => 'en',
|
||||
APPS: { calendar: ['icon', 'CALENDAR'] }, openApp: id => terminalRenders.push(id),
|
||||
crypto: globalThis.crypto, day: () => '2026-09-28',
|
||||
mems: () => terminalStorage.get('lifeos_memories') || [],
|
||||
sys: () => terminalStorage.get('lifeos_system') || { schemaVersion: 2 },
|
||||
save: (key, value) => terminalStorage.set(key, structuredClone(value)),
|
||||
renderAll: () => terminalRenders.push('render'),
|
||||
filmFor: () => ({ filmTitle: 'Prompt', filmSubtitle: '', filmLine: '' })
|
||||
});
|
||||
terminalContext.window = terminalContext;
|
||||
vm.runInContext(assets.dataActionsJs, terminalContext);
|
||||
vm.runInContext(assets.terminalAppJs, terminalContext);
|
||||
const command = terminalContext.LifeOSTerminalEngine.run;
|
||||
assert.equal(command('goal add "Sixth form" --due 2026-12-12 --pin').message, 'terminal.goalSaved');
|
||||
assert.equal(command('task add "Review words" --goal "Sixth form" --priority high').message, 'terminal.taskSaved');
|
||||
assert.equal(command('task done 1').message, 'terminal.taskCompleted');
|
||||
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('goal add "Invalid date" --due 2026-02-30'), /invalidDate/);
|
||||
assert.equal(command('memory add "Multiline" --date 2026-09-29').compose.title, 'Multiline');
|
||||
assert.equal(command('open calendar').message, 'terminal.opened');
|
||||
assert.ok(terminalRenders.includes('calendar'));
|
||||
assert.deepEqual(Array.from(terminalContext.LifeOSTerminalEngine.words('task add "quoted title" --priority high')), ['task','add','quoted title','--priority','high']);
|
||||
assert.doesNotMatch(source, /Version 0\.1|Schema v1/);
|
||||
const makeStorage = initial => {
|
||||
const values = new Map(Object.entries(initial || {}));
|
||||
@@ -153,7 +183,7 @@ const uiContext = vm.createContext({
|
||||
...Object.fromEntries(["icons", "tasks", "launcher", "calendar", "desktop", "boot", "modal"].map(id => [id, uiNodes.get(`#${id}`)]))
|
||||
});
|
||||
uiContext.window = uiContext;
|
||||
for (const index of [0, 2, 3, 4, 8, 9]) vm.runInContext(shippedScripts[index], uiContext);
|
||||
for (const index of [0, 2, 3, 4, 5, 9, 10]) vm.runInContext(shippedScripts[index], uiContext);
|
||||
vm.runInContext("windows.system={classList:{contains(){return false}}}", uiContext);
|
||||
uiContext.setLanguage("zh");
|
||||
assert.equal(uiContext.LifeI18n.language(), "zh");
|
||||
|
||||
Reference in new issue
Block a user