openvibers/openvibe.design Commits
Patterns: Agents (ask your agent, levels with a floor, the agent's line) (#181)
Changes
@@@ -99,6 +99,28 @@.cr-demo li{display:grid;gap:6px;padding:8px;border-radius:10px;background:var(--bg-secondary);min-width:0;font-size:12.5px;overflow-wrap:anywhere}.cr-thumb{display:grid;place-items:center;aspect-ratio:4/3;border-radius:8px;background:color-mix(in srgb,var(--accent) 14%,transparent);font-weight:800;font-size:11px}.cr-demo.rows{grid-template-columns:minmax(0,1fr);gap:0}.cr-demo.rows li{grid-template-columns:40px minmax(0,1fr);align-items:center;border-radius:0;background:none;border-bottom:1px solid var(--border)}.cr-demo.rows .cr-thumb{aspect-ratio:1}++.ask-demo{display:flex;gap:8px;align-items:center;padding:10px}++.ask-share{display:inline-flex;align-items:center;min-height:36px;padding:0 14px;border-radius:999px;border:1px solid var(--border)}++.ask-chip{min-height:36px;padding:0 14px;border-radius:999px;border:1px solid color-mix(in srgb,var(--accent) 45%,var(--border));background:color-mix(in srgb,var(--accent) 10%,transparent);color:inherit;font:inherit;font-weight:700;cursor:pointer}++.ask-dock{display:none;gap:8px;margin:0 10px 10px;padding:10px;border-radius:14px;background:var(--bg-secondary)}++.ask-dock.open{display:grid}++.ask-box{width:100%;box-sizing:border-box;font:inherit;padding:8px 10px;border-radius:10px;border:1px solid var(--border);background:var(--bg-primary);color:inherit}++.lv-demo{display:grid;gap:6px;padding:10px}++.lv-card{display:grid;grid-template-columns:auto 1fr;column-gap:10px;padding:9px 12px;border:1px solid var(--border);border-radius:12px;cursor:pointer}++.lv-card input{grid-row:span 2;margin-top:3px}++.lv-card span{grid-column:2;font-size:13px;color:var(--text-secondary)}++.lv-card:has(input:checked){border-color:var(--accent);background:color-mix(in srgb,var(--accent) 9%,transparent)}++.lv-floor{margin:0 10px 10px;padding:9px 12px;border-radius:10px;font-size:13px;background:color-mix(in srgb,var(--text-primary) 6%,transparent)}++.lv-floor.gone{display:none}++.line-demo{display:flex;align-items:center;gap:10px;margin:10px;padding:8px 12px;border-radius:10px;border:1px solid var(--border);font-weight:650}++.line-demo i{width:9px;height:9px;border-radius:50%;background:var(--accent);flex:none}++.line-demo[data-state=waiting]{border-color:#f59e0b}.line-demo[data-state=waiting] i{background:#f59e0b}++.line-demo[data-state=done] i{background:#22c55e}.line-demo[data-state=failed] i{background:#ef4444}++.line-act{margin-left:auto;font-size:13px;color:var(--text-secondary)}++.line-work{position:relative;display:grid;place-items:center;height:110px;margin:0 10px 10px;border-radius:12px;background:var(--bg-secondary);color:var(--text-secondary)}++.line-work.covered::after{content:"Building\u2026";position:absolute;inset:0;display:grid;place-items:center;border-radius:12px;background:color-mix(in srgb,var(--bg-primary) 88%,transparent);backdrop-filter:blur(4px);color:var(--text-primary);font-weight:700}++@media (prefers-reduced-motion:no-preference){.line-demo[data-state=running] i{animation:line-live 1.8s ease-out infinite}}++@keyframes line-live{0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--accent) 60%,transparent)}70%,100%{box-shadow:0 0 0 6px transparent}}.even-demo{display:grid;grid-template-columns:repeat(var(--cols,3),minmax(0,1fr));gap:8px;padding:10px}.even-demo span{display:grid;place-items:center;height:44px;border-radius:10px;background:var(--bg-secondary);font-weight:800}.six-demo{flex-wrap:wrap;padding:8px 10px;font-size:13px}.six-demo strong{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:160px}@@@ -211,6 +211,26 @@const draw = (k) => { bar.replaceChildren(node('strong', '', 'OpenVibe.Exchange'), ...all.slice(0, k).map((t) => node('span', '', t)), ...(k < all.length ? [node('span', 'six-more', `More (${all.length - k})`)] : [])); };draw(all.length); widget.append(bar); say('Thirteen links wrap and cut the name.');control('Six, the rest under More', () => { draw(6); say('Six places, one line, the name whole.'); }, true); control('The bug', () => { draw(all.length); say('Thirteen links wrap and cut the name.'); });++ } else if (id === 'ask-your-agent') {++ const row = node('div', 'ask-demo'); const share = node('span', 'ask-share', 'Share'); const chip = button('\u2726 Ask your agent', () => { box.value = 'About the video \u201cThe Workshop tour\u201d on OpenVibe.Video: '; dock.classList.add('open'); box.focus(); say('Vibe opened with the question waiting. Nothing was sent.'); });++ chip.className = 'ask-chip'; row.append(share, chip);++ const dock = node('div', 'ask-dock'); const box = node('textarea', 'ask-box'); box.rows = 2; box.setAttribute('aria-label', 'Your agent\u2019s box'); const send = button('Send', () => { say(box.value.trim() ? 'The person pressed Send: only now does it go.' : 'Nothing to send.'); }, true); dock.append(node('b', '', 'Vibe'), box, send);++ widget.append(row, dock); say('One quiet chip beside Share.');++ control('The bug', () => { dock.classList.add('open'); box.value = ''; say('A blank box on another page: the person retypes what they were looking at.'); });++ } else if (id === 'levels-with-a-floor') {++ const lv = [['Asks first', 'It asks you before it does anything outside its own work.'], ['Edits on its own', 'It changes your files and drafts without asking. Anything else asks.'], ['A second model checks', 'Another model reviews each step. When it says no, you decide.'], ['Full trust', 'It acts without asking.']];++ const list = node('div', 'lv-demo'); lv.forEach(([title, line], i) => { const l = node('label', 'lv-card'); const r = node('input'); r.type = 'radio'; r.name = 'lv'; if (!i) r.checked = true; r.addEventListener('change', () => say(i === 3 ? 'Full trust: still asks before money, passwords, deleting and someone new.' : `${title}.`)); l.append(r, node('b', '', title), node('span', '', line)); list.append(l); });++ const floor = node('p', 'lv-floor', 'Always asks, at every level: spending money \u00b7 a password or key \u00b7 deleting something \u00b7 messaging someone new.');++ widget.append(list, floor); say('Four levels in words, and the floor in view.');++ control('The bug', () => { floor.classList.toggle('gone'); say(floor.classList.contains('gone') ? 'No floor on the page: full trust reads as \u201canything\u201d.' : 'The floor is back in view.'); });++ } else if (id === 'agent-line') {++ const states = [['running', 'Your agent is building this', 'Watch in your Dock'], ['waiting', 'Your agent asks to publish this', 'Answer in your Dock'], ['done', 'Your agent is done', 'See its steps'], ['failed', 'Your agent couldn\u2019t finish', 'See what happened']];++ const bar = node('div', 'line-demo'); const dot = node('i', ''); const words = node('span', ''); const act = node('span', 'line-act'); bar.append(dot, words, act);++ const work = node('div', 'line-work', 'The space, changing as the agent writes it'); let k = 0;++ const draw = () => { const [st, w, a] = states[k]; bar.dataset.state = st; words.textContent = w; act.textContent = a; work.classList.remove('covered'); say(w + '.'); };++ draw(); widget.append(bar, work);++ control('Next state', () => { k = (k + 1) % states.length; draw(); }, true);++ control('The bug', () => { work.classList.add('covered'); say('A blurred cover over the work: nobody can watch it being made.'); });} else if (id === 'mark-system') {const row=node('div','mark-demo'),mark=node('span','ov-mark','ov');row.append(mark,node('span','','OpenVibe'));widget.append(row);control('Loading state',()=>{mark.classList.toggle('loading');say(mark.classList.contains('loading')?'Loading state active.':'Loading state stopped.');});control('Change site',()=>{mark.textContent=mark.textContent==='ov'?'lv':'ov';mark.style.borderRadius=mark.textContent==='lv'?'50%':'16px';say('The mark morphs for the active site.');});control('Open five studies',()=>{window.location.href='/patterns/mark/studies';});}@@@ -57,6 +57,9 @@ const patterns = [['card-or-row','Layout that holds','One card that is also a row','A grid of cards and a list of rows are the same markup laid out two ways, so switching view never re-renders, loses a menu, or changes what is listed. Remember the view the person chose.', '2026-11-07 · Two templates for grid and list drifted apart: the list lost the thumbnail, the grid lost the menu.', 'One item, two layouts, one switch.', t('spaceMd','durationFast')],['even-grid','Layout that holds','An even grid: no card alone on a row','Count the cards before choosing the columns. Eight cards are two or four to a row, six are two or three, and a card left alone on the last row is either a ninth worth adding or a column count to change.', '2026-11-07 · Seven doors in three columns left one card alone on the Exchange home.', 'Columns that divide the cards.', t('spaceMd')],['six-places','Navigation','Six places in the bar, the rest under More','A site’s bar holds at most six places, each with an icon and one or two words. Everything else goes under More, and the brand is the way home. A label never wraps onto a second line.', '2026-11-07 · Fourteen links wrapped into two lines (“Why this model”, “Sell compute”) and cut the site’s name to “OpenVibe…”.', 'Six in the bar, the rest one tap away.', t('tap','spaceMd')],++ ['ask-your-agent','Agents','Ask your agent, with the question waiting','Where a page shows one thing (a stream, a video, a file, a thread), one quiet chip beside Share opens the person\u2019s agent with a sentence about that thing already in its box. It is never sent for them: they read it, change it, and press Send. Signed out, the chip signs in first.', '2026-11-07 · Agents could do things on every site, but every ask started from a blank box on another page.', 'One chip, the context in the box, never sent.', t('radiusPill', 'tap')],++ ['levels-with-a-floor','Agents','Levels with a floor that never moves','An agent\u2019s freedom is four levels in words a person can act on (asks first, edits on its own, a second model checks, full trust), as four cards with one line each. Under them, always in view, the four things that ask at every level: spending money, a password or key, deleting something, and messaging someone new. The floor is enforced on the server, not drawn as an option.', '2026-11-07 · Approval levels existed in the API, and nothing on the page said what full trust still could not do.', 'Four cards, and the floor in view.', t('tap', 'spaceMd')],++ ['agent-line','Agents','The agent\u2019s line leaves the work in view','While an agent works on something the person is looking at (a space, a document, a page), its state is one line above the work, not a cover over it: a live dot while it builds, amber while it asks, green when done, red when stuck, with the Dock one tap away. The person watches the work change as the agent makes it.', '2026-11-07 · Space\u2019s builder covered the preview with a blurred \u201cbuilding\u201d screen, so nobody could watch their agent build.', 'One line above the work, four states.', t('durationBase', 'tap')],].map(([slug,group,title,rule,bug,summary,tokens]) => ({ slug, group, title, rule, bug, summary, tokens: [...new Set([...tokens, 'tap', 'spaceMd', 'durationBase'])] }));function esc(s) { return String(s).replace(/[&<>"']/g, (c) => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); }
@@ -99,6 +99,28 @@
@@ -99,6 +99,28 @@
.cr-demo li{display:grid;gap:6px;padding:8px;border-radius:10px;background:var(--bg-secondary);min-width:0;font-size:12.5px;overflow-wrap:anywhere}
.cr-demo li{display:grid;gap:6px;padding:8px;border-radius:10px;background:var(--bg-secondary);min-width:0;font-size:12.5px;overflow-wrap:anywhere}
.cr-thumb{display:grid;place-items:center;aspect-ratio:4/3;border-radius:8px;background:color-mix(in srgb,var(--accent) 14%,transparent);font-weight:800;font-size:11px}
.cr-thumb{display:grid;place-items:center;aspect-ratio:4/3;border-radius:8px;background:color-mix(in srgb,var(--accent) 14%,transparent);font-weight:800;font-size:11px}
.cr-demo.rows{grid-template-columns:minmax(0,1fr);gap:0}.cr-demo.rows li{grid-template-columns:40px minmax(0,1fr);align-items:center;border-radius:0;background:none;border-bottom:1px solid var(--border)}.cr-demo.rows .cr-thumb{aspect-ratio:1}
.cr-demo.rows{grid-template-columns:minmax(0,1fr);gap:0}.cr-demo.rows li{grid-template-columns:40px minmax(0,1fr);align-items:center;border-radius:0;background:none;border-bottom:1px solid var(--border)}.cr-demo.rows .cr-thumb{aspect-ratio:1}
+.ask-demo{display:flex;gap:8px;align-items:center;padding:10px}
+.ask-share{display:inline-flex;align-items:center;min-height:36px;padding:0 14px;border-radius:999px;border:1px solid var(--border)}
+.ask-chip{min-height:36px;padding:0 14px;border-radius:999px;border:1px solid color-mix(in srgb,var(--accent) 45%,var(--border));background:color-mix(in srgb,var(--accent) 10%,transparent);color:inherit;font:inherit;font-weight:700;cursor:pointer}
+.ask-dock{display:none;gap:8px;margin:0 10px 10px;padding:10px;border-radius:14px;background:var(--bg-secondary)}
+.ask-dock.open{display:grid}
+.ask-box{width:100%;box-sizing:border-box;font:inherit;padding:8px 10px;border-radius:10px;border:1px solid var(--border);background:var(--bg-primary);color:inherit}
+.lv-demo{display:grid;gap:6px;padding:10px}
+.lv-card{display:grid;grid-template-columns:auto 1fr;column-gap:10px;padding:9px 12px;border:1px solid var(--border);border-radius:12px;cursor:pointer}
+.lv-card input{grid-row:span 2;margin-top:3px}
+.lv-card span{grid-column:2;font-size:13px;color:var(--text-secondary)}
+.lv-card:has(input:checked){border-color:var(--accent);background:color-mix(in srgb,var(--accent) 9%,transparent)}
+.lv-floor{margin:0 10px 10px;padding:9px 12px;border-radius:10px;font-size:13px;background:color-mix(in srgb,var(--text-primary) 6%,transparent)}
+.lv-floor.gone{display:none}
+.line-demo{display:flex;align-items:center;gap:10px;margin:10px;padding:8px 12px;border-radius:10px;border:1px solid var(--border);font-weight:650}
+.line-demo i{width:9px;height:9px;border-radius:50%;background:var(--accent);flex:none}
+.line-demo[data-state=waiting]{border-color:#f59e0b}.line-demo[data-state=waiting] i{background:#f59e0b}
+.line-demo[data-state=done] i{background:#22c55e}.line-demo[data-state=failed] i{background:#ef4444}
+.line-act{margin-left:auto;font-size:13px;color:var(--text-secondary)}
+.line-work{position:relative;display:grid;place-items:center;height:110px;margin:0 10px 10px;border-radius:12px;background:var(--bg-secondary);color:var(--text-secondary)}
+.line-work.covered::after{content:"Building\u2026";position:absolute;inset:0;display:grid;place-items:center;border-radius:12px;background:color-mix(in srgb,var(--bg-primary) 88%,transparent);backdrop-filter:blur(4px);color:var(--text-primary);font-weight:700}
+@media (prefers-reduced-motion:no-preference){.line-demo[data-state=running] i{animation:line-live 1.8s ease-out infinite}}
+@keyframes line-live{0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--accent) 60%,transparent)}70%,100%{box-shadow:0 0 0 6px transparent}}
.even-demo{display:grid;grid-template-columns:repeat(var(--cols,3),minmax(0,1fr));gap:8px;padding:10px}
.even-demo{display:grid;grid-template-columns:repeat(var(--cols,3),minmax(0,1fr));gap:8px;padding:10px}
.even-demo span{display:grid;place-items:center;height:44px;border-radius:10px;background:var(--bg-secondary);font-weight:800}
.even-demo span{display:grid;place-items:center;height:44px;border-radius:10px;background:var(--bg-secondary);font-weight:800}
.six-demo{flex-wrap:wrap;padding:8px 10px;font-size:13px}.six-demo strong{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:160px}
.six-demo{flex-wrap:wrap;padding:8px 10px;font-size:13px}.six-demo strong{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:160px}
@@ -211,6 +211,26 @@
@@ -211,6 +211,26 @@
const draw = (k) => { bar.replaceChildren(node('strong', '', 'OpenVibe.Exchange'), ...all.slice(0, k).map((t) => node('span', '', t)), ...(k < all.length ? [node('span', 'six-more', `More (${all.length - k})`)] : [])); };
const draw = (k) => { bar.replaceChildren(node('strong', '', 'OpenVibe.Exchange'), ...all.slice(0, k).map((t) => node('span', '', t)), ...(k < all.length ? [node('span', 'six-more', `More (${all.length - k})`)] : [])); };
draw(all.length); widget.append(bar); say('Thirteen links wrap and cut the name.');
draw(all.length); widget.append(bar); say('Thirteen links wrap and cut the name.');
control('Six, the rest under More', () => { draw(6); say('Six places, one line, the name whole.'); }, true); control('The bug', () => { draw(all.length); say('Thirteen links wrap and cut the name.'); });
control('Six, the rest under More', () => { draw(6); say('Six places, one line, the name whole.'); }, true); control('The bug', () => { draw(all.length); say('Thirteen links wrap and cut the name.'); });
+ } else if (id === 'ask-your-agent') {
+ const row = node('div', 'ask-demo'); const share = node('span', 'ask-share', 'Share'); const chip = button('\u2726 Ask your agent', () => { box.value = 'About the video \u201cThe Workshop tour\u201d on OpenVibe.Video: '; dock.classList.add('open'); box.focus(); say('Vibe opened with the question waiting. Nothing was sent.'); });
+ chip.className = 'ask-chip'; row.append(share, chip);
+ const dock = node('div', 'ask-dock'); const box = node('textarea', 'ask-box'); box.rows = 2; box.setAttribute('aria-label', 'Your agent\u2019s box'); const send = button('Send', () => { say(box.value.trim() ? 'The person pressed Send: only now does it go.' : 'Nothing to send.'); }, true); dock.append(node('b', '', 'Vibe'), box, send);
+ widget.append(row, dock); say('One quiet chip beside Share.');
+ control('The bug', () => { dock.classList.add('open'); box.value = ''; say('A blank box on another page: the person retypes what they were looking at.'); });
+ } else if (id === 'levels-with-a-floor') {
+ const lv = [['Asks first', 'It asks you before it does anything outside its own work.'], ['Edits on its own', 'It changes your files and drafts without asking. Anything else asks.'], ['A second model checks', 'Another model reviews each step. When it says no, you decide.'], ['Full trust', 'It acts without asking.']];
+ const list = node('div', 'lv-demo'); lv.forEach(([title, line], i) => { const l = node('label', 'lv-card'); const r = node('input'); r.type = 'radio'; r.name = 'lv'; if (!i) r.checked = true; r.addEventListener('change', () => say(i === 3 ? 'Full trust: still asks before money, passwords, deleting and someone new.' : `${title}.`)); l.append(r, node('b', '', title), node('span', '', line)); list.append(l); });
+ const floor = node('p', 'lv-floor', 'Always asks, at every level: spending money \u00b7 a password or key \u00b7 deleting something \u00b7 messaging someone new.');
+ widget.append(list, floor); say('Four levels in words, and the floor in view.');
+ control('The bug', () => { floor.classList.toggle('gone'); say(floor.classList.contains('gone') ? 'No floor on the page: full trust reads as \u201canything\u201d.' : 'The floor is back in view.'); });
+ } else if (id === 'agent-line') {
+ const states = [['running', 'Your agent is building this', 'Watch in your Dock'], ['waiting', 'Your agent asks to publish this', 'Answer in your Dock'], ['done', 'Your agent is done', 'See its steps'], ['failed', 'Your agent couldn\u2019t finish', 'See what happened']];
+ const bar = node('div', 'line-demo'); const dot = node('i', ''); const words = node('span', ''); const act = node('span', 'line-act'); bar.append(dot, words, act);
+ const work = node('div', 'line-work', 'The space, changing as the agent writes it'); let k = 0;
+ const draw = () => { const [st, w, a] = states[k]; bar.dataset.state = st; words.textContent = w; act.textContent = a; work.classList.remove('covered'); say(w + '.'); };
+ draw(); widget.append(bar, work);
+ control('Next state', () => { k = (k + 1) % states.length; draw(); }, true);
+ control('The bug', () => { work.classList.add('covered'); say('A blurred cover over the work: nobody can watch it being made.'); });
} else if (id === 'mark-system') {
} else if (id === 'mark-system') {
const row=node('div','mark-demo'),mark=node('span','ov-mark','ov');row.append(mark,node('span','','OpenVibe'));widget.append(row);control('Loading state',()=>{mark.classList.toggle('loading');say(mark.classList.contains('loading')?'Loading state active.':'Loading state stopped.');});control('Change site',()=>{mark.textContent=mark.textContent==='ov'?'lv':'ov';mark.style.borderRadius=mark.textContent==='lv'?'50%':'16px';say('The mark morphs for the active site.');});control('Open five studies',()=>{window.location.href='/patterns/mark/studies';});
const row=node('div','mark-demo'),mark=node('span','ov-mark','ov');row.append(mark,node('span','','OpenVibe'));widget.append(row);control('Loading state',()=>{mark.classList.toggle('loading');say(mark.classList.contains('loading')?'Loading state active.':'Loading state stopped.');});control('Change site',()=>{mark.textContent=mark.textContent==='ov'?'lv':'ov';mark.style.borderRadius=mark.textContent==='lv'?'50%':'16px';say('The mark morphs for the active site.');});control('Open five studies',()=>{window.location.href='/patterns/mark/studies';});
}
}
@@ -57,6 +57,9 @@ const patterns = [
@@ -57,6 +57,9 @@ const patterns = [
['card-or-row','Layout that holds','One card that is also a row','A grid of cards and a list of rows are the same markup laid out two ways, so switching view never re-renders, loses a menu, or changes what is listed. Remember the view the person chose.', '2026-11-07 · Two templates for grid and list drifted apart: the list lost the thumbnail, the grid lost the menu.', 'One item, two layouts, one switch.', t('spaceMd','durationFast')],
['card-or-row','Layout that holds','One card that is also a row','A grid of cards and a list of rows are the same markup laid out two ways, so switching view never re-renders, loses a menu, or changes what is listed. Remember the view the person chose.', '2026-11-07 · Two templates for grid and list drifted apart: the list lost the thumbnail, the grid lost the menu.', 'One item, two layouts, one switch.', t('spaceMd','durationFast')],
['even-grid','Layout that holds','An even grid: no card alone on a row','Count the cards before choosing the columns. Eight cards are two or four to a row, six are two or three, and a card left alone on the last row is either a ninth worth adding or a column count to change.', '2026-11-07 · Seven doors in three columns left one card alone on the Exchange home.', 'Columns that divide the cards.', t('spaceMd')],
['even-grid','Layout that holds','An even grid: no card alone on a row','Count the cards before choosing the columns. Eight cards are two or four to a row, six are two or three, and a card left alone on the last row is either a ninth worth adding or a column count to change.', '2026-11-07 · Seven doors in three columns left one card alone on the Exchange home.', 'Columns that divide the cards.', t('spaceMd')],
['six-places','Navigation','Six places in the bar, the rest under More','A site’s bar holds at most six places, each with an icon and one or two words. Everything else goes under More, and the brand is the way home. A label never wraps onto a second line.', '2026-11-07 · Fourteen links wrapped into two lines (“Why this model”, “Sell compute”) and cut the site’s name to “OpenVibe…”.', 'Six in the bar, the rest one tap away.', t('tap','spaceMd')],
['six-places','Navigation','Six places in the bar, the rest under More','A site’s bar holds at most six places, each with an icon and one or two words. Everything else goes under More, and the brand is the way home. A label never wraps onto a second line.', '2026-11-07 · Fourteen links wrapped into two lines (“Why this model”, “Sell compute”) and cut the site’s name to “OpenVibe…”.', 'Six in the bar, the rest one tap away.', t('tap','spaceMd')],
+ ['ask-your-agent','Agents','Ask your agent, with the question waiting','Where a page shows one thing (a stream, a video, a file, a thread), one quiet chip beside Share opens the person\u2019s agent with a sentence about that thing already in its box. It is never sent for them: they read it, change it, and press Send. Signed out, the chip signs in first.', '2026-11-07 · Agents could do things on every site, but every ask started from a blank box on another page.', 'One chip, the context in the box, never sent.', t('radiusPill', 'tap')],
+ ['levels-with-a-floor','Agents','Levels with a floor that never moves','An agent\u2019s freedom is four levels in words a person can act on (asks first, edits on its own, a second model checks, full trust), as four cards with one line each. Under them, always in view, the four things that ask at every level: spending money, a password or key, deleting something, and messaging someone new. The floor is enforced on the server, not drawn as an option.', '2026-11-07 · Approval levels existed in the API, and nothing on the page said what full trust still could not do.', 'Four cards, and the floor in view.', t('tap', 'spaceMd')],
+ ['agent-line','Agents','The agent\u2019s line leaves the work in view','While an agent works on something the person is looking at (a space, a document, a page), its state is one line above the work, not a cover over it: a live dot while it builds, amber while it asks, green when done, red when stuck, with the Dock one tap away. The person watches the work change as the agent makes it.', '2026-11-07 · Space\u2019s builder covered the preview with a blurred \u201cbuilding\u201d screen, so nobody could watch their agent build.', 'One line above the work, four states.', t('durationBase', 'tap')],
].map(([slug,group,title,rule,bug,summary,tokens]) => ({ slug, group, title, rule, bug, summary, tokens: [...new Set([...tokens, 'tap', 'spaceMd', 'durationBase'])] }));
].map(([slug,group,title,rule,bug,summary,tokens]) => ({ slug, group, title, rule, bug, summary, tokens: [...new Set([...tokens, 'tap', 'spaceMd', 'durationBase'])] }));
function esc(s) { return String(s).replace(/[&<>"']/g, (c) => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); }
function esc(s) { return String(s).replace(/[&<>"']/g, (c) => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); }