openvibers/openvibe.design Commits
openvibe-contracts v0.416.0 (was v0.406.0) (#298)
Changes
@@@ -232,7 +232,7 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by[OpenVibers](https://github.com/OpenVibers).<!-- versions:start -->--- openvibe-contracts: v0.406.0++- openvibe-contracts: v0.416.0- openvibe-sdk: v0.150.0- openvibe-shared: v4.223.0- openvibe-publishing: v1.7.0@@@ -8,11 +8,11 @@"runtime": "node 22, express 4, openvibe-shared/shell; PostgreSQL (or embedded PGlite in development), OpenVibe.AI generate jobs, OpenVibe.Events account data in, no Valkey","port": "5090","domain": "openvibe.design",-- "contracts": "openvibe-contracts v0.406.0",++ "contracts": "openvibe-contracts v0.416.0","sdk": "openvibe-sdk v0.150.0","shared": "openvibe-shared v4.223.0","packages": [-- "openvibe-contracts v0.406.0",++ "openvibe-contracts v0.416.0","openvibe-publishing v1.7.0","openvibe-sdk v0.150.0","openvibe-shared v4.223.0"@@@ -16,7 +16,7 @@"express-rate-limit": "^7.5.0","helmet": "^8.1.0","opentype.js": "^2.0.0",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.406.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.416.0","openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.7.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.150.0","openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.223.0",@@@ -1414,9 +1414,9 @@}},"node_modules/openvibe-contracts": {-- "version": "0.406.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.406.0",-- "integrity": "sha512-V/Tjss2UWvzI551bw3ACb/RqWg0lChxGITxfCVZ8qou2Docg0pBMvIjkeQoib56yq3r6UZGB6DGl+pA+qfjOiA==",++ "version": "0.416.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.416.0",++ "integrity": "sha512-gFZGSXY7GM2SHdyOmqZ0699yJhF9ZsF/Rb0WHo3asI+S4LTqthqIQPOCoYczlF/dcB5p13KBjLCMdAvEqmNLoQ==","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -22,7 +22,7 @@"express-rate-limit": "^7.5.0","helmet": "^8.1.0","opentype.js": "^2.0.0",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.406.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.416.0","openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.7.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.150.0","openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.223.0",@@@ -40,9 +40,9 @@ function looiPack() {el('Look up', 'Tilt the head up.', button('Look up'), { kind: 'robot.head_preset', product: 'bot', preset: 'up' }),el('Look down', 'Tilt the head down.', button('Look down'), { kind: 'robot.head_preset', product: 'bot', preset: 'down' }),el('Look level', 'Bring the head back to level.', button('Look level'), { kind: 'robot.head_preset', product: 'bot', preset: 'center' }),-- el('Say hello', 'The robot says hello to chat.', button('Say hello'), { kind: 'robot.say', product: 'bot', text: 'Hello, chat!', voice: 'default' }),-- el('Say nice', 'The robot says something nice.', button('Say nice'), { kind: 'robot.say', product: 'bot', text: 'You are doing great.', voice: 'default' }),-- el('Say bye', 'The robot says goodbye.', button('Say bye'), { kind: 'robot.say', product: 'bot', text: 'See you next stream!', voice: 'default' }),++ el('Say hello', 'The robot says hello to chat.', button('Say hello'), { kind: 'robot.say', product: 'bot', text: 'Hello, chat!' }),++ el('Say nice', 'The robot says something nice.', button('Say nice'), { kind: 'robot.say', product: 'bot', text: 'You are doing great.' }),++ el('Say bye', 'The robot says goodbye.', button('Say bye'), { kind: 'robot.say', product: 'bot', text: 'See you next stream!' }),el('Headlights on', 'Turn the headlights on.', { component: 'toggle', label: 'Headlights on', icon: 'spark', props: { on: true }, style: { tint: 'accent', shape: 'pill', opacity: 1, show_label: true } }, { kind: 'robot.headlight', product: 'bot', on: true }),el('Headlights off', 'Turn the headlights off.', { component: 'toggle', label: 'Headlights off', icon: 'spark', props: { on: false }, style: { tint: 'surface', shape: 'pill', opacity: 1, show_label: true } }, { kind: 'robot.headlight', product: 'bot', on: false }),],@@@ -17,6 +17,21 @@ const { clean } = require('../server/typefaces/glyph');const { compile } = require('../server/typefaces/compile');const { sanitize } = require('../server/vectors/svg');++/**++ * Compile with the clock frozen. opentype.js stamps the font's head table with the build time when it writes++ * the file, so two compiles either side of a second would differ in those bytes alone; freezing the clock++ * makes the determinism check about the glyphs, the metrics and the names, which is what must not change.++ */++async function compileAt(fixedNow, input) {++ const RealDate = Date;++ class FrozenDate extends RealDate {++ constructor(...args) { super(...(args.length ? args : [fixedNow])); }++ static now() { return fixedNow; }++ }++ global.Date = FrozenDate;++ try { return await compile(input); } finally { global.Date = RealDate; }++}++/** The letters the fake model draws. Every one of them is a closed path inside the em box. */const LETTERS = {A: 'M100 0 L300 700 L500 0 L420 0 L340 180 L160 180 L80 0 Z',@@@ -115,8 +130,8 @@ function fakeAi(overrides = {}) {// ── The compiler, on fixed fixtures ──await check('the same glyphs always compile to the same font', async () => {const glyphs = Object.entries(LETTERS).map(([char, d]) => ({ char, d }));-- const first = await compile({ familyName: 'Fixture Sans', glyphs, model: 'claude-opus-5-5', designer: 'Made on OpenVibe.Design' });-- const second = await compile({ familyName: 'Fixture Sans', glyphs, model: 'claude-opus-5-5', designer: 'Made on OpenVibe.Design' });++ const first = await compileAt(Date.UTC(2026, 10, 12), { familyName: 'Fixture Sans', glyphs, model: 'claude-opus-5-5', designer: 'Made on OpenVibe.Design' });++ const second = await compileAt(Date.UTC(2026, 10, 12), { familyName: 'Fixture Sans', glyphs, model: 'claude-opus-5-5', designer: 'Made on OpenVibe.Design' });assert.ok(first.ok, first.why);assert.ok(second.ok);assert.deepEqual(first.otf, second.otf);
@@ -232,7 +232,7 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by
@@ -232,7 +232,7 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by
[OpenVibers](https://github.com/OpenVibers).
[OpenVibers](https://github.com/OpenVibers).
<!-- versions:start -->
<!-- versions:start -->
-- openvibe-contracts: v0.406.0
+- openvibe-contracts: v0.416.0
- openvibe-sdk: v0.150.0
- openvibe-sdk: v0.150.0
- openvibe-shared: v4.223.0
- openvibe-shared: v4.223.0
- openvibe-publishing: v1.7.0
- openvibe-publishing: v1.7.0
@@ -8,11 +8,11 @@
@@ -8,11 +8,11 @@
"runtime": "node 22, express 4, openvibe-shared/shell; PostgreSQL (or embedded PGlite in development), OpenVibe.AI generate jobs, OpenVibe.Events account data in, no Valkey",
"runtime": "node 22, express 4, openvibe-shared/shell; PostgreSQL (or embedded PGlite in development), OpenVibe.AI generate jobs, OpenVibe.Events account data in, no Valkey",
"port": "5090",
"port": "5090",
"domain": "openvibe.design",
"domain": "openvibe.design",
- "contracts": "openvibe-contracts v0.406.0",
+ "contracts": "openvibe-contracts v0.416.0",
"sdk": "openvibe-sdk v0.150.0",
"sdk": "openvibe-sdk v0.150.0",
"shared": "openvibe-shared v4.223.0",
"shared": "openvibe-shared v4.223.0",
"packages": [
"packages": [
- "openvibe-contracts v0.406.0",
+ "openvibe-contracts v0.416.0",
"openvibe-publishing v1.7.0",
"openvibe-publishing v1.7.0",
"openvibe-sdk v0.150.0",
"openvibe-sdk v0.150.0",
"openvibe-shared v4.223.0"
"openvibe-shared v4.223.0"
@@ -16,7 +16,7 @@
@@ -16,7 +16,7 @@
"express-rate-limit": "^7.5.0",
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"helmet": "^8.1.0",
"opentype.js": "^2.0.0",
"opentype.js": "^2.0.0",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.406.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.416.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.7.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.7.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.150.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.150.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.223.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.223.0",
@@ -1414,9 +1414,9 @@
@@ -1414,9 +1414,9 @@
}
}
},
},
"node_modules/openvibe-contracts": {
"node_modules/openvibe-contracts": {
- "version": "0.406.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.406.0",
- "integrity": "sha512-V/Tjss2UWvzI551bw3ACb/RqWg0lChxGITxfCVZ8qou2Docg0pBMvIjkeQoib56yq3r6UZGB6DGl+pA+qfjOiA==",
+ "version": "0.416.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.416.0",
+ "integrity": "sha512-gFZGSXY7GM2SHdyOmqZ0699yJhF9ZsF/Rb0WHo3asI+S4LTqthqIQPOCoYczlF/dcB5p13KBjLCMdAvEqmNLoQ==",
"license": "AGPL-3.0-only",
"license": "AGPL-3.0-only",
"dependencies": {
"dependencies": {
"ajv": "^8.17.1",
"ajv": "^8.17.1",
@@ -22,7 +22,7 @@
@@ -22,7 +22,7 @@
"express-rate-limit": "^7.5.0",
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"helmet": "^8.1.0",
"opentype.js": "^2.0.0",
"opentype.js": "^2.0.0",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.406.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.416.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.7.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.7.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.150.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.150.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.223.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.223.0",
@@ -40,9 +40,9 @@ function looiPack() {
@@ -40,9 +40,9 @@ function looiPack() {
el('Look up', 'Tilt the head up.', button('Look up'), { kind: 'robot.head_preset', product: 'bot', preset: 'up' }),
el('Look up', 'Tilt the head up.', button('Look up'), { kind: 'robot.head_preset', product: 'bot', preset: 'up' }),
el('Look down', 'Tilt the head down.', button('Look down'), { kind: 'robot.head_preset', product: 'bot', preset: 'down' }),
el('Look down', 'Tilt the head down.', button('Look down'), { kind: 'robot.head_preset', product: 'bot', preset: 'down' }),
el('Look level', 'Bring the head back to level.', button('Look level'), { kind: 'robot.head_preset', product: 'bot', preset: 'center' }),
el('Look level', 'Bring the head back to level.', button('Look level'), { kind: 'robot.head_preset', product: 'bot', preset: 'center' }),
- el('Say hello', 'The robot says hello to chat.', button('Say hello'), { kind: 'robot.say', product: 'bot', text: 'Hello, chat!', voice: 'default' }),
- el('Say nice', 'The robot says something nice.', button('Say nice'), { kind: 'robot.say', product: 'bot', text: 'You are doing great.', voice: 'default' }),
- el('Say bye', 'The robot says goodbye.', button('Say bye'), { kind: 'robot.say', product: 'bot', text: 'See you next stream!', voice: 'default' }),
+ el('Say hello', 'The robot says hello to chat.', button('Say hello'), { kind: 'robot.say', product: 'bot', text: 'Hello, chat!' }),
+ el('Say nice', 'The robot says something nice.', button('Say nice'), { kind: 'robot.say', product: 'bot', text: 'You are doing great.' }),
+ el('Say bye', 'The robot says goodbye.', button('Say bye'), { kind: 'robot.say', product: 'bot', text: 'See you next stream!' }),
el('Headlights on', 'Turn the headlights on.', { component: 'toggle', label: 'Headlights on', icon: 'spark', props: { on: true }, style: { tint: 'accent', shape: 'pill', opacity: 1, show_label: true } }, { kind: 'robot.headlight', product: 'bot', on: true }),
el('Headlights on', 'Turn the headlights on.', { component: 'toggle', label: 'Headlights on', icon: 'spark', props: { on: true }, style: { tint: 'accent', shape: 'pill', opacity: 1, show_label: true } }, { kind: 'robot.headlight', product: 'bot', on: true }),
el('Headlights off', 'Turn the headlights off.', { component: 'toggle', label: 'Headlights off', icon: 'spark', props: { on: false }, style: { tint: 'surface', shape: 'pill', opacity: 1, show_label: true } }, { kind: 'robot.headlight', product: 'bot', on: false }),
el('Headlights off', 'Turn the headlights off.', { component: 'toggle', label: 'Headlights off', icon: 'spark', props: { on: false }, style: { tint: 'surface', shape: 'pill', opacity: 1, show_label: true } }, { kind: 'robot.headlight', product: 'bot', on: false }),
],
],
@@ -17,6 +17,21 @@ const { clean } = require('../server/typefaces/glyph');
@@ -17,6 +17,21 @@ const { clean } = require('../server/typefaces/glyph');
const { compile } = require('../server/typefaces/compile');
const { compile } = require('../server/typefaces/compile');
const { sanitize } = require('../server/vectors/svg');
const { sanitize } = require('../server/vectors/svg');
+/**
+ * Compile with the clock frozen. opentype.js stamps the font's head table with the build time when it writes
+ * the file, so two compiles either side of a second would differ in those bytes alone; freezing the clock
+ * makes the determinism check about the glyphs, the metrics and the names, which is what must not change.
+ */
+async function compileAt(fixedNow, input) {
+ const RealDate = Date;
+ class FrozenDate extends RealDate {
+ constructor(...args) { super(...(args.length ? args : [fixedNow])); }
+ static now() { return fixedNow; }
+ }
+ global.Date = FrozenDate;
+ try { return await compile(input); } finally { global.Date = RealDate; }
+}
+
/** The letters the fake model draws. Every one of them is a closed path inside the em box. */
/** The letters the fake model draws. Every one of them is a closed path inside the em box. */
const LETTERS = {
const LETTERS = {
A: 'M100 0 L300 700 L500 0 L420 0 L340 180 L160 180 L80 0 Z',
A: 'M100 0 L300 700 L500 0 L420 0 L340 180 L160 180 L80 0 Z',
@@ -115,8 +130,8 @@ function fakeAi(overrides = {}) {
@@ -115,8 +130,8 @@ function fakeAi(overrides = {}) {
// ── The compiler, on fixed fixtures ──
// ── The compiler, on fixed fixtures ──
await check('the same glyphs always compile to the same font', async () => {
await check('the same glyphs always compile to the same font', async () => {
const glyphs = Object.entries(LETTERS).map(([char, d]) => ({ char, d }));
const glyphs = Object.entries(LETTERS).map(([char, d]) => ({ char, d }));
- const first = await compile({ familyName: 'Fixture Sans', glyphs, model: 'claude-opus-5-5', designer: 'Made on OpenVibe.Design' });
- const second = await compile({ familyName: 'Fixture Sans', glyphs, model: 'claude-opus-5-5', designer: 'Made on OpenVibe.Design' });
+ const first = await compileAt(Date.UTC(2026, 10, 12), { familyName: 'Fixture Sans', glyphs, model: 'claude-opus-5-5', designer: 'Made on OpenVibe.Design' });
+ const second = await compileAt(Date.UTC(2026, 10, 12), { familyName: 'Fixture Sans', glyphs, model: 'claude-opus-5-5', designer: 'Made on OpenVibe.Design' });
assert.ok(first.ok, first.why);
assert.ok(first.ok, first.why);
assert.ok(second.ok);
assert.ok(second.ok);
assert.deepEqual(first.otf, second.otf);
assert.deepEqual(first.otf, second.otf);