openvibers/openvibe.design Commits
The controls rule says plain Stop (#299)
Changes
@@@ -1,5 +1,10 @@# Changelog++++## Unreleased — a plain Stop++++- The controls rule: Stop is one plain little button a person may add, not a red-ringed disc that is always there.++What changed in OpenVibe.Design, newest first. Each site also publishes its patch notes at /updates.## 0.1.0 — 2026-10-28@@@ -440,7 +440,7 @@ const RULES = Object.freeze(["It follows the finger or the mouse each frame and lands with a spring; dropped over the player it sits in the player, anywhere else it floats over the page and stays on screen while it scrolls; a flick carries on; near an edge it snaps. Thrown past a side it tucks into it, a tab with its picture still going. While it connects or its picture stops it shows the mark at work over its last frame (never a black box) and tries again by itself. Its place is kept per slot, in the browser and in the account (newest wins).",'2026-11-11', "The owner: \"move it anywhere … on mobile and desktop … more solid feeling … that black thing that’s just stuck there … use the OV animations … saved and synced to the OpenVibe account.\""),R("controls-one-family", "products", "A device’s controls are one family, arranged per person.",-- "The stick has its four marks and lights when it drives; Stop is a red-ringed disc beside it; a slider says its value with − and + to step it; battery and latency are slim readout rows. Each person arranges the pad (the Stop’s hand, the pad’s size, the readouts shown or folded) and the arrangement works while the device is offline. A device that is offline says so in a sentence.",++ "The stick has its four marks and lights when it drives; Stop is one plain little button a person may add; a slider says its value with − and + to step it; battery and latency are slim readout rows. Each person arranges the pad (the pad’s size, the readouts shown or folded, and whether Stop is there) and the arrangement works while the device is offline. A device that is offline says so in a sentence.",'2026-11-11', "The owner, circling the pad, the Stop, the head slider and the readouts: \"redesign … polish … more consistent … on a per user basis.\""),R("ai-text-sounds-like-a-person", "agents", "AI text about a person reads as a person wrote it.","A card line is what a friend who watches would say in one breath: one sentence, specific to this channel, starting with the thing itself (not \"Streams\", \"A channel\", \"Join\"), without the name the card already shows. The stock words that make every channel sound the same stay out: focus, content, engaging, various, a mix of, dive into, showcasing, vibrant, dynamic, journey, immersive, a blend of. A line that fails falls back to the overview reshaped.",@@@ -39,7 +39,7 @@ const report = {],comparison: { columns: ['Site', 'Density'], rows: [['acme.example', 'dense']] },best: { source_id: 'src_acme', what: 'The Stop disc stays in the same place at every size.', evidence: [ev('https://acme.example/controls', 'acme.example')] },-- mistakes: [{ what: 'A menu hides the emergency stop.', evidence: [ev('https://acme.example/controls', 'acme.example')] }],++ mistakes: [{ what: 'A menu hides the Stop button.', evidence: [ev('https://acme.example/controls', 'acme.example')] }],gap: 'No one shows the robot’s state beside the controls.',reference_tokens: {palettes: [{ source_id: 'src_acme', colours: ['#0b0f19', '#e6edf7', '#e5484d'] }],nnew file mode 100644@@@ -0,0 +1,26 @@++'use strict';++// These are small hobby robots: no page, doc or AGENTS.md says E-STOP, estop or emergency stop. Inline code++// names a legacy protocol field and is skipped.++const assert = require('assert');++const fs = require('fs');++const path = require('path');++const root = path.join(__dirname, '..');++const files = ['AGENTS.md', 'README.md', 'STATUS.json'].map((f) => path.join(root, f)).filter((f) => fs.existsSync(f));++const walk = (dir, ext) => {++ if (!fs.existsSync(dir)) return;++ for (const name of fs.readdirSync(dir)) {++ const p = path.join(dir, name);++ if (fs.statSync(p).isDirectory()) walk(p, ext);++ else if (p.endsWith(ext)) files.push(p);++ }++};++walk(path.join(root, 'docs'), '.md');++walk(path.join(root, 'public', 'fragments'), '.html');++const bad = /(^|[^A-Za-z])e-?stop([^A-Za-z]|$)|emergency\s+stop/i;++for (const file of files) {++ let text = fs.readFileSync(file, 'utf8');++ text = text.replace(/```[\s\S]*?```/g, '').replace(/`[^`]*`/g, '')++ .replace(/<pre[\s\S]*?<\/pre>/gi, '').replace(/<code[\s\S]*?<\/code>/gi, '');++ assert.ok(!bad.test(text), `${path.relative(root, file)} says E-STOP; write Stop`);++}++console.log('plain-stop-text: no page, doc or AGENTS.md says E-STOP');
@@ -1,5 +1,10 @@
@@ -1,5 +1,10 @@
# Changelog
# Changelog
+
+## Unreleased — a plain Stop
+
+- The controls rule: Stop is one plain little button a person may add, not a red-ringed disc that is always there.
+
What changed in OpenVibe.Design, newest first. Each site also publishes its patch notes at /updates.
What changed in OpenVibe.Design, newest first. Each site also publishes its patch notes at /updates.
## 0.1.0 — 2026-10-28
## 0.1.0 — 2026-10-28
@@ -440,7 +440,7 @@ const RULES = Object.freeze([
@@ -440,7 +440,7 @@ const RULES = Object.freeze([
"It follows the finger or the mouse each frame and lands with a spring; dropped over the player it sits in the player, anywhere else it floats over the page and stays on screen while it scrolls; a flick carries on; near an edge it snaps. Thrown past a side it tucks into it, a tab with its picture still going. While it connects or its picture stops it shows the mark at work over its last frame (never a black box) and tries again by itself. Its place is kept per slot, in the browser and in the account (newest wins).",
"It follows the finger or the mouse each frame and lands with a spring; dropped over the player it sits in the player, anywhere else it floats over the page and stays on screen while it scrolls; a flick carries on; near an edge it snaps. Thrown past a side it tucks into it, a tab with its picture still going. While it connects or its picture stops it shows the mark at work over its last frame (never a black box) and tries again by itself. Its place is kept per slot, in the browser and in the account (newest wins).",
'2026-11-11', "The owner: \"move it anywhere … on mobile and desktop … more solid feeling … that black thing that’s just stuck there … use the OV animations … saved and synced to the OpenVibe account.\""),
'2026-11-11', "The owner: \"move it anywhere … on mobile and desktop … more solid feeling … that black thing that’s just stuck there … use the OV animations … saved and synced to the OpenVibe account.\""),
R("controls-one-family", "products", "A device’s controls are one family, arranged per person.",
R("controls-one-family", "products", "A device’s controls are one family, arranged per person.",
- "The stick has its four marks and lights when it drives; Stop is a red-ringed disc beside it; a slider says its value with − and + to step it; battery and latency are slim readout rows. Each person arranges the pad (the Stop’s hand, the pad’s size, the readouts shown or folded) and the arrangement works while the device is offline. A device that is offline says so in a sentence.",
+ "The stick has its four marks and lights when it drives; Stop is one plain little button a person may add; a slider says its value with − and + to step it; battery and latency are slim readout rows. Each person arranges the pad (the pad’s size, the readouts shown or folded, and whether Stop is there) and the arrangement works while the device is offline. A device that is offline says so in a sentence.",
'2026-11-11', "The owner, circling the pad, the Stop, the head slider and the readouts: \"redesign … polish … more consistent … on a per user basis.\""),
'2026-11-11', "The owner, circling the pad, the Stop, the head slider and the readouts: \"redesign … polish … more consistent … on a per user basis.\""),
R("ai-text-sounds-like-a-person", "agents", "AI text about a person reads as a person wrote it.",
R("ai-text-sounds-like-a-person", "agents", "AI text about a person reads as a person wrote it.",
"A card line is what a friend who watches would say in one breath: one sentence, specific to this channel, starting with the thing itself (not \"Streams\", \"A channel\", \"Join\"), without the name the card already shows. The stock words that make every channel sound the same stay out: focus, content, engaging, various, a mix of, dive into, showcasing, vibrant, dynamic, journey, immersive, a blend of. A line that fails falls back to the overview reshaped.",
"A card line is what a friend who watches would say in one breath: one sentence, specific to this channel, starting with the thing itself (not \"Streams\", \"A channel\", \"Join\"), without the name the card already shows. The stock words that make every channel sound the same stay out: focus, content, engaging, various, a mix of, dive into, showcasing, vibrant, dynamic, journey, immersive, a blend of. A line that fails falls back to the overview reshaped.",
@@ -39,7 +39,7 @@ const report = {
@@ -39,7 +39,7 @@ const report = {
],
],
comparison: { columns: ['Site', 'Density'], rows: [['acme.example', 'dense']] },
comparison: { columns: ['Site', 'Density'], rows: [['acme.example', 'dense']] },
best: { source_id: 'src_acme', what: 'The Stop disc stays in the same place at every size.', evidence: [ev('https://acme.example/controls', 'acme.example')] },
best: { source_id: 'src_acme', what: 'The Stop disc stays in the same place at every size.', evidence: [ev('https://acme.example/controls', 'acme.example')] },
- mistakes: [{ what: 'A menu hides the emergency stop.', evidence: [ev('https://acme.example/controls', 'acme.example')] }],
+ mistakes: [{ what: 'A menu hides the Stop button.', evidence: [ev('https://acme.example/controls', 'acme.example')] }],
gap: 'No one shows the robot’s state beside the controls.',
gap: 'No one shows the robot’s state beside the controls.',
reference_tokens: {
reference_tokens: {
palettes: [{ source_id: 'src_acme', colours: ['#0b0f19', '#e6edf7', '#e5484d'] }],
palettes: [{ source_id: 'src_acme', colours: ['#0b0f19', '#e6edf7', '#e5484d'] }],
new file mode 100644
new file mode 100644
@@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
+'use strict';
+// These are small hobby robots: no page, doc or AGENTS.md says E-STOP, estop or emergency stop. Inline code
+// names a legacy protocol field and is skipped.
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+const root = path.join(__dirname, '..');
+const files = ['AGENTS.md', 'README.md', 'STATUS.json'].map((f) => path.join(root, f)).filter((f) => fs.existsSync(f));
+const walk = (dir, ext) => {
+ if (!fs.existsSync(dir)) return;
+ for (const name of fs.readdirSync(dir)) {
+ const p = path.join(dir, name);
+ if (fs.statSync(p).isDirectory()) walk(p, ext);
+ else if (p.endsWith(ext)) files.push(p);
+ }
+};
+walk(path.join(root, 'docs'), '.md');
+walk(path.join(root, 'public', 'fragments'), '.html');
+const bad = /(^|[^A-Za-z])e-?stop([^A-Za-z]|$)|emergency\s+stop/i;
+for (const file of files) {
+ let text = fs.readFileSync(file, 'utf8');
+ text = text.replace(/```[\s\S]*?```/g, '').replace(/`[^`]*`/g, '')
+ .replace(/<pre[\s\S]*?<\/pre>/gi, '').replace(/<code[\s\S]*?<\/code>/gi, '');
+ assert.ok(!bad.test(text), `${path.relative(root, file)} says E-STOP; write Stop`);
+}
+console.log('plain-stop-text: no page, doc or AGENTS.md says E-STOP');