From 22b9b28181cb1c78c57af06f792db887ffd2c455 Mon Sep 17 00:00:00 2001 From: Mathieu2301 <21021423+Mathieu2301@users.noreply.github.com> Date: Wed, 16 Sep 2026 06:05:09 +0200 Subject: [PATCH] docs: use neutral English examples --- docs/agent-guide.md | 10 ++-- packages/cli/assets/agent-guide.md | 10 ++-- packages/cli/test/discovery.test.ts | 42 +++++++-------- packages/cli/test/mcp.test.ts | 4 +- packages/cli/test/support/flows.ts | 36 ++++++------- packages/cli/test/support/host.ts | 2 +- packages/component/README.md | 8 +-- .../examples/{salon.ts => living-room.ts} | 24 ++++----- packages/component/test/component.test.ts | 52 +++++++++---------- packages/component/test/ui.test.ts | 40 +++++++------- templates/home/README.md | 2 +- templates/home/component/main.ts | 28 +++++----- templates/home/coordinator/home.ts | 8 +-- templates/home/miakapp.yaml | 6 +-- templates/home/test/home.test.ts | 4 +- 15 files changed, 138 insertions(+), 138 deletions(-) rename packages/component/examples/{salon.ts => living-room.ts} (74%) diff --git a/docs/agent-guide.md b/docs/agent-guide.md index d71ded3..6c0f1ce 100644 --- a/docs/agent-guide.md +++ b/docs/agent-guide.md @@ -145,8 +145,8 @@ Four declarations: ```ts { - state: { 'zone.salon.light.on': false }, // paths and initial values - stateAccess: [{ userId, patterns: ['zone.salon.*'] }], + state: { 'zone.living_room.light.on': false }, // paths and initial values + stateAccess: [{ userId, patterns: ['zone.living_room.*'] }], events: [{ topic, directions: EventDirection.publishToUsers }], eventAccess: [{ userId, publish: [], subscribe: [topic] }], functions: { async 'lighting.set'(call) { /* authorize, act, return */ } }, @@ -201,8 +201,8 @@ Handlers are functions, not identifiers to wire up by hand: ```ts ui.toggle({ - id: 'salon-light', - label: 'Lampe du salon', + id: 'living-room-light', + label: 'Living-room lamp', value: asBoolean(home.state.get(LIGHT_ON)), disabled: home.staging || !healthy, pending, @@ -238,7 +238,7 @@ try { } catch (error) { // Deliberately not retried. The call may already have reached the lamp, // and the next state snapshot settles the question. - failure = error instanceof Error ? error.message : 'La commande a échoué'; + failure = error instanceof Error ? error.message : 'The command failed'; } ``` diff --git a/packages/cli/assets/agent-guide.md b/packages/cli/assets/agent-guide.md index d71ded3..6c0f1ce 100644 --- a/packages/cli/assets/agent-guide.md +++ b/packages/cli/assets/agent-guide.md @@ -145,8 +145,8 @@ Four declarations: ```ts { - state: { 'zone.salon.light.on': false }, // paths and initial values - stateAccess: [{ userId, patterns: ['zone.salon.*'] }], + state: { 'zone.living_room.light.on': false }, // paths and initial values + stateAccess: [{ userId, patterns: ['zone.living_room.*'] }], events: [{ topic, directions: EventDirection.publishToUsers }], eventAccess: [{ userId, publish: [], subscribe: [topic] }], functions: { async 'lighting.set'(call) { /* authorize, act, return */ } }, @@ -201,8 +201,8 @@ Handlers are functions, not identifiers to wire up by hand: ```ts ui.toggle({ - id: 'salon-light', - label: 'Lampe du salon', + id: 'living-room-light', + label: 'Living-room lamp', value: asBoolean(home.state.get(LIGHT_ON)), disabled: home.staging || !healthy, pending, @@ -238,7 +238,7 @@ try { } catch (error) { // Deliberately not retried. The call may already have reached the lamp, // and the next state snapshot settles the question. - failure = error instanceof Error ? error.message : 'La commande a échoué'; + failure = error instanceof Error ? error.message : 'The command failed'; } ``` diff --git a/packages/cli/test/discovery.test.ts b/packages/cli/test/discovery.test.ts index 3b9fa59..70f13b2 100644 --- a/packages/cli/test/discovery.test.ts +++ b/packages/cli/test/discovery.test.ts @@ -49,7 +49,7 @@ describe('reading a flows export', () => { describe('the inventory of a house', () => { test('every tab is reported with how many nodes it holds', () => { const tabs = inventory().flows; - expect(tabs.map((tab) => tab.label)).toEqual(['Salon', 'Chauffage']); + expect(tabs.map((tab) => tab.label)).toEqual(['Living room', 'Heating']); expect(tabs[0]?.nodeCount).toBe(5); expect(tabs[1]?.disabled).toBe(true); }); @@ -58,13 +58,13 @@ describe('the inventory of a house', () => { const broker = inventory().brokers[0]; expect(broker?.host).toBe('192.168.1.10'); expect(broker?.port).toBe(1883); - expect(broker?.subscribes).toEqual(['maison/salon/#', 'maison/salon/temperature']); - expect(broker?.publishes).toEqual(['maison/salon/lampe/set']); + expect(broker?.subscribes).toEqual(['home/living-room/#', 'home/living-room/temperature']); + expect(broker?.publishes).toEqual(['home/living-room/lamp/set']); }); test('the home binding is reported without reading the secret out', () => { const home = inventory().homes[0]; - expect(home?.homeId).toBe('maison-colmon'); + expect(home?.homeId).toBe('sample-home'); expect(home?.coordinatorId).toBe('coord-1'); expect(home?.secretInExport).toBe(true); expect(JSON.stringify(inventory())).not.toContain('s3cr3t-in-the-file'); @@ -73,22 +73,22 @@ describe('the inventory of a house', () => { test('committed variables become state candidates, sorted and name-checked', () => { const state = inventory().state; expect(state.map((entry) => entry.path)).toEqual([ - 'chauffage.consigne', - 'salon.*.on', - 'salon.lampe.on', - 'salon.temperature', - 'salon/humidite', + 'heating.setpoint', + 'living_room.*.on', + 'living_room.lamp.on', + 'living_room.temperature', + 'living_room/humidity', ]); - expect(state.find((entry) => entry.path === 'salon.temperature')?.source).toBe('jsonata'); - expect(state.find((entry) => entry.path === 'chauffage.consigne')?.source).toBe('env'); - expect(state.find((entry) => entry.path === 'salon.lampe.on')?.source).toBe('literal'); - expect(state.find((entry) => entry.path === 'salon/humidite')?.legalV4Name).toBe(true); + expect(state.find((entry) => entry.path === 'living_room.temperature')?.source).toBe('jsonata'); + expect(state.find((entry) => entry.path === 'heating.setpoint')?.source).toBe('env'); + expect(state.find((entry) => entry.path === 'living_room.lamp.on')?.source).toBe('literal'); + expect(state.find((entry) => entry.path === 'living_room/humidity')?.legalV4Name).toBe(true); }); test('user actions become function candidates with their groups', () => { const actions = inventory().actions; - expect(actions.map((entry) => entry.inputId)).toEqual(['chauffage.set', 'salon.lampe.toggle']); - expect(actions[0]?.allowedGroups).toEqual(['adultes']); + expect(actions.map((entry) => entry.inputId)).toEqual(['heating.set', 'living_room.lamp.toggle']); + expect(actions[0]?.allowedGroups).toEqual(['adults']); expect(actions[1]?.allowedGroups).toEqual([]); }); @@ -117,17 +117,17 @@ describe('what the inventory refuses to leave unsaid', () => { test('an action with no group is reported as reachable by every user', () => { const open = inventory().findings.find((item) => item.kind === 'unrestricted_action'); expect(open?.severity).toBe('critical'); - expect(open?.detail).toContain('salon.lampe.toggle'); + expect(open?.detail).toContain('living_room.lamp.toggle'); }); test('a V3 name that V4 would reject is reported for rename', () => { const rename = inventory().findings.filter((item) => item.kind === 'name_needs_rename'); - expect(rename.map((item) => item.detail).join(' ')).toContain('salon.*.on'); + expect(rename.map((item) => item.detail).join(' ')).toContain('living_room.*.on'); }); test('a wildcard subscription is separated from a device topic', () => { const wildcard = inventory().findings.find((item) => item.kind === 'wildcard_subscription'); - expect(wildcard?.detail).toContain('maison/salon/#'); + expect(wildcard?.detail).toContain('home/living-room/#'); }); test('a broker without TLS is reported', () => { @@ -144,10 +144,10 @@ describe('what the inventory refuses to leave unsaid', () => { test('a house with nothing wrong reports no finding', () => { const clean = inventory(JSON.stringify([ - { id: 't1', type: 'tab', label: 'Salon' }, + { id: 't1', type: 'tab', label: 'Living room' }, { id: 'b1', type: 'mqtt-broker', name: 'local', broker: 'mqtt.example.test', port: '8883', usetls: true }, - { id: 'i1', type: 'initMiakapi', z: 't1', home: 'maison', coordID: 'c1', coordSecret: '' }, - { id: 'a1', type: 'onUserAction', z: 't1', inputID: 'salon.lampe.toggle', allowedGroups: ['adultes'] }, + { id: 'i1', type: 'initMiakapi', z: 't1', home: 'home', coordID: 'c1', coordSecret: '' }, + { id: 'a1', type: 'onUserAction', z: 't1', inputID: 'living_room.lamp.toggle', allowedGroups: ['adults'] }, ])); expect(clean.findings).toEqual([]); }); diff --git a/packages/cli/test/mcp.test.ts b/packages/cli/test/mcp.test.ts index 6a40175..2e4d68a 100644 --- a/packages/cli/test/mcp.test.ts +++ b/packages/cli/test/mcp.test.ts @@ -118,11 +118,11 @@ describe('argument translation', () => { test('an underscore in a tool argument is the CLI hyphen', () => { expect(optionName('expected_generation')).toBe('expected-generation'); expect(buildArgv(tool('miakapp_init'), { - home: 'lumiere', + home: 'light', control_plane: 'https://control.example.test/api', })).toEqual([ 'init', - '--home', 'lumiere', + '--home', 'light', '--control-plane', 'https://control.example.test/api', ]); }); diff --git a/packages/cli/test/support/flows.ts b/packages/cli/test/support/flows.ts index 552094d..c1f27ee 100644 --- a/packages/cli/test/support/flows.ts +++ b/packages/cli/test/support/flows.ts @@ -1,6 +1,6 @@ import { MemoryFiles } from './host.js'; -export const FLOWS_PATH = '/home/mathieu/node-red/flows.json'; +export const FLOWS_PATH = '/home/tester/node-red/flows.json'; /** * A synthetic V3 house, written to exercise every branch of the inventory: two @@ -13,12 +13,12 @@ export const FLOWS_PATH = '/home/mathieu/node-red/flows.json'; * and `mqtt-broker`, and the `node-red-contrib-MiakAPI` v3 nodes. */ export const FLOWS_EXPORT = JSON.stringify([ - { id: 't1', type: 'tab', label: 'Salon' }, - { id: 't2', type: 'tab', label: 'Chauffage', disabled: true }, + { id: 't1', type: 'tab', label: 'Living room' }, + { id: 't2', type: 'tab', label: 'Heating', disabled: true }, { id: 'b1', type: 'mqtt-broker', - name: 'maison', + name: 'home', broker: '192.168.1.10', port: '1883', usetls: false, @@ -28,42 +28,42 @@ export const FLOWS_EXPORT = JSON.stringify([ id: 'i1', type: 'initMiakapi', z: 't1', - home: 'maison-colmon', + home: 'sample-home', coordID: 'coord-1', coordSecret: 's3cr3t-in-the-file', }, - { id: 'm1', type: 'mqtt in', z: 't1', broker: 'b1', topic: 'maison/salon/temperature', qos: '2' }, - { id: 'm2', type: 'mqtt in', z: 't1', broker: 'b1', topic: 'maison/salon/#', qos: '0' }, - { id: 'm3', type: 'mqtt out', z: 't1', broker: 'b1', topic: 'maison/salon/lampe/set', retain: '' }, + { id: 'm1', type: 'mqtt in', z: 't1', broker: 'b1', topic: 'home/living-room/temperature', qos: '2' }, + { id: 'm2', type: 'mqtt in', z: 't1', broker: 'b1', topic: 'home/living-room/#', qos: '0' }, + { id: 'm3', type: 'mqtt out', z: 't1', broker: 'b1', topic: 'home/living-room/lamp/set', retain: '' }, { id: 'cv1', type: 'commitVariables', z: 't1', - name: 'Salon', + name: 'Living room', values: { - 'salon.temperature': { type: 'jsonata', value: 'payload.temp' }, - 'salon.lampe.on': { type: 'str', value: 'false' }, - 'salon/humidite': { type: 'str', value: '0' }, + 'living_room.temperature': { type: 'jsonata', value: 'payload.temp' }, + 'living_room.lamp.on': { type: 'str', value: 'false' }, + 'living_room/humidity': { type: 'str', value: '0' }, }, }, - { id: 'a1', type: 'onUserAction', z: 't2', inputID: 'salon.lampe.toggle', allowedGroups: [] }, + { id: 'a1', type: 'onUserAction', z: 't2', inputID: 'living_room.lamp.toggle', allowedGroups: [] }, { id: 'a2', type: 'onUserAction', z: 't2', - inputID: 'chauffage.set', - allowedGroups: ['adultes'], + inputID: 'heating.set', + allowedGroups: ['adults'], }, { id: 'cv2', type: 'commitVariables', z: 't2', values: { - 'chauffage.consigne': { type: 'env', value: 'CONSIGNE_DEFAUT' }, - 'salon.*.on': { type: 'str', value: 'false' }, + 'heating.setpoint': { type: 'env', value: 'DEFAULT_SETPOINT' }, + 'living_room.*.on': { type: 'str', value: 'false' }, }, }, - { id: 'n1', type: 'sendPushNotif', z: 't2', title: 'Alerte', body: 'x', adminOnly: true }, + { id: 'n1', type: 'sendPushNotif', z: 't2', title: 'Alert', body: 'x', adminOnly: true }, { id: 'f1', type: 'function', z: 't2', func: 'return msg;' }, { id: 'f2', type: 'function', z: 't2', func: 'return msg;' }, { id: 'inj1', type: 'inject', z: 't2', repeat: '60' }, diff --git a/packages/cli/test/support/host.ts b/packages/cli/test/support/host.ts index f5b0fc5..0b9a62d 100644 --- a/packages/cli/test/support/host.ts +++ b/packages/cli/test/support/host.ts @@ -1,7 +1,7 @@ import type { CliHost, FileSystem } from '../../src/main.js'; import type { FetchLike } from '../../src/internal/http.js'; -export const PROJECT_ROOT = '/home/mathieu/lumiere'; +export const PROJECT_ROOT = '/home/tester/light-project'; export const ARTIFACT_SOURCE = "self.addEventListener('fetch', function () {});\n"; diff --git a/packages/component/README.md b/packages/component/README.md index f749ab4..ad18206 100644 --- a/packages/component/README.md +++ b/packages/component/README.md @@ -15,11 +15,11 @@ This package is that bridge, typed. import { defineComponent, ui } from '@miakapp/component'; defineComponent((home) => ({ - render: () => ui.screen({ title: 'Salon' }, [ + render: () => ui.screen({ title: 'Living room' }, [ ui.toggle({ id: 'lamp', - label: 'Lampe du salon', - value: home.state.get('zone.salon.light.on') === true, + label: 'Living-room lamp', + value: home.state.get('zone.living_room.light.on') === true, onChange: (on) => void home.call('lighting.set', { on }), }), ]), @@ -106,7 +106,7 @@ There is no URL property anywhere. Images and camera surfaces are named by exact granted handles: ```ts -ui.media({ id: 'door', label: 'Caméra d’entrée', handle: 'media.front_door' }) +ui.media({ id: 'door', label: 'Front-door camera', handle: 'media.front_door' }) ``` Handlers may be callbacks or handler IDs. A callback is registered for the diff --git a/packages/component/examples/salon.ts b/packages/component/examples/living-room.ts similarity index 74% rename from packages/component/examples/salon.ts rename to packages/component/examples/living-room.ts index 51595ee..3a8ece6 100644 --- a/packages/component/examples/salon.ts +++ b/packages/component/examples/living-room.ts @@ -5,7 +5,7 @@ * the CLI check it before publishing: * * ```bash - * bun build examples/salon.ts --format=iife --minify --outfile dist/component.js + * bun build examples/living-room.ts --format=iife --minify --outfile dist/component.js * bunx @miakapp/cli check * ``` * @@ -36,7 +36,7 @@ defineComponent((home) => { } catch (error) { // An unknown outcome is deliberately not retried: the home may already // have applied it. The next state snapshot is the authority. - failure = error instanceof Error ? error.message : 'La commande a échoué'; + failure = error instanceof Error ? error.message : 'The command failed'; } finally { pending = false; home.invalidate(); @@ -45,21 +45,21 @@ defineComponent((home) => { return { render: () => { - const on = asBoolean(home.state.get('zone.salon.light.on')); - const temperature = asNumber(home.state.get('climate.salon.temperature'), 0); + const on = asBoolean(home.state.get('zone.living_room.light.on')); + const temperature = asNumber(home.state.get('climate.living_room.temperature'), 0); - return ui.screen({ title: 'Salon' }, [ - ui.section({ id: 'lights', heading: 'Lumières' }, [ + return ui.screen({ title: 'Living room' }, [ + ui.section({ id: 'lights', heading: 'Lights' }, [ ui.toggle({ - id: 'salon-light', - label: 'Lampe du salon', + id: 'living-room-light', + label: 'Living-room lamp', value: on, pending, onChange: (next) => void setLight(next), }), ui.status({ id: 'light-status', - label: 'État', + label: 'Status', state: home.state.stale ? 'stale' : failure !== undefined @@ -70,7 +70,7 @@ defineComponent((home) => { ...(failure === undefined ? {} : { detail: failure }), }), ]), - ui.section({ id: 'climate', heading: 'Climat' }, [ + ui.section({ id: 'climate', heading: 'Climate' }, [ ui.text({ id: 'temperature', text: `${temperature.toFixed(1)} °C`, @@ -79,8 +79,8 @@ defineComponent((home) => { ui.text({ id: 'temperature-note', text: home.state.stale - ? 'Valeur peut-être périmée, en attente d’un instantané.' - : `Relevé à la révision ${home.state.revision}.`, + ? 'Value may be stale; waiting for a snapshot.' + : `Reading at revision ${home.state.revision}.`, tone: home.state.stale ? 'warning' : 'muted', }), ]), diff --git a/packages/component/test/component.test.ts b/packages/component/test/component.test.ts index 74ee1f8..02f7ea1 100644 --- a/packages/component/test/component.test.ts +++ b/packages/component/test/component.test.ts @@ -28,7 +28,7 @@ function screenOf(node: unknown): UiNode { describe('handshake', () => { test('guest.ready is sent before any other work', () => { - const { broker } = mount(() => ({ render: () => ui.screen({ title: 'Salon' }) })); + const { broker } = mount(() => ({ render: () => ui.screen({ title: 'Living room' }) })); expect(broker.kinds()).toEqual(['guest.ready']); expect(broker.last('guest.ready').payload).toEqual({ abi: 'miakapp.component/1' }); }); @@ -37,7 +37,7 @@ describe('handshake', () => { let setups = 0; const { broker } = mount(() => { setups += 1; - return { render: () => ui.screen({ title: 'Salon' }) }; + return { render: () => ui.screen({ title: 'Living room' }) }; }); expect(setups).toBe(0); broker.boot(); @@ -45,13 +45,13 @@ describe('handshake', () => { }); test('the first render is revision one and its root is a screen', async () => { - const { broker, handle } = mount(() => ({ render: () => ui.screen({ title: 'Salon' }) })); + const { broker, handle } = mount(() => ({ render: () => ui.screen({ title: 'Living room' }) })); broker.boot(); await handle.ready; const render = broker.last('ui.render'); expect(render.payload['revision']).toBe(1); expect(screenOf(render.payload['tree']).type).toBe('screen'); - expect(screenOf(render.payload['tree']).props['title']).toBe('Salon'); + expect(screenOf(render.payload['tree']).props['title']).toBe('Living room'); }); test('render revisions are contiguous', () => { @@ -79,7 +79,7 @@ describe('state', () => { const { broker } = mount((home) => ({ render: () => { seen = home.state.get('zone.alpha.light.on'); - return ui.screen({ title: 'Salon' }); + return ui.screen({ title: 'Living room' }); }, })); broker.boot({}, { 'zone.alpha.light.on': false }); @@ -97,7 +97,7 @@ describe('state', () => { const { broker } = mount((home) => ({ render: () => { present = home.state.has('zone.alpha.light.on'); - return ui.screen({ title: 'Salon' }); + return ui.screen({ title: 'Living room' }); }, })); broker.boot({}, { 'zone.alpha.light.on': false }); @@ -114,7 +114,7 @@ describe('state', () => { const { broker } = mount((home) => ({ render: () => { stale = home.state.stale; - return ui.screen({ title: 'Salon' }); + return ui.screen({ title: 'Living room' }); }, })); broker.boot({}, { 'climate.zone.temperature': 19 }); @@ -130,7 +130,7 @@ describe('state', () => { render: () => { temperature = home.state.get('climate.zone.temperature'); stale = home.state.stale; - return ui.screen({ title: 'Salon' }); + return ui.screen({ title: 'Living room' }); }, })); broker.boot({}, { 'climate.zone.temperature': 19 }); @@ -150,10 +150,10 @@ describe('interaction', () => { test('a toggle handler receives the boolean and the tree recommits', () => { const received: boolean[] = []; const { broker } = mount(() => ({ - render: () => ui.screen({ title: 'Salon' }, [ + render: () => ui.screen({ title: 'Living room' }, [ ui.toggle({ id: 'lamp', - label: 'Lampe', + label: 'Lamp', value: false, onChange: (value) => void received.push(value), }), @@ -174,8 +174,8 @@ describe('interaction', () => { test('a button handler is called without a value', () => { let presses = 0; const { broker } = mount(() => ({ - render: () => ui.screen({ title: 'Salon' }, [ - ui.button({ id: 'go', label: 'Allumer', onPress: () => void (presses += 1) }), + render: () => ui.screen({ title: 'Living room' }, [ + ui.button({ id: 'go', label: 'Turn on', onPress: () => void (presses += 1) }), ]), })); broker.boot(); @@ -191,8 +191,8 @@ describe('interaction', () => { test('an interaction for a stale render is ignored', () => { let presses = 0; const { broker } = mount(() => ({ - render: () => ui.screen({ title: 'Salon' }, [ - ui.button({ id: 'go', label: 'Allumer', onPress: () => void (presses += 1) }), + render: () => ui.screen({ title: 'Living room' }, [ + ui.button({ id: 'go', label: 'Turn on', onPress: () => void (presses += 1) }), ]), })); broker.boot(); @@ -211,7 +211,7 @@ describe('calls', () => { let home: Home | undefined; const { broker } = mount((instance) => { home = instance; - return { render: () => ui.screen({ title: 'Salon' }) }; + return { render: () => ui.screen({ title: 'Living room' }) }; }); broker.boot(); const pending = (home as Home).call('lighting.set', { on: true }); @@ -228,7 +228,7 @@ describe('calls', () => { let home: Home | undefined; const { broker } = mount((instance) => { home = instance; - return { render: () => ui.screen({ title: 'Salon' }) }; + return { render: () => ui.screen({ title: 'Living room' }) }; }); broker.boot(); const pending = (home as Home).call('lighting.set', { on: true }); @@ -250,7 +250,7 @@ describe('calls', () => { let home: Home | undefined; const { broker } = mount((instance) => { home = instance; - return { render: () => ui.screen({ title: 'Salon' }) }; + return { render: () => ui.screen({ title: 'Living room' }) }; }); broker.boot(); const pending = (home as Home).call('lighting.set', { on: true }); @@ -263,7 +263,7 @@ describe('calls', () => { let home: Home | undefined; const { broker } = mount((instance) => { home = instance; - return { render: () => ui.screen({ title: 'Salon' }) }; + return { render: () => ui.screen({ title: 'Living room' }) }; }); broker.boot(); const stream = (home as Home).stream('lighting.set', null); @@ -291,7 +291,7 @@ describe('calls', () => { let home: Home | undefined; const { broker } = mount((instance) => { home = instance; - return { render: () => ui.screen({ title: 'Salon' }) }; + return { render: () => ui.screen({ title: 'Living room' }) }; }); broker.boot({ staging: true }); expect(broker.of('ui.render')).toHaveLength(1); @@ -306,7 +306,7 @@ describe('events', () => { let home: Home | undefined; const { broker } = mount((instance) => { home = instance; - return { render: () => ui.screen({ title: 'Salon' }) }; + return { render: () => ui.screen({ title: 'Living room' }) }; }); broker.boot(); const received: unknown[] = []; @@ -348,7 +348,7 @@ describe('lifecycle', () => { const { broker, handle } = mount((instance) => { home = instance; return { - render: () => ui.screen({ title: 'Salon' }), + render: () => ui.screen({ title: 'Living room' }), dispose: () => void (disposed = true), }; }); @@ -364,7 +364,7 @@ describe('lifecycle', () => { let home: Home | undefined; const { broker } = mount((instance) => { home = instance; - return { render: () => ui.screen({ title: 'Salon' }) }; + return { render: () => ui.screen({ title: 'Living room' }) }; }); broker.boot(); broker.deliver('lifecycle.dispose', {}); @@ -405,7 +405,7 @@ describe('render rate', () => { describe('bridge hygiene', () => { test('reserved runtime messages are ignored, never answered', () => { - const { broker } = mount(() => ({ render: () => ui.screen({ title: 'Salon' }) })); + const { broker } = mount(() => ({ render: () => ui.screen({ title: 'Living room' }) })); broker.boot(); const before = broker.sent.length; broker.deliver('runtime.probe', { challenge: 1 }); @@ -413,15 +413,15 @@ describe('bridge hygiene', () => { }); test('an unknown broker kind is ignored rather than fatal', () => { - const { broker } = mount(() => ({ render: () => ui.screen({ title: 'Salon' }) })); + const { broker } = mount(() => ({ render: () => ui.screen({ title: 'Living room' }) })); broker.boot(); expect(() => broker.deliver('ui.something_new', { a: 1 })).not.toThrow(); }); test('no message carries an undefined property', () => { const { broker } = mount(() => ({ - render: () => ui.screen({ title: 'Salon' }, [ - ui.button({ id: 'go', label: 'Allumer', onPress: () => undefined }), + render: () => ui.screen({ title: 'Living room' }, [ + ui.button({ id: 'go', label: 'Turn on', onPress: () => undefined }), ui.text({ id: 'hint', text: 'Sans ton ni emphase' }), ui.input({ id: 'name', label: 'Nom', value: '', onChange: () => undefined }), ]), diff --git a/packages/component/test/ui.test.ts b/packages/component/test/ui.test.ts index cade5b9..80146a7 100644 --- a/packages/component/test/ui.test.ts +++ b/packages/component/test/ui.test.ts @@ -3,9 +3,9 @@ import { LIMITS, UiError, checkTree, ui, type UiNode } from '../src/index.js'; describe('node construction', () => { test('an optional property is omitted rather than sent as undefined', () => { - expect(ui.text({ id: 'hint', text: 'Bonjour' }).props).toEqual({ text: 'Bonjour' }); - expect(ui.text({ id: 'hint', text: 'Bonjour', tone: 'muted' }).props) - .toEqual({ text: 'Bonjour', tone: 'muted' }); + expect(ui.text({ id: 'hint', text: 'Hello' }).props).toEqual({ text: 'Hello' }); + expect(ui.text({ id: 'hint', text: 'Hello', tone: 'muted' }).props) + .toEqual({ text: 'Hello', tone: 'muted' }); }); test('a stack with no options carries no property at all', () => { @@ -17,12 +17,12 @@ describe('node construction', () => { id: 'mode', label: 'Mode', value: 'eco', - options: [{ value: 'eco', label: 'Éco' }, { value: 'confort', label: 'Confort' }], + options: [{ value: 'eco', label: 'Eco' }, { value: 'comfort', label: 'Comfort' }], handler: 'mode', }); expect(node.props['options']).toEqual([ - { value: 'eco', label: 'Éco' }, - { value: 'confort', label: 'Confort' }, + { value: 'eco', label: 'Eco' }, + { value: 'comfort', label: 'Comfort' }, ]); }); @@ -34,16 +34,16 @@ describe('node construction', () => { }); test('an interactive node needs a handler', () => { - expect(() => ui.button({ id: 'go', label: 'Allumer' })).toThrow(/handler/); + expect(() => ui.button({ id: 'go', label: 'Turn on' })).toThrow(/handler/); }); test('a handler callback outside a render is refused with a usable message', () => { - expect(() => ui.button({ id: 'go', label: 'Allumer', onPress: () => undefined })) + expect(() => ui.button({ id: 'go', label: 'Turn on', onPress: () => undefined })) .toThrow(/outside a render/); }); test('a handler ID works without a render scope', () => { - expect(ui.button({ id: 'go', label: 'Allumer', handler: 'go' }).props['handler']).toBe('go'); + expect(ui.button({ id: 'go', label: 'Turn on', handler: 'go' }).props['handler']).toBe('go'); }); }); @@ -53,7 +53,7 @@ describe('tree invariants', () => { for (let level = depth; level > 0; level -= 1) { node = ui.stack({ id: `level${level}` }, [node]); } - return ui.screen({ title: 'Salon' }, [node]); + return ui.screen({ title: 'Living room' }, [node]); } test('the root must be a screen', () => { @@ -61,9 +61,9 @@ describe('tree invariants', () => { }); test('duplicate node IDs are caught before the broker terminates the instance', () => { - const tree = ui.screen({ title: 'Salon' }, [ - ui.text({ id: 'same', text: 'un' }), - ui.text({ id: 'same', text: 'deux' }), + const tree = ui.screen({ title: 'Living room' }, [ + ui.text({ id: 'same', text: 'one' }), + ui.text({ id: 'same', text: 'two' }), ]); expect(() => checkTree(tree)).toThrow(/Duplicate node ID: same/); }); @@ -78,11 +78,11 @@ describe('tree invariants', () => { { length: LIMITS.uiNodes }, (_unused, index) => ui.text({ id: `t${index}`, text: 'x' }), ); - expect(() => checkTree(ui.screen({ title: 'Salon' }, children))).toThrow(/more than/); + expect(() => checkTree(ui.screen({ title: 'Living room' }, children))).toThrow(/more than/); }); test('one oversized text is refused', () => { - const tree = ui.screen({ title: 'Salon' }, [ + const tree = ui.screen({ title: 'Living room' }, [ ui.text({ id: 'long', text: 'é'.repeat(LIMITS.textBytes) }), ]); expect(() => checkTree(tree)).toThrow(/exceeds 8192 UTF-8 bytes/); @@ -94,15 +94,15 @@ describe('tree invariants', () => { { length: Math.ceil(LIMITS.uiTextBytes / LIMITS.textBytes) }, (_unused, index) => ui.text({ id: `t${index}`, text: block }), ); - expect(() => checkTree(ui.screen({ title: 'Salon' }, children))) + expect(() => checkTree(ui.screen({ title: 'Living room' }, children))) .toThrow(/Aggregate UI text/); }); test('a valid tree is returned unchanged', () => { - const tree = ui.screen({ title: 'Salon' }, [ - ui.section({ id: 'lights', heading: 'Lumières' }, [ - ui.toggle({ id: 'lamp', label: 'Lampe', value: true, handler: 'lamp' }), - ui.progress({ id: 'dim', label: 'Intensité', value: 0.5 }), + const tree = ui.screen({ title: 'Living room' }, [ + ui.section({ id: 'lights', heading: 'Lights' }, [ + ui.toggle({ id: 'lamp', label: 'Lamp', value: true, handler: 'lamp' }), + ui.progress({ id: 'dim', label: 'Intensity', value: 0.5 }), ]), ]); expect(checkTree(tree)).toBe(tree); diff --git a/templates/home/README.md b/templates/home/README.md index eb4fa31..890739b 100644 --- a/templates/home/README.md +++ b/templates/home/README.md @@ -38,7 +38,7 @@ nothing and catches the artifact rules the runtime would reject anyway. To run the coordinator against a real home: ```bash -export MIAKAPP_COORDINATOR_NAME=salon +export MIAKAPP_COORDINATOR_NAME=living-room export MIAKAPP_CONTROL_PLANE_EXCHANGE_ENDPOINT=https://control.miakapp.app/v1/access-tokens:exchange export MIAKAPP_OWNER_USER_ID= export MIAKAPP_HOME_KEY="$(your-secret-manager read miakapp/home-key)" diff --git a/templates/home/component/main.ts b/templates/home/component/main.ts index b79734c..69cb600 100644 --- a/templates/home/component/main.ts +++ b/templates/home/component/main.ts @@ -9,10 +9,10 @@ */ import { defineComponent, ui, type StructuredValue } from '@miakapp/component'; -const LIGHT_ON = 'zone.salon.light.on'; -const TEMPERATURE = 'climate.salon.temperature'; +const LIGHT_ON = 'zone.living_room.light.on'; +const TEMPERATURE = 'climate.living_room.temperature'; const HEALTH = 'service.coordinator.health'; -const LIGHT_CHANGED = 'zone.salon.light.changed'; +const LIGHT_CHANGED = 'zone.living_room.light.changed'; function asBoolean(value: StructuredValue | undefined): boolean { return value === true; @@ -40,7 +40,7 @@ defineComponent((home) => { } catch (error) { // Deliberately not retried. The call may already have reached the lamp, // and the next state snapshot settles the question. - failure = error instanceof Error ? error.message : 'La commande a échoué'; + failure = error instanceof Error ? error.message : 'The command failed'; } finally { pending = false; home.invalidate(); @@ -58,11 +58,11 @@ defineComponent((home) => { const temperature = asNumber(home.state.get(TEMPERATURE)); const healthy = home.state.get(HEALTH) === 'healthy'; - return ui.screen({ title: 'Salon' }, [ - ui.section({ id: 'lights', heading: 'Lumières' }, [ + return ui.screen({ title: 'Living room' }, [ + ui.section({ id: 'lights', heading: 'Lights' }, [ ui.toggle({ - id: 'salon-light', - label: 'Lampe du salon', + id: 'living-room-light', + label: 'Living-room lamp', value: asBoolean(home.state.get(LIGHT_ON)), disabled: home.staging || !healthy, pending, @@ -70,15 +70,15 @@ defineComponent((home) => { }), ui.status({ id: 'light-status', - label: 'État', + label: 'Status', state: lightState(), ...(failure === undefined ? {} : { detail: failure }), }), ]), - ui.section({ id: 'climate', heading: 'Climat' }, [ + ui.section({ id: 'climate', heading: 'Climate' }, [ temperature === undefined - ? ui.text({ id: 'temperature', text: 'Température indisponible', tone: 'muted' }) + ? ui.text({ id: 'temperature', text: 'Temperature unavailable', tone: 'muted' }) : ui.text({ id: 'temperature', text: `${temperature.toFixed(1)} °C`, @@ -87,8 +87,8 @@ defineComponent((home) => { ui.text({ id: 'temperature-note', text: home.state.stale - ? 'Valeur peut-être périmée, en attente d’un instantané.' - : `Relevé à la révision ${home.state.revision}.`, + ? 'Value may be stale; waiting for a snapshot.' + : `Reading at revision ${home.state.revision}.`, tone: home.state.stale ? 'warning' : 'muted', }), ]), @@ -96,7 +96,7 @@ defineComponent((home) => { ...(home.staging ? [ui.text({ id: 'staging-note', - text: 'Version en pré-activation : l’affichage fonctionne, les commandes non.', + text: 'Pre-activation version: display works; controls do not.', tone: 'warning', })] : []), diff --git a/templates/home/coordinator/home.ts b/templates/home/coordinator/home.ts index bfb77b2..409d7eb 100644 --- a/templates/home/coordinator/home.ts +++ b/templates/home/coordinator/home.ts @@ -13,12 +13,12 @@ import { } from 'miakapi'; export const STATE = { - lightOn: 'zone.salon.light.on', - temperature: 'climate.salon.temperature', + lightOn: 'zone.living_room.light.on', + temperature: 'climate.living_room.temperature', health: 'service.coordinator.health', } as const; -export const EVENT_LIGHT_CHANGED = 'zone.salon.light.changed'; +export const EVENT_LIGHT_CHANGED = 'zone.living_room.light.changed'; export interface HomeOptions { /** Firebase UID of the person who may see and drive this home. */ @@ -53,7 +53,7 @@ export function createHomeConfiguration(options: HomeOptions): CoordinatorConfig // miakapp.yaml can never exceed it. stateAccess: [{ userId: options.ownerUserId, - patterns: ['climate.salon.*', 'service.coordinator.health', 'zone.salon.*'], + patterns: ['climate.living_room.*', 'service.coordinator.health', 'zone.living_room.*'], }], events: [{ topic: EVENT_LIGHT_CHANGED, directions: EventDirection.publishToUsers }], diff --git a/templates/home/miakapp.yaml b/templates/home/miakapp.yaml index b7400cb..31f5e77 100644 --- a/templates/home/miakapp.yaml +++ b/templates/home/miakapp.yaml @@ -10,11 +10,11 @@ component: # so every name here must also be covered there. requires: state_read: - - climate.salon.temperature + - climate.living_room.temperature - service.coordinator.health - - zone.salon.light.on + - zone.living_room.light.on event_subscribe: - - zone.salon.light.changed + - zone.living_room.light.changed event_publish: [] call: - lighting.set diff --git a/templates/home/test/home.test.ts b/templates/home/test/home.test.ts index f8dc2f9..0fa4aec 100644 --- a/templates/home/test/home.test.ts +++ b/templates/home/test/home.test.ts @@ -44,9 +44,9 @@ describe('declarations', () => { // Mirrors miakapp.yaml requires.state_read. If one moves, this test fails // before the relay silently withholds a path the interface expects. const required = [ - 'climate.salon.temperature', + 'climate.living_room.temperature', 'service.coordinator.health', - 'zone.salon.light.on', + 'zone.living_room.light.on', ]; for (const path of required) { const covered = granted.some((pattern) => pattern.endsWith('.*')