openvibers/openvibe.shared CHANGELOG.md

CHANGELOG.md

main 463.2 KB · 5740 lines · markdown

1# Changelog
2
3## 4.242.0 — 2026-11-13
4
5**One agent you can watch work, cards that act, and a + that blooms** (the owner, 11-13: "it needs to use a universal
6agent system … your actor is just your agent"; "this doesn't work … typing test … nothing happens … not seamless and
7sleek"; "why don't the avatars show up properly"; "I don't really like the plus … reanalyze and make it a better one …
8way more animated, better effects").
9
10- **The run thread (ov-run.js).** Quiet tool steps fold into one line ("Ran 6 steps", open to see each). Words you
11 send while it works wait as queued messages (Send now, or they go when the turn ends). The list counts what is
12 working now.
13- **Your agents in the menu (search.js, dock.js).** The stack and You's agent card read through Network's Frame
14 (`/api/frame/actor`, `/api/frame/actor/faces`). Your agents at work show with their faces among the active rows; a tap
15 opens their tasks.
16- **Faces that fail to load show the initial** (the feed, the chat list, the Dock), never an empty circle.
17- **The user card acts (ov-user.js).** It reads with the viewer's token (`options.token`, else the bar's new
18 `OpenVibeNavbar.viewerToken()`) and draws the actions Network computed for that viewer: Message, Follow, Add friend,
19 Tip, Watch together, Mute, Block, Report and staff tools. An action calls Network, then the card reads itself again
20 (plan §Y.1 A2).
21- **An empty table is the designed empty card** (kit.js `table(…, { empty })`, rule 141; plan §Y.1 A10).
22- **The composer's + is its own mark (ov-composer.js).**
23 - At rest: a bold rounded plus in a softly lit disc (nothing moves).
24 - Under the pointer, or focused: the arms reach and four petals peek from the diagonals (the tiles it holds).
25 - Pressed: it sinks.
26 - Open: it springs a turn and a half of a quarter into an ×; the disc fills with the accent, rings once, and the petals
27 burst outward in the disc's white.
28 - Calm keeps all of it, slower, a third as far, with no overshoot. Off stills it.
29
30## 4.241.0 — 2026-11-13
31
32**The composer's + is what you can add, and the feed reads like posts by people** (the owner, 11-13: "the plus button
33on the composer … camera, photos, files … our own custom style … a modular system … it can still open this choose an
34action … share … each site can choose … highly configurable"; on openvibe.community's feed: "add icons to those [tags]
35and make those buttons nicer … make it more like a social media … it should say who posted it … their level … their
36username effects … update the listings for deals … way better on mobile, but also great on desktop").
37
38- **The + (ov-composer.js).**
39 - Big tiles for what you add: Camera (the shared capture core), Photos (pictures and videos), Files (any file; on
40 Android the system's Choose an action). Then rows: Paste (a picture or words you copied), Screenshot (a screen,
41 window or tab; desktop), Camera app (the phone's own camera), Share to an app (what you wrote, with its files),
42 Timer, GitHub, a site's own tools, and what a narrow row hides. Each row says what it does in a second line.
43 - A phone gets a sheet from the bottom (the top layer: no panel clips it; swipe it down, tap outside, or the ×); a
44 wide screen a panel under the row with the rows in columns. Tiles rise in turn; a press sinks them.
45 - What the + holds as a tile is not also a button in the row (rule: never twice); a file tool's input stays in the
46 form so the files still go with it.
47 - Configurable: `mount(host, { plus: { tiles, rows, only } })`, a form's `data-ovc-plus`, the site's
48 `OpenVibeComposer.configure({ plus, tools })`; a tool says where it fits with `sub`, `tile`, `accept`, `capture`
49 and `when(env)` (env: files, anyFile, touch, camera, screen, share, clip). `noPlus` leaves the + out.
50- **The feed (ov-home.js).**
51 - Each chip has its glyph (For you, Following, Live, Videos, Clips, Pics, Posts, Rundown, Memes, Games, Chat,
52 Events, Jobs, Deals, Stats, Wiki, Quests, Updates); the chosen one is tinted, not a white block.
53 - A person's card names them: their face, their name wearing its effect (`actor.fx`, drawn by items.css; Inventory
54 by `subject` where the page has items.js), their level badge, @handle, the site and when (feed.item@1 1.1.0,
55 Contracts 0.425.0). "System" is nobody: a site's own card is the site's name and what it is, never
56 "Community · Community".
57 - A post, a picture, a clip, a video, a meme or the Rundown is its card and, under it (outside its link), Comment
58 with the count, likes, and Share (the phone's share sheet, or the link copied).
59 - A run of deals is a listing: each with its picture (or a tag), its price, the old price struck through with the
60 saving, and the store; two columns on a wide screen; "See all 37 deals".
61- Tests: `composer-plus-chrome` (new: tiles and rows, the sheet and the panel, per box and per site, a site's tool,
62 picked files in the box); `composer-v2-chrome`, `dock-chrome` and `menu-feed-v2-chrome` follow the new markup.
63
64## 4.240.0 — 2026-11-13
65
66**One agent in the menu's Vibe, and a menu button drawn from the menu** (the owner, 11-13: "all this separation of
67tasks and codes and chats … we shouldn't even have that. It's just like filters … your actor is just your agent … it
68can orchestrate to openvibe.codes, or timer, task, desktop, browser"; "I tried typing test in and just like nothing
69happens … not seamless and sleek"; "regenerate this icon … based on what the menu actually looks like and what the
70menu has to offer … good animations and effects … highlight differently and glow").
71
72- **The menu's Vibe is one agent (ov-run.js, search.js).**
73 - Your agent heads the list (`o.agent`): its face with a state dot (ready, working, asks you, paused), its name,
74 what it is doing and today's spend, and Pause or Resume. A tap shows its work. It reads the Frame's `/actor`.
75 - All · Chats · Tasks · Code are filter chips over one list, never modes: the Dock's Chat, Agents and Code panels
76 and its Actor card are no longer lent into the menu (only Now and the live view of an agent at work, the Dock's
77 new `part: 'live'`).
78 - One box starts everything ("Ask, or say what to do"); the Frame places the words (Network #911: Codes for a change
79 to code, your computer for "on my computer", Vibe for the rest). The thread opens at once with your words and
80 your agent typing until it answers; a run your agent handed on says where it went ("Sent to Codes"). A start that
81 fails goes back to the list with the words in the box and why.
82 - Now (timers, what is on) shows above the list on every filter; Ask your agent (`[data-ov-ask]`, the Dock's
83 `ask()`) puts the words in the one box, unsent; words put in the box before the shared composer loads carry over.
84- **The menu button (navbar.js)** is the menu, small: the ask bar you lead (your face, the words, Vibe's spark) over
85 the place tabs, one lit. Now and then the light walks across the tabs; under the pointer the tabs lift and light,
86 the spark turns and the mark glows; while an agent works the spark turns slowly (`.is-working`); open, the bar and
87 the tabs fold into the ×. Calm and Off keep it still. Class names are M57's, so callers and tests are unchanged.
88- Tests: `run-thread-chrome` (a new run opens with your words and the typing), `dock-chrome` (the menu's Vibe is one
89 list with your agent; Tasks is a filter; the live view above it).
90
91## 4.239.0 — 2026-11-13
92
93**One floating action, a composer that fits its column, a close button with depth** (the owner, 11-13: "this button
94is buggy … it disappears when you scroll … it should just use a floating button that actually moves its position …
95it's one button, it's not jumping around, it's transitioning"; "this camera button will flash in and out"; "improve
96composer … better responsive support and size on mobile to fit better"; "make the close button look slightly 3D and …
97glowing … make it look kind of cool").
98
99- **`OVFloat.action(el, opts)` (ov-float.js), the floating action.** One element with a home in the page: while its
100 home is on screen it sits there; when the home scrolls away (before the person reaches it, after they pass it, or
101 either: `side`), the same element glides to the dock at the bottom of the window, above the bars
102 (`[data-ov-bottom-bar]`, the app bar), the keyboard and a footer that has come up, and back when its home returns.
103 Never a copy and never hidden: its home keeps the space, the move is a transform (FLIP; from far away it rises from
104 just past the edge), Calm shortens it and Off places it at once. `when()` keeps it home (e.g. only phones float it);
105 `maxWidth`, `stretch`, `gap`, `above`, `avoid`; `ov:fab` and `data-ov-fab="floating|home"` say where it is;
106 `check()` and `destroy()`; `OVFloat.actions()` lists them. Live's Go live adopts it next, replacing its copied dock.
107- **The composer fits its column (ov-composer.js).**
108 - A box narrower than its words says their short form ("Message…", or the site's `data-ovc-short`) and the long
109 one again when there is room; a site that changes its words later is followed. Words never wrap or clip.
110 - Under 340 px the chat preset's tools tighten into quiet 36 px discs; under 240 px they go under the words.
111- **The close orb (search.js)** is a lit sphere: a highlight up and to the left, a shade under it, an accent rim, a
112 soft glow, a white ×; a sheen sweeps across once as the menu opens; hover brightens the glow; a press sinks it.
113 Nothing moves at rest.
114- Tests: `float-action-chrome` (one element floats and comes home, above the bar and the footer, `when()`,
115 `ov:fab`, `destroy()`).
116
117## 4.238.0 — 2026-11-13
118
119**The menu's top is a composer, you lead it, no text arrows anywhere, a ringing bell, You gets the height** (the owner's
120phone, 11-13: "redesign the whole entire top to be our agentic search … uses our composer", "move the actor thing so
121that it's at the top", "merge … this little online thing … the colors and other settings … shouldn't be taking up any
122space", "the close button can't be white", "get rid of every single instance of this right or left arrow", "regenerate
123the bell icon like that's ringing", "hide the label of the inactive tabs", "add the badges back", "the icons are so
124small", "take up the full height properly").
125
126- **The top (search.js).**
127 - The pill is a composer: voice and attach inside it while it is empty, **Ask Vibe** (a send disc) once there are
128 words; Ctrl/⌘+Enter asks Vibe whatever the list shows. A question becomes a Vibe run in the menu; an attached file
129 goes into Vibe's box.
130 - **You lead the pill:** your picture and the online dot.
131 - **What runs for you is stacked behind your picture** ("when you have actors or tasks running … sessions,
132 conversations, anything, there should be stacked … your agents … behind you, and … status indicator"): your Actor
133 while it works or asks, and every run that is going or waits for you; what waits for you first; two faces on a
134 phone and three on a computer, then a count. Each face has its picture, or its kind's glyph on its own hue, and
135 its state on a ring (a comet while it works, amber while it waits for you). They fan out from behind you as the menu
136 opens. Nothing is drawn on your own picture but the online dot (the arc on it read as a stray line).
137 - A tap on your picture floats **your sheet** over the menu: your row with Display and your account's menu, your
138 Actor's card, then **Running for you** (each run, its state, a tap opens it in Vibe); its parts rise in one after
139 the other. A tap elsewhere, Escape or your picture again folds it. You's own row and Actor strip are gone, so You
140 starts at its tabs.
141 - On a phone the pill says "Search or ask Vibe" (the site's name is its tab).
142 - **The close orb** sits beside the pill (not over it), 48 px, an accent disc with a 26 px x; it spins in and turns
143 on hover and press. The white ring was the browser's own focus outline: focus is now an accent ring, for keys only.
144 - The place tabs are shorter on a phone (54 px), their glyphs 30 px over the word (24 px beside it on a computer).
145 - The list fills the menu's height.
146- **You (ov-home.js).**
147 - Tabs: the chosen tab is lit and widens to show its name; the others are their glyphs (27 px); switching, the last
148 one folds as the new one opens. The count sits on the bell's corner; the bell rings once when something is new.
149 The row is shorter (42 px) and closer to the top.
150 - All and Edit: 44 px with 26 px glyphs. Every app's name (yours too) is 13.5 px, 800; your apps' glyphs fill more
151 of their tiles.
152 - `OpenVibeHome.youCard(box, o)` draws your sheet; `actorCard(box, o)` your Actor's card.
153- **Notifications (notification-ui.js).** The ringing bell for the empty state (it swings once, its waves fade in;
154 Calm shortens it, Off stills it), centred; search and Unread are 44 px circles with centred glyphs; the foot's
155 buttons are 44 px with drawn glyphs; in the menu the foot sits at the bottom.
156- **The bell** is redrawn everywhere (kit, glyphs, Dock, notifications, You), sized and centred to the set (19 of 24
157 units), and **`bell-ring`** joins the registry; the glyph fonts and the ink fixture are rebuilt.
158- **No text arrows.** Every →, ↗, ‹, ›, ↑, ↓, ↩ shown to a person is a drawn glyph now (agent-session, agent-sheet,
159 action-center, ov-float, ov-home, ov-look, ov-metrics, search, shipped, showcase, thread, user-card) or words (navbar,
160 url-resolver, components, ov-composer, ov-capture). `test/no-text-arrows.test.js` reads every module's strings with
161 a tokenizer and fails on one.
162- **Code is two ways** (dock-code.js): **Make or change** (something new or a project of yours, named in your words;
163 Codes works out which: `mode: 'make'`, Codes #448) and **OpenVibe**. No repository field.
164- **Vibe's list** (ov-run.js): tighter rows. **Your Actor** (dock-actor.js): its recent steps are a clock button.
165- Tests: `menu-home-screen-chrome` (you lead the pill with your Actor's state; your sheet; the composer's tools; only the
166 chosen tab is named; glyphs ≥ 24 px), `no-text-arrows`, `search-site-name-chrome`, `showcase`, `dock-code-chrome`,
167 `dock-actor-chrome`; budgets: notification-ui 27.1 KB, action-center 8.2 KB (dated notes).
168
169## 4.237.0 — 2026-11-13
170
171**You is one slim row, your site's own are apps, and the menu's badges and the composer's row line up** (the owner's
172phone, 11-13: "completely redesign this card … the buttons are below", "it can be … an app … for admin, mod rank",
173"the red dot … next to the word feed", "improve … match" the badges, the composer's row "offset").
174
175- **You (ov-home.js).**
176 - Your card is one slim row: your picture with its online dot, your name and handle, Display, and your account's
177 menu (a chevron).
178 - The menu is anchored in the top layer (OVSurfaces.anchor) and holds Your profile, Account and settings, Add or
179 switch account and Sign out. The button that opens it closes it; Escape and a press elsewhere do too.
180 - What the site gives you (your channel, Admin, moderation: `me.own`) are apps at the start of the Apps tab, by
181 rank, each with a tooltip; they are no longer buttons in the card. They are their own row (`.ovh-own-apps`), never
182 part of the grid you arrange.
183- **The menu's tabs (search.js).**
184 - Feed's live dot sits after the word and breathes.
185 - Counts (Feed's new, You's unread) ride on the icon's corner instead of a third line under the label.
186 - Every count in the menu (the tabs' and You's Notifications tab) is one badge: an 18 px pill, a ring of the
187 ground, a soft shadow, and a pop-in.
188- **The composer's row.**
189 - Every tool is the same quiet 40 px disc, so the first one lines up with the words above it.
190 - An open + (or a pressed tool) is that disc tinted with a ring, not the only solid shape in the row.
191- Tests: `menu-home-screen-chrome` (the slim row, the account menu, own apps; `OV_CAPTURE_YOU` saves the open menu),
192 `menu-places-chrome` and `navbar-ask-vibe` updated.
193
194## 4.236.0 — 2026-11-13
195
196**You is yours on every site, the wait is the logo, and the box says what a message answers** (the owner's phone,
19711-13: "what happened to all the tabs … bring all that back", "what happened to the user avatar", "completely redesign
198this area", "in edit mode … you can't scroll up", "improve the O so it actually looks like the original", "improve the
199send button", "a replying to area or different content above").
200
201- **You, on a site with its own session (Run's).** The menu knew the person but had no token for the network's reads,
202 so You lost its tabs (Apps, Notifications, Recent) and the You tab its picture. Both follow the person now
203 (`known(o)` in ov-home.js, `me` in search.js's tabs). Without a token, Notifications links to the person's account.
204- **Your card, redesigned.**
205 - Your name and the gear on one line.
206 - Under it, one row of actions across the whole card in equal columns: this site's own (Channel, Admin), Display,
207 Switch, Sign out. Each is its glyph with its name under it and a tooltip; the row starts at the card's edge (it was
208 indented 52 px).
209 - Sign out's red only under a pointer; the gear turns under one.
210- **You's tabs look like tabs.**
211 - Apps, Alerts (Notifications) and History are one segmented track, each with its glyph and name; the chosen one is
212 lit.
213 - All apps and Edit are two glyph buttons at the end: All with its count on its corner, and Edit a pencil that turns
214 into the check, filled with the accent, while you arrange. That check is the one Done; the row is sticky, so it is
215 always in reach.
216- **Editing your home on a phone.** A quick swipe up or down scrolls the menu. A hold of a fifth of a second, or a
217 sideways move, takes the app (it lifts); only then does the page hold still.
218- **The menu's tabs nudge.** This site's tab rolls its glyph and name through the other places in its section, a beat
219 each, and back to its own: a little after the menu opens, then every 40 s while it is open and idle. Calm rolls once
220 per opening and Off never. Its accessible name stays its own; a press stops it.
221- **The wait (ov-mark busy).** The O stays the logo's ring at its full weight, a faint track, with a bright arc
222 running round it that grows as it goes and draws in behind. The V lifts 2.5 units in time (it bobbed 6, off the
223 O's line) and the glow breathes, one 1.25 s rhythm. It replaces eight thinner dots that sat inside the ring's line.
224- **The composer.**
225 - `bar({ icon, title, detail, kind, onClose })` puts any context above the words, inside the box, as reply and edit
226 do: a quote, or a file being answered.
227 - Bars carry a rail of the accent (editing in its own colour) and come in with a short rise.
228 - Send with nothing to send is a quiet outline round its arrow (it was a grey disc). With something to send it
229 fills with the accent on a spring, and the arrow lifts under a pointer and on a press.
230- Tests: `composer-bar-send` (new), `ov-mark` (the arc), `menu-home-screen-chrome` (the card in one aligned row with
231 names and tooltips; `OV_CAPTURE_YOU=<dir>` saves You and Edit at 390 px).
232
233## 4.235.0 — 2026-11-13
234
235**The layers: what floats folds into a small handle and grows to the window, and what opens from a button is anchored
236to it** (the owner's phone, watching a live stream, 11-13: "a small button that lets you pop it in and out", "doesn't
237allow it to be off screen", "I can't even resize it bigger", "the little three dots doesn't actually open a menu").
238
239- **ov-float.js, tucked.**
240 - Thrown past a side (or T, or a tuck button), a box folds into a small handle at that edge: 30 × 64 px, 34 × 72 to
241 a finger, set with `--ov-float-tab-w/-h`. It used to leave a strip as tall as itself hanging off the screen.
242 - The fold is a clip animated with the Web Animations API, so nothing inside is squeezed, while the landing's spring
243 moves it to the side.
244 - The handle is a button that moves: a tap opens it, a drag up or down slides it along its side, and a pull sideways
245 brings it out under the finger.
246 - Tucked by hand, it opens again where it was (`ux`, kept in this browser only).
247- **ov-float.js, size.**
248 - It grows to the whole width of the window: `MAX_W` is 1 (was 0.60).
249 - Past what a docked frame holds (its width, or 85% of its height), a pinch, the corner or + lifts it over the page
250 where it is, and it keeps growing, up to 92% of the window's height.
251 - A double tap on a phone makes it the screen's width.
252 - New `f.sizeTo(px)`.
253- **ov-float.js, the place.**
254 - This browser's place wins. The account's (`/api/me/layout`) fills a device that has none, so a phone and a
255 desktop each keep their own.
256 - The account and `onChange` get only the account's fields (`placeOf`), never the browser's extras.
257 - A turn of the phone, the keyboard (the visual viewport), the address bar or a resize puts it back inside, once a
258 frame.
259 - While it moves, nothing in it is blurred: a backdrop blur repainted every frame made a phone's drag stutter.
260- **surfaces.js, `OVSurfaces.anchor(el, trigger, { place, gap, margin, sheet, animate })`.**
261 - The element goes in the top layer (the Popover API; fixed without it). No `overflow: hidden`, mask or stacking
262 context clips it.
263 - It is placed beside its button: `place` is side-align. It flips to the side with room, shifts to stay inside the
264 visual viewport, and is kept no taller than its side's room.
265 - It is placed again on scroll, resize and size changes, and released when its button leaves the page.
266 - `sheet: 'phone'` makes it a bottom sheet under 560 px.
267 - `data-ov-side` says where it went, and the default entrance grows from the button.
268 - `OVSurfaces.popover({ …, pop, place, sheet })` anchors its menu while it is open.
269- **kit-web.js.** Every `details.ovk-more` menu is anchored, not only those inside a rail: a phone's clipped layout, a
270 sheet or a rail no longer hides it.
271- Tests: `ov-float-chrome` (the handle's size and place, a slide, a pull, growing past the frame, a phone's double tap,
272 this device's place, the account's fields) and `surfaces-chrome` (a clipped menu at a phone's bottom opens above,
273 on top, inside the screen, and is released; `placeBox`).
274
275## 4.234.0 — 2026-11-13
276
277**The bottom bar hides all the way, and the composer stops repeating itself** (the owner's phone screenshots, 11-13).
278
279- **The bottom bar (`appbar.js`).** It stepped aside for a keyboard or an open panel with `translateY(110%)` while its
280 lift above the visible viewport still applied, so above a keyboard, or with Android's toolbar gap, it stopped half on
281 the screen. A hidden bar now drops the lift, sits fully below the screen, and is invisible and not clickable until it
282 comes back.
283- **The + sheet.** It no longer repeats a tool the row already shows; the emoji button and an "Emoji" tile side by
284 side were the same thing twice. The sheet holds what a narrow row hides, and the extras (Timer, GitHub).
285- **The GitHub picker:**
286 - each row's mark is its owner's initial on a colour of the name; GitHub's own pictures were blocked by most sites'
287 image policy, so every row showed a broken picture, and each one asked GitHub;
288 - the same words are answered from the page's cache for ten minutes;
289 - a new search cancels the one still running;
290 - it waits a third of a second after the last key;
291 - the list scrolls inside its panel and no longer runs over the box.
292- Tests `composer-v2-chrome` (nothing repeated, the initial, the cache) and `navbar-appbar` (hidden is all the way).
293
294## 4.233.0 — 2026-11-13
295
296**`ov-metrics.js` on a phone, and prices under a dollar.**
297
298- **A narrow table is cards** (`OVMetrics.table`):
299 - in a box under 520 px wide (a container query, so a side column counts too), each row is a card: its name on top
300 and each value under its label;
301 - the column heads become a row of sort buttons, and the one in use is outlined;
302 - nothing scrolls sideways;
303 - the table, its rows, cells and heads carry their roles, so a screen reader still reads a table whatever the display.
304- **`format.usd` keeps two significant digits under a dollar**: $0.0118 is `$0.012`, not `$0.01`; $0.25 stays `$0.25`.
305 A per-call price no longer rounds away.
306- Test `ov-metrics-chrome`: the card form at 390 px, the labels, the roles, no sideways scroll, and the new prices.
307
308## 4.232.0 — 2026-11-12
309
310**`ov-metrics.js`: the network's one way to show numbers** (ai-metrics.md). Report, Exchange, Compare and the admin panel
311draw their AI and usage numbers with it, and nobody draws a chart of their own. The look is the theme's (tokens only).
312Every piece works at 390 px and in both themes, and stays still with motion off.
313
314- `OVMetrics.tiles()`: label, value, unit, a delta that knows whether up is good, a sub-line and a trend.
315- `.spark()`: a trend line with its last point.
316- `.bars()`: a ranked bar list.
317- `.percentiles()`: p50, p90 and p95 on one scale, with a spoken summary.
318- `.split()`: one bar in parts, with a legend.
319- `.table()`: sortable on a click, with `aria-sort`, numeric columns and an optional in-cell bar.
320- `OVMetrics.format`: the same words everywhere, and servers get them from `require('openvibe-shared/ov-metrics')`:
321 - `ms` (540 ms · 1.3 s · 1 min 38 s), `usd` ($0.0042 · $61.20), `count` (18k · 9.1M), `tokens`, `rate` (99.4%);
322 - `tps` (86.0 tok/s), `bytes`, `delta` (+12%);
323 - unknown is a dash, never 0.
324- Test `ov-metrics-chrome`.
325
326## 4.231.0 — 2026-11-12
327
328**OpenVibe's own icons, and no Font Awesome drawing left** (the owner, 11-12: "make sure no remnants of font awesome or
329other libraries are still being used rather than our openvibe.design systems").
330
331- **The ring icons' solids are the glyphs' own.** Nine product icons (Network, Live, Tools, Media, Games, Community,
332 Chat, Domains, Bench) drew Font Awesome outlines as their solid twin. Each is now the solid drawing of the glyph
333 that stands for it, on the same 24-unit grid as the line drawings. Exchange keeps its line drawing until `swap` has
334 a solid. `scripts/build-navbar-icons.js` writes them from `glyphs.js` and refuses any drawing that is not a glyph.
335- **The generator that read Font Awesome's font is gone** (`scripts/build-nav-icons.py`; `npm run build:icons` is
336 `build-navbar-icons.js`). The bar's dead 512-grid path in `navbar.js` went with it.
337- **OpenVibe's own markup:**
338 - `<i data-ov-icon="eye">` names any glyph and `<i data-ov-brand="github">` any logo, drawn by `ov-glyphs.js`;
339 - classes `ovg-xs` … `ovg-3x`, `ovg-fw`, `ovg-rotate-*`, `ovg-flip-h`/`-v` and `ovg-fill` (what `fa-solid` did);
340 - `OVGlyphs.icon()`, `.html()` and `.has()` in a page, and `glyphs.markup()` on a server;
341 - the drop-in now carries every glyph (287), and the navbar loads it for this markup too.
342- **The bar draws glyph names:** a name the bar's set lacks is drawn as `<i data-ov-icon>`, never as Font Awesome.
343- **`scripts/fa-to-ov.js`** rewrites a repository's static Font Awesome markup into `data-ov-icon` / `data-ov-brand`.
344 It keeps each attribute's own quote, and running it twice changes nothing. It lists what a person has to decide
345 (names built at run time, JS class strings, CSS rules, animations, unknown names). `glyphs.fromFaClasses()` is its
346 mapping.
347- **Names that drew nothing now draw their glyph:** `arrow-rotate-left`, `chart-pie`, `book-skull`, `crosshairs`,
348 `location-crosshairs`, `satellite`, `globe-americas`, `street-view`, `magnifying-glass-location`, `apple-whole`,
349 `backpack`, `shield-heart`, `openstreetmap`; and the logos `reddit-alien` and `kickstarter-k`.
350- Test `fa-to-ov`.
351
352## 4.230.0 — 2026-11-12
353
354- **OpenVibe's own controls on every site** (the owner, 11-12: "improve the selection boxes to use a more customized look integrated into openvibe.design and themed better with animations and effects for all inputs and buttons"). `ov-forms.css` now draws every checkbox and radio: a rounded box whose check pops in, and a ring whose dot grows. The mark is the box's own `::before` (a clip-path check, scaled), so no picture is fetched. Both lift their border on hover, squeeze on a press and glow with focus; the mixed state is a bar; disabled is faded. A switch (`role="switch"`) glows when on, its knob squeezes when pressed, and it has a focus ring. Buttons answer a press, and text fields, text areas and selects glow in the accent with focus. Everything is `:where()`, so a site's own rule wins; `data-ov-native` keeps the browser's control; motion follows the person (Calm slower, Off moves nothing).
355- The bar loads `ov-forms.css` on any page with a text field, a text area or a button. The select kit's script still waits for a select, a box or a file input.
356
357## 4.229.0 — 2026-11-12
358
359- **Back brings you back to the menu** (the owner, 11-12: "fix the history back button so it properly brings you back to this GUI rather than the page behind it"). Leaving through a link in the menu marks where it was on its history entry (its place, its door, its scroll); Back to that page opens the menu again there, whether the page comes back from the browser's cache, loads again, or a site swaps it in place (Boost). It reuses that entry, so Forward still works, and closing it then leaves the page as it was. Only for a Back or Forward within half an hour.
360- **Links are on their way before the click**: anywhere on the page (the bar, the menu, a footer, a card), a finger landing on or a pointer resting on a link to another OpenVibe site opens its connection at once; this site's pages were already prefetched by the bar's speculation rules.
361- **All apps has one search** (the owner, 11-12: "confusing"): no second box under the menu's pill. The pill reads "Find among 64 apps" and filters the apps in place; the view's head is its name, its count and a hint, coming in with the view. Back on Home, the pill searches OpenVibe again.
362- **The close x has no white ring**: the ring that leaves it as the menu opens is invisible at rest, so where animations are off (a phone's "Remove animations") it no longer stays as a white circle.
363- **The Feed never shows a card twice**: a card id already on the list is skipped (Network names a Deals burst once, as of its #887).
364- **Feed cards with no person wear their site's app icon** (design rule 142), 40 px beside 40 px faces, the glyph filling the square (the owner, 11-12: "icons to increase size").
365- **Any button can open a place in the menu** (the owner, 11-12: "other tools and buttons can open specific areas of the menu"). A menu address is a place, or place/part: `you/notifications`, `you/apps` (All apps), `you/recent`, `feed`, `here/live`, or a door (`vibe`, `chats`, `agent`, `code`, `tasks`). Any element with `data-ov-menu="you/notifications"` opens it on a click, on any page with the bar; a link with an address still opens elsewhere with a modifier key. A page address ending `#ov-menu=you/notifications` opens it as the page loads (on any OpenVibe site), and a same-page link to it works too; the hash leaves the address. `OpenVibeNavbar.openMenu(address)` and `OpenVibeNavbar.menuAddress(address)` take the same addresses; OpenVibeHome's `show('drawer')` opens All apps.
366- **One search field for every site: `ov-searchbox.js`** (the owner, 11-12: "improve the search field … autocomplete, dropdown, animated effects … a shared, universal, reusable search input component"). `<input name="q" data-ov-searchbox="/api/…/suggest" data-ov-examples="streams|clips">` in a form becomes a pill: the search mark, the field, a clear button and a "/" key hint on a computer; its edge glows with focus. The placeholder walks through the examples while idle (Calm slower, Off never). Suggestions come from the source in OVCombo's shape, with icons (search, recent, page, person, live, video, place) or a small picture; while empty, the person's recent searches here. The first suggestion that starts with the typed words shows the rest as a ghost: Tab or → takes it. Enter searches the typed words (the form's action) or the chosen row's page; "/" anywhere puts the cursor in it; Escape closes, then clears. `OVSearchBox.mount(input, { source, examples, scope, recent, onSearch })`; a cancelable `ov-search` event lets a page search in place.
367- **OVCombo grows hooks** for anything that is not a value picker: `pick`, `enter`, `onRows`, `selectFirst: false`, `none`, `limit`, `cacheMs`, `anchor`; rows take `icon` or `image`; the skeleton no longer flashes over rows already showing while a quick answer comes.
368- The navbar's budget is 52 KB brotli (51.6 KB with the menu addresses).
369
370## 4.228.0 — 2026-11-12
371
372- **A page's sections as tabs on a phone** (the owner, 11-11: "consolidate these into tabs, self-contained tabs, so the page isn't so freaking huge … at least on mobile"). `showcase.parts({ name, items: [{ id, label, count, html }], front })` shows one part at a time with a tab bar (its name and count) at 900 px and under, 600 for a front page; every part stays in the HTML (crawlers, AI readers); radios and `:has()`, no script; the arrow keys move between parts; wide screens and browsers without `:has()` stack them. `ov-parts.css` is the same look alone, for a page that does not load showcase.css (Live's home).
373- **`showcase.pick()`: the pick block, once.** Twenty-five sites kept a copy of a "pick block" (server/render/short.js) whose chips only jumped to a pane, so a phone still showed them all. `pick({ key, label, head, panes, checked })` is that block on `parts()`: a heading, then tabs on a phone, every pane in the HTML with its id, `ov-parts.css` linked by its hash. `parts()` takes `checked` too, and a link to something inside a part opens it (CSS `:target`, no script).
374- **Caption it with AI.** A picture in any composer has ✨: one tap sends it, made small (JPEG, 768 px), to the Frame's `/api/frame/caption` on the person's own allowance; the caption fills an empty box (theirs to change) and `ovc:caption` carries the alt text. `ctx.alt(file)` and `ctx.caption(file)` for a page; `data-ovc-caption="off"` leaves it out.
375- **`OpenVibeNavbar.token()`** reads the person's Network token the site gave `init()`. Caption it sends it as the Bearer on any site (the Network's session cookie is not sent cross-site), and the composer's settings sync, which asked for it all along, now finds it.
376- **A picture alone is a post.** The menu's Feed composer no longer asks for a description of every picture: one left empty is written by AI as it posts, and a post goes without one if that cannot be done.
377
378## 4.227.0 — 2026-11-12
379
380- **One app icon, drawn on the server too** (design rule 142). `OpenVibeIcons.appHtml(site, { size, label, hue })` returns the app icon as markup (no DOM needed): its hue and size inline and its glyph already drawn, so `ov-icons.js` finds nothing to fill and nothing moves when it arrives. `OpenVibeIcons.APP_CSS` is the app icon's own rules, sizes 20 · 26 · 32 · 44 · 56 · 64 included: a server-drawn page puts it in its stylesheet once and its icons are whole before any script runs.
381- **Every site's "More on OpenVibe", app launcher and next steps draw sites as their app icons** (showcase `network()`, `apps()`, the next-step list), whole on the server and the same as the menu's: 44 px in a row, 56 px as a launcher tile on a phone. A non-OpenVibe glyph (`fa-…`) keeps its tile.
382- **The square keeps its size before `ov-icons.js`**: the navbar's early rules hold every `.ov-appicon` at its `data-size`, so a page's app icons no longer pop in.
383- **The menu's "Across OpenVibe" sites and a quest's steps wear their app icons** instead of a small glyph in a ring.
384- **The Feed's chips name the one feed's new kinds**: Rundown, Jobs, Deals and Stats, beside the media kinds.
385- **A release reaches a returning visitor within minutes**: an unversioned asset (`cache-policy` `assetHeaders`) keeps five minutes of stale-while-revalidate, not a day. Sites load `navbar.js` without `?v=`, so the first visit to each site after a release ran the old menu.
386- **/updates passes its contrast check again**: a filter chip's count no longer fades to 70% (axe color-contrast, serious, on every site's /updates; the release browser check had paged as critical).
387
388## 4.226.0 — 2026-11-12
389
390- **The menu's feed draws the one feed's new kinds (feed.item@1)** (the one-feed job's finding: they fell through to a
391 bare card):
392 - **A report** is its number, large, with which way it moved and from what (▲ 12%, was 1,075).
393 - **A folded run** (new jobs, new deals) lists its first three, each with its picture where there is one, rows
394 lined up, then "and 11 more".
395 - **What a count means** is said: "14 jobs", "41 voted"; `counts.watchers` reads "watched".
396 - **The Rundown** is about its actor (feed.item@1's `subject` is a person's id, so the old reading found nothing).
397 - A card without a person no longer says its text twice.
398 - `menu-feed-v2-chrome` checks each.
399
400## 4.225.0 — 2026-11-12
401
402**The menu's Vibe is one place: All · Chats · Tasks · Code, no switch** (the owner, 11-12, crossing out the Chat · Code ·
403Agents switch: "one new unified agent … a shared UI").
404
405- **Vibe opens on All:** every run from the Frame (Vibe chats, tasks, coding runs) in one list (`ov-run.js`, 4.224.0).
406 - The Action Center's waiting and running work from every product (Network `/api/v1/me/actions`) joins it, unless a
407 run already shows it. What needs you comes first, approved or declined in place; the rest open their own pages.
408 - Above the list: the Dock's Now and timeline, your Actor and your agent's live view (lent with
409 `OpenVibeDock.mountInto(el, { tab: 'agent', part: 'top' })`).
410 - Vibe's six starters sit above the box; a press starts that run. Enter in the menu's field starts a run too.
411- **Chats, Tasks and Code are the Dock's own panels, lent whole** (Vibe's conversations with voice, undo and the timer;
412 Agents with its history, skills and filters; Code with its repos). They move into the one view when it does all they
413 do, so nothing is lost on the way.
414- The Dock's places open their filter: `OpenVibeDock.open({ tab: 'vibe' })` opens Chats, `'agent'` Tasks, `'code'` Code.
415 The status on the menu button still opens what needs you. `OpenVibeNavbar.openMenu()` also takes `'code'` and
416 `'tasks'`.
417- **`ov-run.js` gains:**
418 - `o.starters`;
419 - `o.actions: { base }`, the Action Center's work in the list;
420 - `o.top(el)`, the host's slot above the list on All;
421 - `o.panes: { chat, task, code }`, filters the host draws itself;
422 - `o.onPlace`;
423 - `view.prefill(text)`, words in the box, unsent.
424- A system light theme darkens the agents' glyphs.
425- **The menu scrolls on a phone wherever the swipe begins** (the owner, 11-12: "scrolling is very buggy … on mobile …
426 especially in notifications").
427 - Inside the menu, the notifications list was a scroll box of its own that held its overscroll. It never overflowed
428 there, so a swipe that began on a notice moved nothing.
429 - Inside the menu it is now part of the menu's one scroller (one scroller per layer).
430 - Measured with real touches at 390 px, and checked on every tab: nothing else nests a scroll box like it.
431 - New test `notifications-menu-scroll-chrome` (it fails without the fix).
432 - `notification-ui.js`'s brotli budget grows from 25.9 to 26.1 KB for it.
433- **The composer's + has GitHub.**
434 - It searches repositories through OpenVibe Exchange's code search (the Frame's `/api/frame/code/search`, Network
435 #878) a quarter second after the last key.
436 - Each row shows the owner's picture (GitHub's avatar host only), the name, the description, the language, stars
437 and the licence, all as text. Enter or a press puts the repository's address in the box, where every site draws
438 it as a card.
439 - A box may point it at another Network with `data-ovc-frame`. `composer-v2-chrome` checks it.
440- **One app icon for every site: `.ov-appicon` and `OpenVibeIcons.app(site, { size, label })`** (design rule 142).
441 - It is the menu's and Home's own icon: the app's steady hue on a rounded square, with its glyph drawn over it.
442 - Use it from markup (`<span class="ov-appicon" data-icon="live" data-size="64">`) or from script. Markup added later,
443 or an app that changes, is drawn again.
444 - With a label it is an image with a name; otherwise it is decoration. `OpenVibeIcons.hue(id)` is the shared hue.
445 `ov-appicon-chrome` checks it.
446- **Home on a phone: a long press then a move keeps the app under the finger.**
447 - Entering Edit repaints the grid, so the tile under the finger left the page, and the grid's touch guard no longer
448 heard that touch: the first move scrolled the menu and cancelled the press.
449 - The guard now rides on the pressed tile itself until the finger lifts.
450 - `menu-home-screen-chrome` drags with a real touch (it fails without the fix).
451- The menu reads runs and the Action Center from the Dock's Network base (`runsBase`).
452- **Tests:**
453 - `dock-chrome` gains the All view at 390 px: the four filters on All; a run and the Action Center's work in one
454 order; the Dock's top lent without its list; the starters; Tasks as the Agents panel, remembered by the Network;
455 approve in place from All. Every Dock check before it still passes unchanged.
456 - `run-thread-chrome` gains the starters, the Action Center's rows (nothing twice), the host's slot, the place and
457 prefill.
458 - `menu-places-chrome` and `menu-home-screen-chrome` follow the filters in place of the switch.
459
460## 4.224.0 — 2026-11-12
461
462**One thread view for every run: chats, tasks and code** (the owner, 11-12, crossing out the menu's Chat · Code · Agents
463switch: "one new unified agent … a shared UI … a full conversation with rich embedding").
464
465- **New module `ov-run.js` (`OpenVibeRun`)** draws any run from the Frame's Run API (Network `/api/frame/runs`; Contracts
466 `agents.run@1`, `agents.step@1`), on every site:
467 - `OpenVibeRun.mount(host, { base, token, me, sibling, examples, start, filter, open })` returns
468 `{ list(filter), open(ref), start(text), destroy() }`.
469 - `OpenVibeRun.card(type, render)` adds a rich card for a step's `ui.type`; built in: `results`, `table`, `link`,
470 `code`. A card it does not know shows the step's words.
471- **The list:**
472 - Filters: All · Chats · Tasks · Code.
473 - Order: what needs you first, then newest.
474 - Each run shows its agent's face (its picture, or its glyph on its own hue, with a badge saying what it is doing),
475 its title in the brand face, its last line and when.
476 - Pills say "Needs you", "Working" and "Did not finish". A product that did not answer is named under the list.
477 - A run that asks before it acts is answered from the list: Approve or Not now, in place.
478 - A filter reloads only the rows, so the words you are typing stay.
479- **The thread:**
480 - Your words on the right. The agent's on the left: paragraphs, lists, bold, inline code, links, and numbered
481 sources whose [n] marks link to them.
482 - Thinking is folded. Each tool is one quiet row (it opens to its output).
483 - An approval is a card with its answers. Child runs are rows that open them; files are chips or pictures.
484 - It follows the run live: server-sent events read with fetch, so the bearer goes along, resuming after the last step
485 it has.
486 - The composer underneath says the next thing. A new run starts from the list's box, sent once with an idempotency
487 key.
488 - Page text is never markup, and only https links are drawn.
489- **The menu keeps the Dock's panels for now.** Its Vibe moves to this view (and the switch goes) once the view does
490 everything those panels do:
491 - the Action Center's items from every product, approved in place;
492 - the live agent view (Pause, Curtain, Mute, End);
493 - Vibe's starters and voice;
494 - "Ask your agent" from a page;
495 - the place the Network remembers.
496- **Arrange:** Stop is an ordinary optional part; it docks right like any other (#501).
497- `ov-run.js` and `ov-composer.js` have content hashes in `SIBLING_REV`, ready for the menu. Every site serves
498 `ov-run.js` (`files.js`), and the package exports `openvibe-shared/ov-run`.
499- **Tests:**
500 - New `run-thread-chrome` (390 and 1280 px): the list and filters; approve in place; every kind of step; the live
501 stream and where it resumes; approvals, messages and a new run reaching the API with the person's token; no markup
502 from page text; no overflow.
503
504## 4.223.1 — 2026-11-12
505
506The menu keeps its search field and tabs (the owner, 11-12, two phone screenshots: "the gui gets messed up and loses
507its tabs", "Missing top tabs").
508
509- **What went wrong:**
510 - 4.223.0's slow light behind the menu is drawn 20% past the panel's edges, and the panel was `overflow: hidden`.
511 - A hidden-overflow box is still a scroll container, so a focus or a scroll into view inside the menu scrolled the
512 whole panel up: the field and the tabs left the screen, with an empty band at the bottom.
513- **The fix:** the panel now clips (`overflow: clip` is never a scroll container), and any scroll an older engine
514 gives it anyway is put back at once.
515- "Your own AI" keeps its name on one line; its sentence takes the wrap.
516- **New test** `menu-panel-still-chrome`: at 390 and 1280 px, a focus deep in the site list moves the list, never the
517 panel.
518
519## 4.223.0 — 2026-11-12
520
521You redone as tabs, icons that fill their squares everywhere in the menu, names in the brand face, and one composer with
522a + for every tool (the owner, 11-12, a run of phone and desktop screenshots: "the notifications area is ultra buggy …
523move the notifications into a tab … put the apps more front facing"; "make the icons bigger so they actually take up
524more of the square … fix the alignment"; "waste of space … compact"; "fix the mic to the left … the timer should be in
525the plus thing … usable on all composers across all sites").
526
527- **You:**
528 - **One top:** your row, then your AI. Side by side when the menu is wide. Your row holds every round button, with
529 this site's own (your channel, Admin, what a site adds) first as icons. The lone "Channel" row is gone.
530 - **Three tabs:** Apps (first) · Notifications · Recent. One row, with All and Edit at its end; no "Your home" head.
531 - An ink slides to the chosen tab and the tab's content slides in from its side. Calm fades; Off stills.
532 - On a phone, only the chosen tab says its name. The arrow keys move between tabs.
533 - **Notifications** is a tab:
534 - the whole list, with its own search, filters and Mark all read;
535 - it loads `notification-ui.js` itself when the tab opens first (it said "unavailable");
536 - its count follows the bell live.
537 - **Recent** is the whole history:
538 - newest first, in days (Today, Yesterday, This week, Earlier);
539 - each place a card with its whole title, its site and when, and LIVE when it is live;
540 - two columns when wide, Show more, and Full history to openvibe.my/history.
541 - **Your AI:**
542 - its face shows what it is doing (reading, writing, searching, coding, waiting for you…) as a glyph;
543 - it breathes while working;
544 - Open is a round arrow and Answer is a pill;
545 - not yet set up, it is one slim row.
546 - **All apps:** back is a round arrow, with the search beside it. Each group is marked with its glyph and its count.
547- **App icons, everywhere in the menu** (You, the site list, search rows, the feed):
548 - the site's hue on a rounded square, with the glyph at 58% of it;
549 - drawn absolutely, so no hover can move it off centre;
550 - a folder's four apps sit in an even 2×2 (they were offset and cut off);
551 - a + to add an app sits on the icon's corner;
552 - a mouse lifts an icon and lights it in its hue, and a press sinks it.
553- **Names in the brand face:**
554 - app, site, tab and section names are in OV Brand (Nunito 800, loaded by the navbar on every site); words you
555 read stay in the UI face;
556 - names wrap to two balanced lines and never end in "…";
557 - section heads are names, not small capitals.
558- **The site list** (a site's own tab) is a grid of app icons: four across on a phone, as many as fit beyond. Each
559 section wears its glyph.
560- **The feed:**
561 - a recording's length reads as a time (5203 → 1:26:43);
562 - the Rundown is its own card: "The Rundown · about Maticus", its mark, a ribbon on its picture, and a grade, so it
563 never reads as the stream's card;
564 - the kind glyph beside the time is 18 px.
565- **One composer** (`ov-composer.js`, every box on every site):
566 - **The row:** the mic first, then the + and the box's first tool on a phone; the + opens every tool of the box, big
567 and named. It lists the row's own tools, the mods' tools and the extras.
568 - **Timer:** a tool in every box's + (`OpenVibeComposer` TOOL `timer`, `extra: true`). Vibe's switch row no longer
569 has a timer of its own.
570 - **Examples:** an empty box shows what people do with it, in turn: `examples`, or a preset's own, or
571 `data-ovc-examples`. Calm is slower; Off keeps the first.
572 - **Send:** the round send is an arrow up, in the accent when there is something to send.
573- **Vibe:**
574 - its box is the same composer, with its hold-to-talk mic first and then the +;
575 - a link in your own message reads (it was blue on blue);
576 - Chat · Code · Agents is one segmented control with a sliding ink, at 15 px with 20 px glyphs.
577- **The menu:**
578 - Close is a round × on every screen; the phone's close orb is glass with a hairline edge, never a thick ring;
579 - a slow light drifts behind the panel (Calm slows it; Off stops it).
580 - `OpenVibeNavbar.openMenu(place)` opens the menu at a place: `notifications`, `recent`, `apps`, `you`, `feed`,
581 `vibe`, `chats` or `agent`. A site's own links use it (openvibe.my's Notifications tile clicked a bell that is gone).
582- **Times:** a time without a zone is UTC. Network's history sends "YYYY-MM-DD HH:MM:SS", and browsers read it as local
583 time; west of UTC every visit said "just now".
584- **The notification rows:**
585 - one way to clear a notice (×); a tap reads it, and a swipe still offers Read or Clear;
586 - the title and its ×N count are one run of text (the count overlapped the words).
587- **Tests updated:** menu-home-screen, menu-places, menu-close, dock-chrome (the timer through the +), composer-v2,
588 composer-camera, inline-composer.
589
590## 4.222.0 — 2026-11-12
591
592The wallet sheet redesigned for phone and desktop, every phone page back to its true width, and whole site names in the
593menu (the owner, 11-12, a phone screenshot of the wallet marked "UGLY" twice; "make everything a little more
594user-friendly and elegant … perfect the layout … across all sites").
595
596- **The wallet:**
597 - **Balances first.**
598 - **What changed reads as rows:** one per currency, with its mark, a counted summary ("Quests done ×10") and when, and
599 the amount in its colour ("+161k"). Each change, with its own amount, sits in a fold ("See all 10"). Before, every
600 quest was strung into one paragraph.
601 - **Every section has a head with its glyph:** What changed, Recent activity, Store, Your items.
602 - **Empty parts are a small card with their mark** saying what will appear (openvibe.design rule 141), never a grey
603 line under a heading.
604 - **Recent activity in the same hand:** the currency's mark, the reason as a 44 px link (no underline across the row),
605 its first letter up, the amount in its colour.
606 - **Store prices are pills with the currency's mark**, each named in full for a screen reader; "Owned" is a green
607 badge; pictures and names sit closer.
608 - **On a computer, What changed and Recent activity sit side by side.**
609 - The head is the wallet's mark, "Wallet" and "Yours on every OpenVibe site".
610 - The sheet's marks are the registry's glyphs (`test/wallet-glyphs.test.js`).
611- **The bar never widens a phone page.** Every OpenVibe page was 392 px wide on a 390 px phone (so phones zoomed out a
612 little): Sign in's larger tap area reached 8 px past the bar's edge. `.openvibe-navbar { overflow-x: clip }`;
613 dropdowns hang below, so only the sides clip.
614- **The menu keeps a site's whole name:** a long one takes a second line instead of "Downlo…".
615
616Tests: `wallet-chrome` and `purse-catchup-chrome` (the new rows), `wallet-glyphs` (new); the wallet fixture can give a
617rich history (`changes: 'many'`).
618
619## 4.221.0 — 2026-11-12
620
621The icon system, audited end to end (plan L32 I1/I2; the owner, 11-12: "Broken icon. Improve the icons that exist, add
622more icons, and improve the icon system in general").
623
624- **No broken icon anywhere.** `scripts/icon-audit.js` reads every OpenVibers repository's main and lists every icon
625 name that does not resolve: `fa-*` classes, `kit.icon('…')`, `glyphs.*('…')`, and the `icon:` fields in NAVs, menus
626 and manifests. A site can run it from the package (`npx ov-icon-audit --root .`) and fail CI on a miss; the audit
627 found and this release fixes `fa-shoe-prints`, `fa-calendar-check`, `fa-cloud-arrow-down`,
628 `fa-magnifying-glass-chart`, `fa-magnifying-glass-dollar`, `fa-plug-circle-plus`, `fa-power-off` and `fa-receipt`.
629- **`kit.icon(name)` draws every glyph in `openvibe-shared/glyphs`**, not only the kit's own 86: the rest travel with
630 kit.js as a generated block (`scripts/build-kit-glyphs.js`) and a test checks the block is current. `compass`, `chip`,
631 `gauge`, `layers`, `route`, `scale` and `flask` no longer draw an empty `<svg>` through the kit.
632- **Nine new glyphs**, each with a solid twin: `binoculars`, `telescope`, `microscope`, `gpu`, `cpu`, `memory`,
633 `joystick`, `wand` and `layers-plus`; `wand-magic-sparkles` now draws the wand, and `ram` maps to `memory`. All are
634 centred and sized to the set (checked by test/glyph-metrics, test/icon-ink), reviewed at 16/20/24 px in light and
635 dark, and rebuilt into the sprite, CSS, web/desktop fonts and code points.
636- **Keywords for every glyph.** `glyphs.META[name].tags` carries the Font Awesome names a glyph stands for plus its
637 synonyms (for the icon pages and the suggest API), so every glyph has searchable words.
638
639## 4.220.0 — 2026-11-12
640
641**One UI: the floating Dock is gone; the menu holds Chat and Vibe on every screen** (the owner, 11-12, with a phone
642screenshot of the Dock crossed out: "there should only be the one unified UI … we're supposed to delete … that has the
643old tab system", and the menu marked "the one unified"). And the Vibe place redesigned (the owner: "sleek … nice on
644mobile … organized properly and sized well … without all these huge spaces … makes use of the full space … icons for
645everything using OpenVibe.design … modular … reusable for different sites").
646
647- **Gone:**
648 - the Dock's pill;
649 - its floating card with the Vibe · Code · Agents · Chat · Activity tab bar, More, pin and Close;
650 - corners, drag and resize;
651 - the phone sheet that opened out of the menu button;
652 - the window of its own (pop-out);
653 - the You tab's "The Dock" row;
654 - the Activity tab (the menu's You tab has the notifications; `OpenVibeNotifications.renderDock` is gone with it).
655 `dock.js` is 703 lines, from 1,129.
656- **The menu's Chat and Vibe open inside the menu on every screen.** A computer opened the floating Dock.
657 - Vibe's Chat · Code · Agents switch has the timer as an icon at its end; it was a full row under the box.
658 - On a computer the menu grows tall while a place is open (`min(820px, 86vh)`).
659 - `OpenVibeSearch.open({ door })` and `OpenVibeSearch.door(id)` open the menu on a place, or move an open menu there.
660- **`OpenVibeDock.open({ tab })`, `ask({ text })`, `share({ text, title })`, Alt+V and `OpenVibeNavbar.openDock(id)`**
661 open the menu on that place (`opts.openMenu`, given by `navbar.js`); `close()` closes the menu. A place in Your places
662 (Vibe, Agents) opens there too.
663- **What the pill said is a status on the bar's menu button** (`opts.status`, painted by `navbar.js`):
664 - a breathing accent dot while agents work;
665 - an amber one when one needs you (the button then opens the menu on Agents);
666 - a timer's minutes in a small label under the button.
667 The tooltip and the button's name say it in words. Calm slows the breathing; Off stops it.
668- **Vibe, redesigned:**
669 - a header that says what it is: back, Vibe's mark, "Vibe" over "Your AI on every OpenVibe site", New chat and Open
670 in Chat as icons;
671 - a greeting at the top ("What can I do for you?"), and a new chat opens there;
672 - six starters as full-width rows with their marks (two columns on a computer);
673 - the composer's two-row box (the words over the whole width, then picture, Build it in Code, the mic and a round
674 send);
675 - result cards with their actions under the words;
676 - long links wrap.
677- **Every Vibe mark is the network's glyph** (`openvibe-shared/glyphs`, shown on openvibe.design/icons); so is the
678 Dock's send. `test/dock-vibe-glyphs.test.js` keeps them equal.
679- **Direct messages take the network's message actions** (openvibe.design rule 138, as Live's chat and `messages.js`):
680 - Edit is a pencil beside your bubble on its empty side, on hover or focus, or a tap on a phone; it never covers words;
681 - the box is the editor, under an "Editing your message" bar (Escape or × lets go); the browser's prompt is gone;
682 - "edited" is one quiet word inside the bubble;
683 - `dock-chat-edits.js` keeps only Chat's socket.
684- `bottom.js`: the pill's stacking entries and `--ovb-composer` (which only the pill read) are gone.
685- **One head, not two** (the owner, 11-12: "part of the one UI instead of having two different areas"): inside the menu a
686 place has no header band of its own. An open conversation's + and ↗ move into the menu's search pill (as a list's
687 already did); it keeps a slim way back and its name; Vibe's mark is the greeting's.
688
689Tests:
690- Rewritten for the menu: `dock-chrome` and `dock-vibe-timer-chrome` (no pill or floating Dock ever; the status on
691 the menu button; Agents, Chats, Send to, the agent view, Vibe, voice, the timer and Ask your agent through the menu),
692 `dock-mount-chrome`, `dock-signin-chrome`, `menu-places-chrome` (Chat and Vibe inside the menu at 390 and 1280),
693 `menu-home-screen-chrome`, `bottom-chrome` (no pill).
694- New: `dock-vibe-glyphs`.
695- Gone: `pill-composer-chrome` (the pill it kept above a chat's box is gone).
696
697## 4.219.0 — 2026-11-12
698
699Every plain element styled by the theme (the owner, 11-12, with circled screenshots of openvibe.exchange: "make sure all
700basic elements are styled … custom with the theme system … update the little dots … reflect and improve all existing
701elements … the spacing could be improved").
702
703- **Radios and checkboxes are drawn** under `.ov-base`: a ring in the theme's line colour, the site's fill when chosen,
704 the radio's dot growing in and the checkbox's tick popping in (Calm keeps the change, Off removes the movement), a
705 focus ring, and a label that holds its control is one tappable row.
706- **Text fields**: the placeholder in the muted colour, an accent ring on focus, room under each field.
707- **A plain details** (no class of its own) gets a chevron that turns when it opens, and no browser marker; a site's own
708 details keeps its own.
709- **A site's own questions in a showcase section** (a plain `<details>` straight in `.sc-sec`, as 20 homes have them)
710 look like `sc.faq`'s: a card each, no browser marker, a chevron that turns, the answer padded (unstyled-sweep found
711 them on Blog, Chat, Compare, Coupons, Deals, Download, Food, Games, Host, News, OpenRestream, Reviews, Run, Space, Tips,
712 Trade, VIP, Vision, Wiki and Zone).
713- **Words straight in a kit section** (`<p>`, `<a>`, `<details>`, lists, quotes, code) keep the section's padding; they sat
714 flush against the box's edge.
715
716Test: `kit-layout-chrome` (the drawn radio and its dot, the filled checkbox, the plain summary's chevron, a section's
717padding).
718
719## 4.218.0 — 2026-11-12
720
721One composer, three verbs: **Post · Ask · Do** (research-2026-11-12/composer-post-ask-do.md; the owner, 11-12: "make it so
722that the composer is able to … integrate directly to have the models list from OpenVibe.Exchange … the search the web
723feature, the search engine feature … more cohesive, interconnected", and "Actor should be more of an orchestrator … same
724features across all different sites").
725
726- **`verbs: ['post', 'ask', 'do']`** (a site opts in; the first is where the box starts) draws one switch: on a post or
727 a reply beside the name, in a chat or an agent's box at the start of the row; on a phone the chosen verb keeps its
728 word and the others are icons. **Ask**: an AI answers you; **Do**: Actor does it for you (the orchestrator, handing
729 parts to Codes, Bot, your computers); **Post** (Send, Reply) is unchanged.
730- **Ask and Do's chips**, a line of their own above the words:
731 - **Model**: a sheet (the whole width on a phone) over the Frame's `/choices?task=`: Auto, then OpenVibe's models
732 from Exchange's directory with their price per million and a link to each model's page on openvibe.exchange, your
733 keys, your computers, your plans; a find field; the choice is remembered per task;
734 - **Sources**: Web, OpenVibe, Mine, This page (when the site says what the page is), as switches;
735 - **Depth**: Quick, Balanced, Deep research.
736- **The answer card** under the box: the answer streamed from the run's events (AG-UI over
737 `/api/v1/runs/:id/events`), its sources as numbered chips, the model and the cost, and three ways on: **Post it**
738 (the answer becomes the box's own post or message), **Continue in Vibe**, **Do this**. An Ask never reaches the
739 box's onSubmit.
740- Without `onAsk` / `onDo` the box asks the Frame (`POST /ask`, `/do` at `frameBase`, default
741 https://openvibe.network/api/frame); a site that answers itself gets the same card (`api.card`: append, set, cite,
742 done, fail). The api adds `verb()`, `setVerb()`, `model()`, `sources()` and `answer()`.
743- **The inline Community thread is the comment preset** (#490): `thread.composer({ preset: 'comment' })` marks the
744 server form, and `ov-thread.js` replaces it in place with `OpenVibeComposer.mount(..., { preset: 'comment' })` for
745 every `[data-ovc-thread]`: the action, a reply's `parent_id`, files and the no-JS post stay. Video, Pics and a paste's
746 comments are the same box, once.
747
748Tests: `composer-verbs-chrome` (the switch, the chips, the Model sheet from a fake Frame with prices and Exchange
749links, find and pick, Sources, an Ask streamed into the card with its sources, Post it, a site's own answer, 390 px),
750`inline-composer-chrome` (the inline thread mounts the comment preset, keeps its action and `parent_id`, posts at 390 px).
751
752## 4.217.0 — 2026-11-12
753
754The composer gets what the agents portal's own box had: a camera, Try again, and pictures made smaller before they go.
755
756- **A camera in the box** (`camera`, in the post and agent presets; any site can name it): a sheet over the page, the
757 whole screen on a phone, with the picture, Photo or Video, the shutter, Switch camera and Close, as a phone's camera
758 app has. A photo or a clip (up to a minute) goes into the box as a file, nothing kept or sent before the message is.
759 It opens through the shared capture core (`ov-capture.js`, loaded from beside the composer the first time), so its
760 help when a camera does not open is the same plain help Live's go-live page gives; the tool is not drawn where the
761 browser has no camera.
762- **Try again on a failed upload**: the file's own picture (or icon) becomes the button, and a post's picture tile says
763 "Try again" across it.
764- **Pictures made smaller before they upload**: `resize` (the long side in pixels; `data-ovc-resize` on a box made by
765 hand): 2560 for a post, a chat or a reply, 1568 for an agent (the size a model reads, as the agents portal sent them).
766 JPEG, PNG and WebP only; a smaller file is never swapped for a bigger one; GIFs and SVGs are left as they are.
767- **Fixed:** in the one-line chat box, the "Editing your message" and "Replying to …" bar is a line of its own above the
768 words (4.216.0 drew it in the box's first column, over the words).
769
770## 4.216.0 — 2026-11-12
771
772One capture core for every camera, and a chat message's actions in one place on every site.
773
774- **`ov-capture.js`: `window.OVCapture`, one way to open a camera, a microphone or a screen, take a photo and record a
775 clip**, shared by Live's go-live page, OpenVibe.Cam's recorder and `<ov-camera>`, and the composer (the owner, 11-12:
776 Live's capture must be "completely customize[able] … highly over engineered modular editable", then "OpenVibe.cam is
777 actually fine … Harmony"). It began as Live's own system (OpenVibe.Live #847) and every site serves its own copy at
778 `/shared/ov-capture.js`, so a camera never waits on another site, a content policy or a cache (from 11-09 to 11-12
779 nobody could go live because Live loaded Cam's kit from openvibe.cam and the browser refused it).
780 - `open({ mode, facing, lens, mic, resolution })` runs the strategies in order (together, then each on its own, then
781 anything), each with a time limit; a refusal stops at once; the failure that says most is the one reported.
782 - `screen()`, `photo(stream, { mirror })`, `record(stream, { maxSeconds })` → `{ stop() → Blob, cancel() }` (the first
783 format the browser supports), `devices()`, `meter()`, `network()`, `can()` (what this browser allows here).
784 - `explain(error, mode, { instead })`: plain steps for each browser (Safari's "aA", Android's site settings,
785 Firefox's blocked icon, an app's in-app browser, a Mac's Screen Recording switch), with a last step of the site's
786 own.
787 - Settings: this device first; `settings.sync({ load, save })` lets the choices that mean the same anywhere (facing,
788 resolution, mirror, effects) follow the person, for example through OpenVibe.Cam's camera settings; camera and
789 microphone ids never leave the device.
790 - Every part (`use(name, impl)` returns the one it replaced), every strategy (`strategies.add/remove/replace/reset`)
791 and every event (`on('opened' | 'failed' | 'settings' | 'synced' | 'recorded' …)`) can be changed; it fetches
792 nothing. A newer copy replaces an older one, never the reverse.
793- **A message's actions are one cluster at its corner** (openvibe.design rule 138; the owner, 11-12, with phone
794 screenshots of a stream's chat: "The edit button needs improvement … so it never happens again", "it also looks
795 stupid as shit after you edit it"). `messages.js`:
796 - `render(root, { …, actions, onAction })`: `actions` names what to offer (`reply`, `edit`, `react`, `translate`,
797 `copy`, `delete`; edit and delete on your own messages only) as icon buttons in one cluster just above the
798 message's right end, on hover or focus on a computer and a tap on a phone (thumb-sized there), never in the words;
799 a press calls `onAction(id, message, row)` and bubbles `ovm:action`.
800 - `actions(row, ids, { onAction, message, mine })` gives a site's own rows (Live's chat) the same cluster.
801 - `edited(textNode, at)`: "edited" is one quiet word right after the words, the time as its title, never a line of
802 its own.
803- **The composer edits and replies in the box**: `api.edit({ text, onSave, onCancel })` puts your message's words in
804 the box under an "Editing your message" bar (Enter saves through `onSave`, never `onSubmit`; Esc or × puts back what
805 was in the box; the send is a check); `api.reply({ name, text, data })` shows "Replying to …" and the send carries
806 `reply: data`; `clearBar()`, `editing()`.
807
808## 4.215.0 — 2026-11-12
809
810Composer v2: one elegant composer that every site mounts (the owner, 11-12: "this composer is completely ugly and
811broken … doesn't have the file attachment … it needs to be super elegant, like some kind of social media platform … a
812shared composer … interconnected through all sites with all the features we might ever need … really modular").
813
814- **`OpenVibeComposer.mount(host, options)`** draws a whole composer from a preset, and everything `enhance()` gives
815 comes with it (files by the picker, a paste or a drop, uploads with progress, `/` commands, `@` mentions, voice,
816 drafts, the keys, your settings):
817 - **post**: one line with your picture, your name and who sees it (a chip), then the words over the full width, a box that grows with the words, pictures as tiles
818 each with its description and files as chips under the words, photos, files, emoji and voice, the ring that fills
819 toward the limit (amber near it, red past it) and Post;
820 - **chat**: one line that grows, attach, emoji, voice and a round send;
821 - **agent**: a roomy box, attach and voice, the mode chip and a round send (Actor, Codes);
822 - **comment**: the small one, with your picture and Reply.
823 - `onSubmit({ text, files, uploads, audience, mode }, api)`: the box clears after it; `false` (or an error, shown in
824 the box) keeps it. `audience` and `mode` are chips over the browser's own list.
825- **For agents** (from the agents portal's own box): `chips: [{ name, label, icon, options, value }]` for any number of
826 choices (Model and Reasoning beside the mode), all sent as `chips` by name; `setRunning(true, onStop)` turns the send
827 into Stop while a run works (a click stops, nothing is sent), `setRunning(false)` brings it back.
828- **Tools are modules**: `OpenVibeComposer.tool(id, { label, path, accept | run(ctx, button, form) })` adds or replaces
829 one for every box; a preset or a site names the ones it shows (`tools: ['photo', 'file', 'emoji', 'poll', 'voice']`).
830 Built in: photo, file, attach, emoji (a panel of 90 at the caret), voice. `presets()` and `tools()` list them.
831- **The Feed's composer is the post preset.** Its own + drew an icon with no stroke, so it could not be seen; the
832 Feed's posting (pictures with their descriptions through the frame, other files to Media) is unchanged.
833- `enhance()`: a box takes a file any of its pickers takes; a mounted box grows with its words; the file strip sits
834 beside the box wherever the box is.
835
836Test: `test/composer-v2-chrome.test.js` (the four presets at 390 px, a post's chip above and pictures under, onSubmit
837and false, the ring, emoji, a site's tool); `menu-home-screen-chrome` reads the Feed's note inside the composer.
838
839## 4.214.0 — 2026-11-11
840
841A train of four pull requests: the Arrange editor (#481), one inline comment thread for every site (#479), the bar's first paint following the sign-in hint (#482) and four redrawn glyphs (#484).
842
843- **The Arrange editor: a person's own controls, on the real parts.** `ov-arrange.js` is loaded only when someone
844 presses Arrange, and loads `ov-arrange-core.js` (openvibe-sdk/ui-layout for the browser, generated by
845 `scripts/build-arrange-core.js`) from beside itself by content hash. A surface's `[data-ov-part]` elements are
846 placed from a ui.layout@1: drag to move on the grid, the corner to resize, arrows to move and Shift with the
847 arrows to resize (a docked overlay chooses an edge the same way). The part's sheet has visible, size, tint, shape,
848 opacity, label and bindings; long press opens it on a phone. The SDK rules refuse a bad move with the reason in
849 plain words (Stop has to stay visible; it is too small), every move and resize is announced, and Undo, Reset to
850 default, Save and Share are in the bar. Ask OpenVibe's ui.layout.suggestion is previewed on the real parts and
851 applied only with Keep; Motion Off shows no animation and Calm plays the short one. (#481)
852
853- **One inline comment thread for every site: `thread.renderThread` and `thread-inline.css` (new), beside the
854 composer.** MediaHub's Video and Pics item pages and Community's paste and `/c` pages rendered two copies of the
855 same thread; the renderer now lives in `openvibe-shared/thread`, with both sites' options:
856 `attachmentsAllowed` (Pics and a paste page offer files; elsewhere the composer does not) and `draft` (the message
857 kept after a failed post). It draws the quiet head, Top and Newest only when there is something to order, the
858 composer or one sign-in line (the whole empty state when nobody has commented), the comments with their replies,
859 targets, deletions and attachments, More, and the discussion link. The markup is byte-for-byte what the two sites
860 render; each keeps its own post, delete and sign-in routes. The stylesheet is served like the package's other CSS
861 (`/shared/thread-inline.css?v=<content hash>`, `files.js` STYLES), so a site pins one look. Both sites' renderer
862 tests are here; MediaHub and Community delete their copies and pin this release. (#479)
863
864- **The bar's first paint follows the sign-in hint (asks 327, 345).** When `ov_sso_hint` says `account`, navbar.js
865 draws the signed-in shape at once: a neutral placeholder avatar, and the account's own name/initial only when the
866 hint carries it (`account:<username>`); the session check then replaces it or corrects the bar to guest. A failed
867 check no longer leaves the optimistic bar behind, and an explicit `guest` hint keeps the guest bar and starts no
868 silent round trip. The hint is never authorization: it holds no token and is not remembered as an account.
869 sso-client.js stores the name after a FedCM sign-in when the response has one. (#482)
870
871- **Glyphs: care, fish, handshake and eye-off redrawn for 16 px** (the last four the 11-11 glyph review named weak): care keeps its heart clear of an open hand; fish has an open V tail and a dot eye; handshake drops the tiny finger strokes and gets its first solid twin; eye-off breaks the outline where the slash crosses (#484).
872
873## 4.213.0 — 2026-11-11
874
875- **The menu button's flight, polished** (the owner, 11-11: "the icon becomes whiter than it was when idle … needs
876 general polish … effects when it lands … improve both the open and close transitions"). search.js's flight now
877 copies the pressed button exactly as it looked before the press (every computed paint frozen onto the copy: the
878 navbar's colour rules stop at its edge, and the press had already turned it bright and half into an x). It drops
879 first and glides into its tab, swelling a little and tilting into the turn with a short trail and a soft glow; it
880 lands with a squash and a ring; flying back it does the same on the bar button. The menu's circle now grows from the
881 button around the flight (it was cut short), the close orb turns in once the button has left, and turns away when
882 closing with a button flying back. Calm plays the same path short, with no trail, tilt or glow; Off skips it.
883- **The carried Live card, designed** (the player that keeps playing across sites, 4.212.0): at rest a live card keeps a
884 small red dot in its corner; playing without sound (a browser that would not carry the sound over) it shows one
885 "Tap for sound" chip, at rest too, that turns the sound on (only its icon on a card under 200 px); "Open the full
886 page" draws a page with an arrow out of its corner, not fullscreen's two arrows.
887- **A menu in a rail opens in the top layer** (Community's paste cards, 11-11: the ⋯ menu was cut off). A rail's edge
888 fade is a mask, which hides even a fixed menu outside the rail's box; kit-web.js now shows a rail's `details.ovk-more`
889 menu with the Popover API, placed under its button, and keeps the fixed placement where the API is missing.
890
891## 4.212.0 — 2026-11-11
892
893A train of four pull requests (one line each):
894
895- **A carried Live player can play on any OpenVibe page that permits Network frames.** The portable card keeps the
896 original Live embed and channel addresses in its state, while its iframe loads Network's narrow Live player bridge;
897 sound controls and state events pass through that frame, so no destination's frame policy changes (#473, ask 360).
898- **The bar catches up on balance changes across sites (ask 273).** Once a signed-in wallet loads, its pill plays each
899 currency's net gain or loss once and marks that currency's cursor read through Network's frame relay; Calm keeps one
900 quiet effect, Motion Off updates the figures only. The Wallet sheet starts with the recent net amounts and the API's
901 plain reason names (#474).
902- **The Dock's pill stands above a page's bottom composer.** bottom.js measures a composer docked at the bottom of the
903 screen (`form[data-ovt-composer]` or `data-ov-bottom-dock`) and writes `--ovb-composer`; the pill keeps above it as
904 it grows and comes back when it goes (on openvibe.chat it covered the send button) (#475).
905- **Glyphs: focus and bridge redrawn** after the all-glyph review: focus is a viewfinder round its centre, bridge two
906 towers, the cable and the deck; both have solid twins, drawn to the set's size (the long side 0.79 of the box);
907 the drop-in, sprite and fonts are rebuilt (#476).
908
909## 4.211.0 — 2026-11-11
910
911- **The menu's You tab leads with you (ask 158b, "the You tab still sucks").** Its notifications are a preview of the
912 newest three, with no search, filters, foot or Load more until "Show all N" opens the whole list in place; the
913 person's bar, their AI and the rest are a glance below instead of a thousand pixels down. The person's name and handle
914 wrap instead of being cut (rule names-are-never-cut).
915- **An agent's sheet (agent-sheet.js, new; the Dock's Actor row opens it).** One window (ov-window.js: a panel beside
916 the row on a computer, a sheet on a phone) with everything about one agent: its face, state and instructions, then
917 Now, Asks (first when it waits for you, with the count), Work, Schedule, Memory and Computer as tabs (arrow keys move
918 between them), each with its own words when empty; the mark's wait while it reads and Retry when a read fails. Data:
919 Contracts actor.agent-sheet@1 / codes.agent-sheet@1 through Network's relay (/api/frame/agents/:id/sheet,
920 /api/frame/codes-agents/:id/sheet) with the bearer, no cookies; all text, never markup.
921 `OVAgentSheet.open({ kind, id, name, token, frameBase, base, from, openHref })`. The row used to leave for the
922 Actor's page; that page is the sheet's Open now (a modified click still goes straight there).
923- **A composer's count shows only near its limit (ov-composer.js).** The last 15% of the box, at most 200 and at least
924 20 characters: a 120-character search box said "120 left" while it was empty (Exchange's /models).
925
926## 4.210.0 — 2026-11-11
927
928- **A page's title and its line wrap before they are cut (kit.css `.ovk-head`).** The title takes up to two lines and
929 its line up to three, instead of one line each with an ellipsis (the Arena's "One prompt, two models, side b…" on a
930 phone). openvibe.design rule names-are-never-cut.
931
932## 4.209.0 — 2026-11-11
933
934**The Quest guide on any page, and the current Live step in the Dock's agent view.**
935
936- **The Quest guide for browser pages (#464).** Pages can start the Quest guide from an opaque `qstep` link once the host
937 resolves the step; it waits for late controls and checks the Events-backed completion read, and the shared navbar
938 starts it from its own sign-in.
939- **The Dock's agent view shows the current Live step (#466)** when the stream supplies one; older streams keep showing
940 the task without a step line.
941
942## 4.208.0 — 2026-11-11
943
944**One move engine for every floating thing: the picture-in-picture's drag, flick, tuck, pinch, corner and place, now
945shared and adopted by the window.**
946
947- **`ov-float.js`.** `OVFloat.attach(el, { frame, key, defaults, aspect, minPx, onChange })` gives any box Live's
948 picture-in-picture behaviour: docked in its frame or fixed over the page, the lift on the first move, a transform
949 each animation frame, the FLIP landing with the spring, a flick, edge snapping, the tuck into a side with its tab,
950 pinch and corner sizing, a double tap, the keyboard (arrows, + −, T, F, Home), fullscreen, reduced motion, and its
951 place as fractions of its frame. The place is kept in localStorage by `key` and in the account through
952 `OVFloat.store` (`GET`/`PATCH /api/me/layout`, the `pip` map); `OVFloat.layout()` and the `ov:float` event say where
953 every box is. Its behaviour CSS is injected once; a component keeps its own look.
954- **A panel window moves, tucks and keeps its place (`ov-window.js`).** A panel's head is an OVFloat handle: drag it
955 anywhere, flick it, throw it past a side to tuck it with a tab, and it opens where it was left. The absolute place
956 older releases wrote stays readable, and the size the corner grip keeps is unchanged.
957- **Live's picture-in-picture uses it (`stream-pip.js`, in the Live release).** The viewer sessions, waits and
958 actions stay Live's; the move, tuck, size, keyboard and persistence are the shared engine's.
959
960## 4.206.0 — 2026-11-11
961
962**The menu keeps a stream playing, the Feed in the menu is searchable with the one composer at its top, and a Code task
963opens in the Dock's step card.**
964
965- **The menu no longer pauses what is playing (#461, ask 360).** Opening the menu on a phone left live video and audio
966 paused under it; the sheet now leaves media alone and the page can read the player's state.
967- **The Feed in the menu (#463, ask 360).** Feed search uses Community's search and examples through Network, with
968 loaded-card filtering when search is unavailable; the example searches are chips that fill the field. The Feed's
969 post form is the one composer (OpenVibeComposer: files, paste, drop, commands, drafts, settings); each picture needs a
970 description and other files upload to Media. Its + and the example chips carry their own styles in the menu (they
971 drew as bare browser buttons in the PR's screenshots).
972- **Chat and Vibe (#463).** The Chat summary's Vibe row is an accessible Open Vibe button that selects the menu's Vibe
973 tab; on a desktop Chat and Vibe open the Dock after the menu closes, and the Dock opens from its pill only when that
974 pill is visible.
975- **A Code task in the Dock (#465, ask 354).** The Dock's Code card shows the task's live steps with the AG-UI step view
976 (agent-session.js card mode) and opens the whole run.
977
978## 4.205.0 — 2026-11-11
979
980**Agents show their work everywhere: the one step view reads AG-UI, compact like the portal, with the one composer to
981talk to the run; and every site's first HTML is crawlable.**
982
983- **The step view reads AG-UI 1.0 (`agent-session.js`, protocol 1.2, ask 354).** Run, step, text, tool call,
984 reasoning, state, activity and custom events, with OpenVibe's words in `metadata.ov` (title, kind, sources): phases
985 with a plan row ("1 of 3"), steps under the agent's own titles (rows under one title show their own target), thinking
986 folded to one line, sources gathered in one strip (site tiles, https links), text streamed in place.
987- **Compact runs.** A group is one line (its state, its words, the step it is on in mono, the chevron), the head says
988 what it is doing now, three dots while it works; `mount(el, { mode: 'card', href })` for a chat message, the Dock or
989 a feed card.
990- **Talk to the run with the one composer.** `mount(el, { onSend(text, { files, uploads }) })`: the shared composer's
991 markup ("+" for files, paste and drop, "/" commands, voice, drafts, settings), loaded from beside the view.
992- **The composer on any page (`ov-composer.js`).** `clear()` and `uploads()` for boxes sent by script, a form reset
993 clears it, and it styles its own tool buttons and row, so it looks the same without thread.css.
994- **`openvibe-shared/crawlable` (was #457).** Static navigation, the network's sites by group, JSON-LD and llms.txt for
995 the HTML a site sends before its scripts run.
996
997## 4.204.0 — 2026-11-11
998
999**Buttons that show their state, windows that resize, the one composer everywhere, players that start sooner, and one
1000place for the network's CSP.**
1001
1002- **Buttons that show what they do and what is happening (`kit.css`, `kit.js`, `kit-web.js`).** Kinds by importance
1003 (hero, soft, live beside primary, quiet, ghost, danger); a count badge, a status dot, a toggle's fill, a progress
1004 fill; attention (the one next step glows now and then); busy → a check, or a shake and red
1005 (`OpenVibeKitWeb.busy()`); hold-to-confirm for destructive buttons; icons that move by meaning. Every motion follows
1006 the motion setting. docs/kit.md#buttons.
1007- **A panel window can be resized (`ov-window.js`).** `resizable: true` adds a corner grip that a mouse, a finger or
1008 a pen drags and the arrow keys move; the size is kept with the place per id.
1009- **The composer carries its own look, and Vibe's Dock box is the same composer (`ov-composer.js`, `dock-vibe.js`).**
1010 Its styles moved out of `thread.css` into the script; Vibe's box gets drafts, "/" commands, the marks and settings,
1011 with `data-ovc-no-files` keeping its picture upload its own. (Was #458, never released.)
1012- **Players start sooner (`player.js`, #454).** The poster stays until a frame is presented; preload follows the
1013 connection; battery-aware HLS attachment with a low first rung. docs/player-startup.md.
1014- **No more macOS flakes from fixed sleeps (tests).** A browser test now checks the page with `eventually()` from
1015 browser-harness (it retries until the page is there, 5 s at most) instead of after a guessed sleep: 382 checks in 26
1016 files, including menu-places-chrome, whose 180 ms sleep against a 140 ms debounce failed on the slow macOS runners
1017 and held back 4.202.0 and 4.203.0. `test/fixed-waits.test.js` keeps the count of sleep-then-check places at zero.
1018- **`openvibe-shared/csp` (#455).** One place for the network's Content-Security-Policy pieces; `connectSources()`
1019 names Media, the composer's upload origin.
1020
1021## 4.203.0 — 2026-11-11
1022
1023- Dock conversations update edited messages and link cards live; authors can edit their own messages.
1024
1025## 4.202.0 — 2026-11-11
1026
1027**One field dropdown, one window for small interfaces, one user card, a measured page end, eight site groups, and the reveal remade.**
1028
1029- **An empty footer column is never drawn (`footer.js`).** A group with no heading or no links is left out instead of
1030 showing as an untitled "0 links" fold.
1031- **One field dropdown (#447).** `ov-combo.js` adds async, grouped suggestions and optional creation; the composer handoff to Code and whole site names in the phone menu are included.
1032- **One window (#451).** `ov-window.js` supplies dialog, sheet and panel modes, and the composer's settings use it.
1033- **One user card (#452).** Shared mention hooks, the profile card, wallet window and display style settings.
1034- **Page end (#442).** A shared bottom measurement keeps the end of a page at 32 px on computer and 24 px on phone.
1035- **Eight site groups (#445).** The footer, rooms, You folders and home pages use the same groups.
1036- **Reveal (#448).** Headings emerge from blur, rules draw, and up to six cards stagger as they enter view.
1037- **Jump back in (#412).** The full history link and private history support are present from the 4.201.0 base.
1038
1039## 4.201.0 — 2026-11-10
1040
1041**Notifications move into the one menu; the mark and the name are one button; one composer and Code on every site.**
1042From the owner's notes of 2026-11-10 (asks 334 to 350).
1043
1044- **The one menu's look (`search.js`).** Vibe's three parts (Chat, Code, Agents) sit in one row on a phone. Notifications
1045 in You have a head: their name, the red "n new", and Show all in the head instead of a dashed box under the list. The
1046 Timer under Vibe and the Dock's actions in You are rows with a glyph, not dashed folds.
1047- **Notifications in You, no bell in the bar (`navbar.js`, `search.js`, `notification-ui.js`).** The bar's bell is
1048 gone: the signed-in picture wears the red unread count, and pressing it opens the menu's You at Notifications, on
1049 phones and computers alike (the menu's You tab wears the red count too). The notification read/unread
1050 BroadcastChannel keeps open tabs in sync. Feed keeps its calm new count and live dot, now synced across same-origin
1051 tabs by BroadcastChannel and the storage event; the menu button shows the Feed indicator too. Messages and Vibe stay
1052 inside the menu on phones and open the floating Dock on computers. The pressed bar button still flies into its tab
1053 (`OpenVibeSearch.open({ from })`, 4.200.0's flight kept whole). The menu's feed counts are the server half's
1054 (Network #790).
1055- **A picture that cannot show becomes its initial (`footer.js`, ask 345).** Any `img[data-ov-initial]` that fails is
1056 replaced by its initial (a capture listener plus a sweep for early failures; CSP-safe). Footer faces and the
1057 account picture carry it: openvibe.locker's footer showed a broken avatar where five sites' `img-src` omit
1058 openvibe.media.
1059- **The player on a phone (`player.js`, `player.css`, ask 288).** The phone rules lost to the bar's sizing block later
1060 in the file, so at 390 px the row ran ~50 px off screen. A last-in-file phone block: speed and PiP out, a 64 px
1061 slider, mute alone under 380 px, position alone under 360 px (the length is now its own span). 1280 px unchanged.
1062- **The quest card module (`quest.js`, `quest.css`, `guide.js`).** The daily quest card from OpenVibe.Live's finished
1063 design, as a component any site can mount: `OpenVibeQuest.mount(host, { load })` returns `{ refresh, el }`, with the
1064 site fetching the quest shape its own way under the person's Network token. Signed out, `1 OF 3` with the streak
1065 chip, `DONE`; the next step marked, done steps checked. Scoped to `ovq-`, on the kit's tokens.
1066- **Soft pages (`soft.js`).** Prefetch on intent (pointer down, or 80 ms of hover on a computer; never on Save-Data or
1067 2G/3G, at most four in flight, never auth, posts, downloads, queries, anchors or `data-ov-no-prefetch`), a
1068 conservative prefetch-only speculation rule in Chrome/Edge, `OVMark.navStart('page')` on the press finished by the
1069 arriving page at first paint, and cross-document view transitions where they exist (the shared bar held still, the
1070 rest cross-fading 180 ms; Calm 120 ms; Off none). One opt-in line after `theme-loader.js`; not loaded by
1071 `navbar.js`, so a site that does not opt in is unchanged. `window.OpenVibeSoft = { on(), off(), exclude() }`.
1072- **Updates (`shipped.js`, ask 341).** The filter chips ask the feed with `kind=` and carry their totals; Improved has
1073 no tag; empty kinds say so; commit hashes open on git.openvibe.codes.
1074- **Icon ink normalised (`glyphs.js`, `nav-icons.js`, `kit.js`, `ov-glyphs.*`).** 666 drawings normalised to a 0.79
1075 ink-size target; app tiles use a 0.58 painted-ink fraction and the old oversized tile rule is gone. Measured in
1076 headless Chrome at 96 px (painted ink bounds in `test/fixtures/icon-ink.json`), within 3% centre and 4% size.
1077- **The Dock's browser test runs in two halves (`test/dock-chrome.test.js`,
1078 `test/dock-vibe-timer-chrome.test.js`).** The whole file took about 65 s and the runner stops a file at 60 s, so it
1079 timed out on the slower macOS machines. Its sections each open a fresh page: the first file runs the first half
1080 (`DOCK_CHROME_PART` 1, the default), the new second file sets it to 2; 0 runs both.
1081- **The mark and the name are one button (`navbar.js`, `ov-drop.js`, `nav-slots.js`, ask 347).** The mark and the
1082 site's name are one 40 px button (46 px to tap) with a chevron: the accent tint on hover, a stronger tint with a
1083 ring while open. It opens the site's pages (Home first, the present page marked); "All OpenVibe sites" at the end
1084 opens the full menu on every site. The lead button a site draws (`nav-slots.js`) matches the menu button's size and
1085 corner. A site that turns the switcher off keeps the old links.
1086- **One composer on every site (`ov-composer.js`, `ov-thread.js`, `thread.js`, `thread.css`, ask 349).** Every
1087 `thread.composer()` box — and any site's own form through `OpenVibeComposer.enhance(form)` — gets files (the + menu,
1088 paste and drop become chips: a thumbnail, the name and size, ×), `/` (the site's, its mods', the person's own, and
1089 settings) and `@` (the mods' mention sources), voice where the browser can listen, drafts per box for 7 days, and
1090 the person's settings under `/settings` (kept in this browser, and on their account once Network's composer route
1091 lands). `OpenVibeComposer.mod({ id, match, tools, commands, mentions, upload, beforeSend })` for mods.
1092- **Vibe is Chat · Code · Agents (`search.js`, `dock.js`, `dock-code.js`, ask 350).** The menu's Vibe place holds three
1093 parts (your chats with Vibe, your coding tasks, your agents), and the Dock grows the same Code tab. `dock-code.js`
1094 gives one box to start a coding task the way openvibe.codes does (Create, OpenVibe, My code) with your latest tasks
1095 under it — state, repository, how long ago, and Stop — read through the Network's same-origin relay.
1096- Budget: `dock.js` 20.51 KB brotli (budget 20.55 KB, was 20.45 KB) for the Code tab's mount branch; the tab itself
1097 ships in `dock-code.js`, loaded the first time it opens. `notification-ui.js` 25.72 KB (budget 25.75 KB, was
1098 25.7 KB) for the one menu's notifications. The bar holds its 49.9 KB budget.
1099
1100## 4.200.0 — 2026-11-10
1101
1102**The button becomes its tab; Vibe holds Chat and Agents; every site from the site's name.** From the owner's notes of
11032026-11-10 (asks 334 to 338).
1104
1105- **The button becomes its tab (`search.js`, `navbar.js`).** "When you click this button, I want it to animate into the
1106 tab it becomes. Same with the other buttons." The bar's button that was pressed (the menu button, the picture; the
1107 bell once it opens You) flies to its tab's icon in the menu and turns into it on the way: its own picture fades out as
1108 the tab's fades in, along one short arc, and the tab settles with a small give. The bell's count rides along to the
1109 tab's count. Closing flies it back into the bar. When a button flies, the sheet's close X is simply there (the menu
1110 button no longer travels to it). Motion Off skips it; Reduced plays it short and straight. A flying button is hidden
1111 by opacity, so a quick second press still lands on it. `OpenVibeSearch.open({ from })` takes the pressed element.
1112- **Vibe, with Chat and Agents (`search.js`, `dock-vibe.js`).** The menu's "AI" place is called Vibe; its two-part
1113 switch is Chat · Agents (your chats with Vibe, and your agents). The list loses its "Vibe conversations" heading and
1114 the large "New conversation" button: a + icon (new chat) and an ↗ icon (open in OpenVibe.Chat) stand in for them.
1115- **Chat, where it was Messages (`search.js`, `dock.js`).** The menu's Messages place is called Chat (on
1116 openvibe.chat itself, whose own tab is already "Chat", it keeps "Messages" so the row never shows one word twice). An
1117 ↗ icon opens OpenVibe.Chat over your conversations and in a thread; the footer that said the same in words is gone.
1118 The "AI chats" row among your conversations is "Vibe". The Dock's own tab is "Chat" too.
1119- **A place's icons in the search pill (`search.js`).** Inside the menu, a place's icons (+ and ↗) sit in the search
1120 pill, left of the close button, so they cost no row; the floating Dock keeps them in a slim row of their own. The
1121 menu mirrors the panel's buttons and each mirror presses the panel's own.
1122- **Every site from the site's name (`ov-drop.js`, `navbar.js`, `search.js`).** The dropdown under a site's name ends
1123 with "All OpenVibe sites", which opens the menu scrolled past the site's own pages to every site. The scroll had never
1124 run: the menu read the tab after "sites" had become "here".
1125- **The site's name, highlighted (`navbar.js`, ask 342).** The name that opens its pages gets its highlight as a spread
1126 shadow with room around it (no added width, so the name still reads whole at 360 px): a faint tint on hover; open, a
1127 deeper tint with a hairline ring that comes up on a small spring. Motion Off keeps it still (the theme loader's rule).
1128- **A quick close and reopen keeps the menu open (`search.js`).** Closing the menu steps back in history; that step's
1129 event arrives later and used to close a menu opened again in between. The menu now ignores the one step it took itself.
1130- Budget: `dock.js` 20.43 KB brotli (budget 20.45 KB, was 20.30 KB) for the "Open in Chat" icon in the always-loaded
1131 Dock; the Vibe-only button rules stay in `dock-vibe.js`.
1132## 4.199.0 — 2026-11-10
1133
1134**Every page downloads about 56 KB less: the shared files are served minified.** From the owner's notes of 2026-11-10:
1135"Make sure everything loads … non-blocking asynchronously … minified".
1136
1137- **Served minified, cached by content hash (`serve.js`, new `minify.js`; OpenVibe.Shared #429).** Sites have no build
1138 step by choice, so the shared browser files are minified where they are served: terser for JavaScript, clean-css for
1139 stylesheets. It runs once per release per file, and the immutable `?v=` address does the rest. On 4.197.0's eager set
1140 (the bar, the theme, the footer, the kit and the rest) that is 806 KB → 523 KB raw and 189 KB → 133 KB after brotli,
1141 about 56 KB less per page view; `navbar.js` alone is 49.8 KB → 34.4 KB as delivered. A file the minifier cannot
1142 handle, or one that would not come out smaller, is served as it is; a minifier error never fails a request. ETag and
1143 `?v=` immutability are unchanged. Stylesheets are minified at clean-css level 1 only (inside each rule), never level
1144 2, which moves and merges rules and could change which rule wins. New dependencies: `terser`, `clean-css`.
1145- **Avatars reserve their box (OpenVibe.Shared #430).** The bar's, the footer's, the account switcher's and the user
1146 card's pictures carry their width and height and decode asynchronously (the footer's also load lazily, below the
1147 fold), so a signed-in page no longer shifts when the picture arrives.
1148- Checked on production openvibe.chat with every shared file served minified, on a phone and a computer: the bar, the
1149 menu, Messages and AI inside it, the Dock; no page errors.
1150
1151## 4.198.0 — 2026-11-10
1152
1153**On a phone the menu is one sheet: Messages and AI open inside it.** From the owner's notes of 2026-11-10, with the menu
1154and the Dock side by side: "combine and consolidate these two menus into one unified menu … easier to search between",
1155and a phone picture of the Dock's own head over the menu ("Menu", "…", close, then four tabs: two rows of chrome).
1156
1157- **The menu (`search.js`).** On a phone, Messages and AI in the row of places no longer close the menu to open the
1158 Dock's second sheet. They open inside the menu, under its own head, and become the chosen place like Feed or You. AI
1159 carries a small two-part switch at its top: Vibe · Agents (your Actor is the top of Agents). Feed, Here or You, typing
1160 in the field, or closing the menu hand the Dock's panel back with its state. On a computer the two places still open
1161 the floating Dock, which stays beside the work.
1162- **The Dock (`dock.js`, new `dock-mount.js`).** `OpenVibeDock.mountInto(host, { tab })` → `{ show(tab), destroy() }`
1163 shows one of the Dock's panels (chats, vibe, agents, activity) inside another sheet, with no head of its own: the same
1164 panel body, state, counts, token and loaders as the floating Dock, which closes while a panel is lent. The layout
1165 loads on first use (`dock-mount.js`), so `dock.js` stays inside its budget. Six mount and destroy laps leave no nodes or
1166 listeners behind (`test/dock-mount-chrome.test.js`).
1167- **The agent view (`dock-agent.js`).** An ended session is two lines of words with its one button beside them (it sat
1168 alone on a third row), and the agent view's buttons are 44 px under a finger.
1169- The bar's touch-feedback comment, which ships inside its styles, is one line now: the bar is back under its size
1170 budget with room for the next hashes.
1171
1172## 4.197.0 — 2026-11-10
1173
1174**The footer says "3 online", and the tooltip part works anywhere.** From the owner's notes of 2026-11-10: "Make it like
1175shorthand '3 online'. If you put your mouse over it, it says 'last 30 minutes'".
1176
1177- **The footer's online strip (`footer.js`)** read "13 online in the last 30 min", then the faces, then "1 member · 12
1178 guests". It now says **"13 online"** and the faces. Hovering the count (or reaching it with the keyboard) shows the one
1179 tooltip: "In the last 30 minutes", and under it how many are members and guests. A press opens the same words above
1180 the list of names, because a finger never sees a tooltip; with guests only, the count still opens that one line
1181 (before, a guests-only strip opened nothing).
1182- **The tooltip part (`ov-tip.js`) loads at the first hover over anything that carries `data-ov-tip`**, not only over the
1183 bar: the footer's count, and whatever a site labels the same way. The waiting listener lives at module level, so it
1184 holds no bar that a soft navigation has replaced.
1185- A test (`menu-places-chrome`) measured the menu's origin after a fixed wait and could read it mid-motion on a busy
1186 machine; it now waits for the motion to end.
1187
1188## 4.196.0 — 2026-11-10
1189
1190**Your Actor in the Dock, redrawn: one row and one line to type in. And a tone is a lean and a glow, never a bar.** From
1191the owner's notes of 2026-11-10.
1192
1193- **The Dock's Actor block (`dock-actor.js`).** The owner, on the Agents tab on a phone: "improve the design and layout
1194 so it's more intuitive, minimalist, elegant". It was a row with two outlined pills ("Resume", "Open"), a grey box
1195 that said "Paused" a second time beside a green dot, the model and the cap in a sentence, a field with a "Send" pill
1196 and a "Read recent history" pill. Now:
1197 - **Who, in one row.** The face wears a ring in the colour of its state (green and breathing while it works, amber
1198 while it waits for you, dimmed when paused), then the name and one quiet line: the state, and today's spend against
1199 the cap ("Paused · $0.00 of $1.00 today"; the model is that figure's tooltip). The row is the link to the Actor's
1200 page, so "Open" is gone.
1201 - **One action, the one that fits.** "Resume" in the accent when the Actor is paused; a quiet pause button when it
1202 runs. What happened is said in one line under the field ("Resumed.", "Sent. Your Actor reads it at its next step.",
1203 or why it did not go through), in place of notes piling up at the bottom.
1204 - **What it does, only when there is something to say.** The step it is on (a soft pulse), what it is looking at, the
1205 steps before it and what comes next ("Next: … , in 40 min"), as a short line of dots. A paused Actor shows none of
1206 it, and the state words Actor sends as a step ("Paused", "Resting until the next goal is due.") are no longer
1207 drawn as one.
1208 - **What it asks you** sits in the warning tone (a lean and a glow), with its own buttons: the first in the accent,
1209 "Not now" as plain words, and "1 more on your Actor's page" as a link.
1210 - **One field to tell it something**, with send inside the field; it lights up when there are words in it.
1211 - **Built once.** A refresh (every 15 seconds) used to rebuild the whole block and wiped the line being typed; now it
1212 repaints the row, the steps and the ask only when the answer changed, and never touches the field.
1213 - "Recent history" is a quiet text button; the button that opens the list closes it, one line per entry.
1214- **The agent view (`dock-agent.js`): no black box for a session that left no picture.** A session that ended with no
1215 still drew a 16:9 black stage saying "Nothing to show", which pushed the runs off a phone's screen. The stage is
1216 left out; the line under it still says what it was and how long it worked.
1217- **The kit's tone (`kit.css`).** The owner, on a notice with a red bar down its side: "instead of just like this red
1218 and crappy border … blur maybe effect or shadow, glow". Notices and `.ovk-tone[data-tone=good|alert|warning]` take a
1219 tinted hairline, a gradient that leans from the corner, a soft glow under them and a round icon chip; no side stripe
1220 anywhere.
1221
1222## 4.195.0 — 2026-11-10
1223
1224**The You tab gets a row of its own, and the menu grows out of the button that was pressed.** From the owner's notes of
12252026-11-10.
1226
1227- **The You tab: one slim row, and the old account block is gone from the menu.** The owner, with the block crossed
1228 out: "When I said integrate the user drop down into this menu, I didn't mean like this … get rid of this thing … We
1229 didn't really need the actual [block] here, other than like adding your account … making a new design for that."
1230 The row: your face and name, add or switch account, Display, Settings, Sign out (each a full touch target, with a
1231 tooltip). Under it, small, only what has no other home in the menu: this site's own items, your status, and Admin
1232 for an admin. History, Ask Vibe, Work, the Dock and the wallet are not repeated: they are tiles, the search box, the
1233 row of places and the balances pill. `navbar.js` hands the menu three small things (`me: { add, out, own }`) in
1234 place of its dropdown; `ov-home.js` draws the row.
1235- **The menu grows out of the pressed button** ("go … to the proper element … what you clicked on, and be more aware …
1236 of positioning"). On a computer it unfolded from its left corner, though its button has been at the right since
1237 4.193.0: its origin is now the pressed button's centre, wherever the sheet was clamped to, on the dropdown's own
1238 curve.
1239- Tests: `menu-home-screen-chrome` and `navbar-ask-vibe` read the row (the old block and its grid are not in the
1240 menu); `menu-places-chrome` checks the sheet's origin against the pressed button. `navbar.js` is under its budget.
1241
1242## 4.194.0 — 2026-11-10
1243
1244**One row of places in the menu, a wallet that is there at once, and a Sign in that says so.** From the owner's notes
1245of 2026-11-10.
1246
1247- **The menu's head is one row of five places** ("combine and consolidate these two menus into one unified menu … It
1248 just seems a little much with all the buttons on the first one … the agents and AI tools versus the feed and kind of
1249 community stuff"). This site · Feed · Messages · AI · You, in place of three tabs with a second row of four under
1250 them: on a phone each is its icon over its word, on a computer side by side. Messages and AI (Vibe and your agents)
1251 open the Dock on those places for now; their panels move into this sheet next. A visitor has this site, Feed and
1252 You, as before. The Dock draws Vibe as the spark and Agents as a robot, as the row does (the spark meant two things).
1253- **The wallet sheet is there at once and nothing jumps** ("load faster and dynamically sync smarter … isn't so jumpy
1254 as it loads in, the different elements and balances"). It opens holding what it last showed in this tab, or room
1255 held for its three cards; the store and your items stand as placeholders of their size; the store, the kinds and the
1256 items are asked side by side with the balances (they waited for them before); each part arrives where it stands,
1257 once. What it keeps is the wallet's own answer in the person's tab, forgotten when the account changes.
1258- **Sign in says "sign in"** ("should represent … that they need to sign in"). In the filled circle the figure and an
1259 arrow coming in through a door take turns (the arrow for a moment every six seconds); under the pointer it is the
1260 arrow, what a press does. Off shows the figure and does not move.
1261- **The tooltip's file is fetched by the first pointer over the bar**, not with the page (it was fetched at once on
1262 any device that can hover); what that pointer rests on gets its tooltip when the file arrives.
1263- Tests: `menu-places-chrome` (five places in one row signed in, the doors open the Dock, three for a visitor),
1264 the wallet's and the bar's. `dock.js` is 20.30 KB brotli (budget raised by 0.05 KB for the robot glyph, dated).
1265
1266## 4.193.0 — 2026-11-10
1267
1268**The bar's right end, one tooltip, and one frame for mail.** From the owner's notes of 2026-11-10.
1269
1270- **The menu button is at the right on a computer too** ("The menu button should be on the right, even on desktop …
1271 there's like a duplicated icon"). The orb at the bar's right is drawn on every width, and the one beside the name is
1272 not drawn where it is: the left end is the logo (the network's sites) and the name (this site's pages), the right
1273 end the menu and then the person. A second press closes the menu.
1274- **Sign in is an icon** ("This can be majorly improved with a custom icon. Doesn't need the label really, and could
1275 have a tooltip"). A filled circle with a figure in it, the size of the picture it stands in for, in the readable
1276 accent pair; the figure arrives on load, lifts under the pointer, and the ring runs while the sign-in page opens. Its
1277 name is its own (`aria-label="Sign in"`) and its tooltip. A finger's area is 44 px and stays off the menu button's.
1278- **New `ov-tip.js` (`OVTip`): the network's one small tooltip.** Anything with `data-ov-tip` (and an optional quiet
1279 second line, `data-ov-tip-sub`) gets a small card that grows out of what it names, under it or above, kept on the
1280 screen. A mouse sees it after a short rest (the next one at once), the keyboard's focus at once; a press, a scroll,
1281 Escape or leaving hides it; a finger never sees it, so nothing is said only there. The bar loads it where a pointer
1282 can hover; the menu button carries one too (its native `title` made a second, plain one).
1283- **New `email.js`: one frame for every mail** ("Improve the entire email system so it uses openvibe.design … uses the
1284 theme applied by the user … explain more details … what they can do, what they can click"). `frame({ theme, site,
1285 tone, kicker, title, lead, body, facts, code, actions, next, why, manage, preheader })` returns `{ html, text }`: the
1286 person's theme written on the elements (all fifty read at 4.5:1 or better), a title that says what happened, the
1287 facts as a list, one button and quieter links, what happens next, and why it came. Text only and safe links only. A
1288 tone is never a hard coloured border ("instead of just like this red … border … shadow, glow"): the card leans toward
1289 the tone at its top and glows in it, the kicker is a pill with a round mark, the facts stand in a box with a tinted
1290 hairline. `palette(theme)` and `mix(a, b, t)` are exported for a site's own blocks.
1291- **`mini.js` plays what the page plays.** Found on openvibe.video the hour it shipped: the page's script gives the
1292 video its streaming playlist, and one recording's plain file stopped with a decode error three seconds in. The small
1293 player now takes the playlist (`data-ov-mini-hls`) where the browser plays one by itself, in the page's cross-origin
1294 mode, and the plain file (`data-ov-mini-src`) otherwise; a playlist that fails gives way to the file at the same
1295 second. A video that cannot be played either says so ("Open its page to keep watching"), keeps the way back, and
1296 does not follow to the next page; it used to close without a word.
1297- **The wallet says what a line was for** (OpenVibe.Shared #416, from the money-reasons work): the reason is a made
1298 sentence, linked to the thing it paid for.
1299- The 4.192.0 entry's date is corrected (it said the 11th).
1300- Tests: `ov-tip-chrome`, `email` (every theme, every tone), `mini-chrome` (the cannot-play state), `navbar-fit-chrome`
1301 (the menu button at the right at every width, no second one by the name), the tap-area tests with the round Sign in.
1302
1303## 4.192.0 — 2026-11-10
1304
1305**The video that comes with you.** The owner, 2026-11-10, with the browser's floating picture-in-picture window over a
1306watch page: "should … actually [be] like YouTube where it moves that video. Now you go to the search again or the index
1307… the homepage … it's just kind of confusing".
1308
1309- **New `mini.js` (`OpenVibeMini`): a small player of our own.** A watch page says its video may come along
1310 (`<video data-ov-player data-ov-mini data-ov-mini-title data-ov-mini-by>`). The shared player's picture-in-picture
1311 button then writes down what is playing (the page, the file, the title, who made it, the poster, the second, the
1312 speed, the volume, whether it was playing) and takes the person to the site's front page. Every page of the site
1313 that finds the record draws the small player and goes on from the same second: a card at the bottom left on a
1314 computer (the picture, "Back to the video" on hover, a line of progress, the title, who and the time, play and
1315 close), one bar above the app bar on a phone. The video's own page takes the second back and draws nothing. A page's
1316 own video starting closes it: one sound at a time. Where a new page may not start with sound, it plays silent with
1317 "Turn the sound on". The record is the tab's own (`sessionStorage`), so a private file's signed address never
1318 leaves the site. `OpenVibeMini.carry(video, { to })`, `.open(record)`, `.resume()`, `.close()`, `.record()`.
1319- **`player.js`:** on a video with `data-ov-mini` the picture-in-picture button is "Keep watching while you browse"
1320 and is shown also where the browser has no floating window of its own (it was hidden there).
1321- **A site turns it on** by loading `mini.js` on its pages (`ovServe.url('mini.js')`, deferred): the file starts
1322 itself and does nothing without a record. The bar does not load it: `navbar.js` is at its size budget, and the loader
1323 moves into the bar with the bar's diet.
1324- Motion: the card rises in 280 ms; Calm fades; Off does not move.
1325- `test/mini-chrome.test.js`: from the player's button to the front page at the same second, pause kept across pages,
1326 closing for the page's own video, the second handed back, Close forgets, the bar on a phone.
1327
1328Across sites the record will travel another way (storage is each site's own); gapless moves need pages changed in
1329place. Both are researched first (plan, ask 265).
1330
1331## 4.191.0 — 2026-11-10
1332
1333- **A guest's page view counts at once again.** The beacon waits for the shared sign-in state only for a browser that has a token and no user yet (at most 2.5 s, so a signed-in person is not also counted as a guest); a page that knows its person, and a browser with no token at all, send it as they always did. The navbar's brotli budget follows (49.9 KB).
1334
1335## 4.190.0 — 2026-11-10
1336
1337**The one dropdown, the wallet in colour, who is online, and five smaller things.** From the owner's notes of
13382026-11-10, each with a screenshot.
1339
1340- **The site's name opens its pages** ("clicking the actual name of the domain should have a sleek little minimalist
1341 drop down … that just shows the pages … have the home button as an option … a custom, animated system for these drop
1342 downs and positioning of them, used across all sites"). New `ov-drop.js`: a small list that opens out of what was
1343 pressed (under it, or above when there is no room), kept on screen, its rows arriving in turn; arrow keys, Enter,
1344 Escape with the focus given back, a press outside; one open at a time, with the page's other panels. `OVDrop.open`,
1345 `.attach` and `.pages`. In the bar the name shows a small mark and lists Home first, each page with its icon and the
1346 present page marked; with a modifier key it is still a link to the site's home.
1347- **The wallet sheet in the currencies' colours, with motion** ("make this way nicer with animations and effects and
1348 color … nicer for the different currencies … effects like animations for the coin loss … shared and unified, also
1349 SDK API"). Each balance is a tinted card with its drawn icon in a tile and its action in the same colour; a light
1350 crosses each card as the sheet opens and the numbers count in; after a purchase the number counts to the new amount
1351 and the currency's pieces leave its card with the amount beside it. On a phone: the tile, the name over the number,
1352 the action at the right. A read that fails is said in a person's words, never a program's.
1353- **The effects as calls** (`purse.js`): `OpenVibePurse.burst(element, id, delta)` plays the gain or spend at any
1354 element, inside an open dialog too, upward near the bottom of the screen; `.count(element, from, to, format)`;
1355 `data-purse` gives a holder the currency's colour (`--pc`); `.ovp-play` plays an icon's own move.
1356- **The footer strip says who is online** ("show how many users are across all sites … the last half hour … guests
1357 and users, like the old forum style where it lists everyone … live smaller, only if someone's live … other
1358 metrics"). One read, `/api/frame/online`: the count with a few faces, members and guests, and where members may be
1359 named the list of them with their site; then small tiles that open what they count: live (only while someone is),
1360 joined this week, the site's own items, the sites, the week's page views. The page-view beacon marks a signed-in
1361 browser with `?in=1` (no identity), so that person is counted from their presence and not as a guest.
1362- **Uploads say when you already have the file** ("tell the user they already have one"). `OVUpload.queue` takes
1363 `checkDuplicate(file, sha256)`; the row says "You already uploaded this on 10 Nov 2026." with Open it, Use that one
1364 and Upload again, as one block. `OVUpload.preflight` does it for a single picker. Files over `hashLimitBytes` (32 MiB)
1365 or slower to hash than to send are not hashed.
1366- **A change is New, Improved or Fixed, with a mark** ("what is this confusing tag … better next to everything?").
1367 A star, an arrow up, a tick; an improvement shows the mark alone. "Better" is gone.
1368- **One picker for a person's computers** (`machines.js`, with Network's /add-computer): each computer a tile with
1369 its state and what it can run, "Auto", and "Add a computer" through OpenVibe Link.
1370- **One bar mount per page: an empty band under the bar is gone from seventeen sites** ("why is there so much wasted
1371 space or unnecessary margin … for no reason", with the space under the bar circled on openvibe.community). Since
1372 4.186.0 the server writes the bar's links itself, in its own `#navbar-mount`; a page whose body already had
1373 `<div id="navbar-mount"></div>` then had two, the bar mounted in the first, and the second stayed an empty 52 px box.
1374 `shell.page` puts the server's bar into the page's own mount where it has one.
1375- **Sign In has its own icon** (an arrow into the ring; it steps in now and then, and the ring's gap runs once
1376 pressed), and the menu button's three squares hop in turn every few seconds ("use a nice custom animated icon for the
1377 sign in … make the menu button animated as well").
1378- **The bottom bar's highlight is centred on its icon** ("this is all offset"). It was placed once from `offsetTop`,
1379 before the icons and the brand font arrived, and stayed above its icon; it is measured from where things are and
1380 placed again when the bar or an icon changes size.
1381- Jump back in links to the full history on openvibe.my; the Dock's agent can read recent history on a press (the
1382 history-timeline job's plumbing).
1383
1384- **The test runner gives a failed browser file one more try** (`retry` in `test-runner.js`, used by this package's
1385 own run). Three releases in a row waited on the macOS runner, each time for different headless-Chrome files that
1386 pass on Linux, on Windows and locally. A pass on the second try is named in the summary.
1387
1388Built by runner jobs and given their look here: the upload check (upload-dedupe), the computers picker
1389(machines-one-place), the footer's read (whos-online-strip), the history link (history-timeline).
1390
1391## 4.189.0 — 2026-11-10
1392
1393**The balances are one pill, the menu and the Dock lead to each other, and the menu opens where you are.** Seven notes
1394from the owner on 2026-11-10, each with a screenshot of the bar or the menu.
1395
1396- **One pill for the balances** ("we just show one, but it can animate between all three … it only needs to show the
1397 things you have … nice icons that are animated, designed for the three currencies … a custom animation for gaining
1398 and losing, with coins coming out of it"). `#openvibe-chips-all` is drawn at every width, with the balances the
1399 person has (above zero, the largest first; the money balance leads when AI was used and it is empty) stacked in one
1400 place and one showing. New `purse.js`: an icon drawn for OpenCoins (a coin struck with the ring), Vibes (a cut gem)
1401 and the money balance (a note with a seal), each in its own colour leaned toward the theme's text so it holds on
1402 light and dark; a turn through the balances on the first page of a visit and one every forty seconds or so; and on a
1403 change the pill turns to that balance, counts the number, takes its colour for a moment and sends a few of its
1404 pieces in (a gain) or out (a spend) with the amount under it. Calm motion shortens the moves; Off removes them.
1405 The two-line pill on phones and the separate chips on computers are gone.
1406- **The menu opens on this site's tab** ("this button should open the first tab so people can switch between pages on
1407 the site they're on … make it more clear that those are pages on the site you're on"). No tab is remembered; the
1408 picture still opens You. The pages are tiles under "Pages on <site>", in the case it is written in, behind a dot that
1409 marks where you are.
1410- **The current site is first in its group**, the site tiles are full touch targets, and a group's name sits on a
1411 hairline ("design should be the first result … improve these section titles … make the icons look nicer").
1412- **From the menu to the Dock and back** ("how are you supposed to get to the other menu from this area … a way to hop
1413 over to the other interface that has the chat"). Under the menu's tabs a row opens the Dock on Vibe, Agents, Messages
1414 or Activity (`ctx.dock`, given by the navbar where the Dock is on); the Dock's head has "Menu", which folds the Dock
1415 and opens the menu out of the same button.
1416- **The Dock's head** ("buggy and kind of ugly … more consistent, cohesive"). Its close button was drawn as a white bar
1417 over blue dots: the Dock's line style for icons reached the menu button's parts. It is the same cross as the menu's
1418 now. The chosen tab takes the theme's accent, as the menu's does (it was a dark box inside a box). On a phone the
1419 head is two rows, as the menu's is: Menu, More and Close where the bar's bell and button are, and under them the
1420 four tabs, full width, in the look of the menu's row that leads here.
1421
1422## 4.188.0 — 2026-11-10
1423
1424**The account's things are in the menu.** The owner, 2026-11-10, with the dropdown under the picture marked "Replace →
1425to You tab": "this user drop down is just redundant … move it into the You tab of the menu … add account switching
1426and adding accounts to that … consolidate the two into one menu."
1427
1428- **The picture opens the OpenVibe menu on You.** The block the dropdown drew (who you are, add another account,
1429 settings, sign out, your other accounts, the site's own items from `addMenuItem`, light or dark and Display) is a
1430 block of that tab, at its top, where a shorter bar with the name was. It is there wherever the bar has a person, with
1431 or without a token for the network's reads.
1432- **No dropdown.** The popup, its position, its height rules and its place among the panels (`user-menu`) are gone.
1433 The block rests in the bar, out of sight, while the menu is closed: a site that looks for `#openvibe-logout-btn`,
1434 `[data-ov-ask]` or its own item still finds it.
1435- **The mark is larger in the bar** (42, was 34; 34 on a small phone, was 28). The owner: "make the OV branding take
1436 up most of its area … stand out a little more … compared to the My." Nothing overlaps from 320 px
1437 (`navbar-phone-fit-chrome`).
1438- In the You tab, "All 65" keeps its own case (it was drawn in the heading's capitals).
1439
1440## 4.187.0 — 2026-11-10
1441
1442**The bar the owner chose.** 2026-11-10, in the Arena: "here's the new menu button and the logo … these two, but
1443improve upon and expand on it. And let's move the user … so that it's to the right of this. And instead of having
1444messaging and notifications, just have the notifications."
1445
1446- **The menu button is "Search and apps"** (the Arena's M57): the menu itself, small: a search bar over three apps.
1447 Under the pointer its parts lift in turn; while the menu loads the apps breathe; open, the bar turns into one stroke
1448 of the close mark and the three apps line up as the other. One drawing (`OpenVibeOrb.svg()`, navbar.js) for the
1449 bar, the menu's sheet and the Dock's sheet, where three copies of nine dots were.
1450- **The mark has the wide gap** (the Arena's P07): the ground around the V where it crosses the O is 14, was 7, in
1451 `ov-mark.js`, `mark.js`, the app icons and the loader's tab icon alike (`test/mark.test.js`).
1452- **The person is last in the bar**: balances, the bell, the menu button, then the picture (or Sign In).
1453- **Messages is the bell.** The Messages button leaves the bar; the count of new messages (Chat's, or a site's own
1454 through `setMessages(count)`) is added to the bell's number and named in its label ("Notifications, 1 unread, 2 new
1455 messages"). With new messages and nothing else new, the bell opens the Dock on Chats; otherwise on Activity.
1456- **One menu.** The owner: "make sure these two share the same universal system … it just opens the actual first
1457 tab … same UI." The mark opens the OpenVibe menu on its first tab; `site-switcher.js`, its panel, its own icons
1458 and its "here" label are deleted. `OpenVibeNavbar.openSwitcher()` opens the menu.
1459- **All apps is a small button beside Edit** in the menu's You tab ("All 65"), where a bar the width of the sheet
1460 was under the grid.
1461- **An agent's turn shows it is thinking**: in the conversation kit (`thread.css`), the agent's avatar shows three
1462 rising dots while its turn is open.
1463
1464Removed: `site-switcher.js` (and its export), the Messages button and its `messages: { href, onOpen, label }`
1465behaviours (the Dock's Chats tab is where messages open; `messages: false` still turns the count off).
1466
1467## 4.186.0 — 2026-11-10
1468
1469**The mark at work is a ring of dots, and thinking is a state of its own.** The owner, 2026-11-10: "the progress
1470animation… should make the O turn into a bunch of little circles, like how a normal loading spinner looks… a cooler
1471animation that actually looks like it's loading or doing something. Also, there should be one dedicated for thinking
1472when your agent is thinking or replying… easier to know that it's replying or outputting tokens."
1473
1474- **`data-state="busy"` (working):** the O breaks into eight dots (a dash of no length with a round cap) and three
1475 bright ones go round the ring, dot to dot, while the V bobs. It was the O breathing. This replaces the old line
1476 "nothing spins": a wait is the mark's own O going round, never a borrowed spinner.
1477- **`data-state="think"` (new):** three dots rise in turn inside the O, the ring quiets, and the V's two strokes glimmer
1478 one after the other. One sign, wherever an agent is thinking or tokens are on their way.
1479- Both are shown at every size and stay readable with motion off (the dots are simply there).
1480
1481**The menu and the Dock are one kind of sheet on a phone.** The owner: "make the two UIs look match a little better,
1482especially the close button, so it feels more seamless… make the animation effect for the menu opening and closing more
1483similar to how the other ones are… and make it cooler."
1484
1485- **One close button:** the Dock's is the menu's now: the same round, ringed button, the same × of dots in the same
1486 colours, at the same place on the screen (measured: both centred 36px from the right edge, 34px from the top).
1487- **One move:** the Dock's sheet opens as a circle growing from the orb and folds back into it, with the menu's own
1488 timing (380 ms in, 280 ms out); it faded and scaled in 4.185.0.
1489- **A ring leaves the orb** as either sheet opens (smaller under Calm, by `--ov-move`).
1490- The close orb is measured after the sheet's layout is set, so it starts exactly on the navbar's button.
1491
1492**A control nobody styled wears the theme.** The owner: "customize all the inputs and buttons and things that aren't
1493styled." A `<button>`, a text field, a `<select>` or a `<textarea>` with **no class of its own** gets the theme's face
1494from the loader: its colour, border, corner and font, and the accent on a tick box or a slider. Only the face, and at
1495no specificity (`:where`), so any rule a site wrote still wins and no layout moves; a control's real shape is the
1496kit's (`ovk-btn`, `ovk-input`). The loader grew about 1 KB raw (budget 67,600 characters).
1497
1498The Dock is 20.00 KB brotli (budget 20.25 KB, with the reason beside it).
1499
1500## 4.185.0 — 2026-11-10
1501
1502**The You tab: your home first, a shorter bar, Most used as shortcuts.** The owner, 2026-11-10: "Your home should be
1503above Jump back in… the thing at the top with your username and online and the settings… a little more compact… most
1504used is cool, but maybe we should actually have links to whatever the user uses the most… so it's just easier to jump
1505to the app."
1506
1507- **The order** is you, your own AI, **Your home**, Most used, Jump back in (it was you, AI, Jump back in, Most used,
1508 Your home). Nothing above the grid comes or goes while Home is being arranged, as before.
1509- **The bar with your name** is 52px tall (was 68): a 36px picture, name and handle on two tight lines. Display and
1510 Settings keep their 44px.
1511- **Most used** keeps its one bar; its key is now the apps themselves, five across: each app's icon in its own hue, its
1512 name, and how often with the bar's colour beside the number. Each is a link (`.ovh-key`, "Live, opened 183 times") and
1513 a full touch target; it was a row of small legend entries.
1514- **Jump back in** comes last and says what it is: "where you were last".
1515
1516**A phone's Dock opens out of the navbar's menu button, and that button is its close button.** The owner, 2026-11-10:
1517"that close button should, instead of coming from that menu, fade and have an animation effect from the top right, the
1518menu button that already exists… it's supposed to be nicer connected to this." Until now the sheet rose from the bottom
1519and carried its own close button up with it.
1520
1521- On a phone (640px and under) with the orb at the bar's right, the sheet fades and grows from the orb's centre
1522 (`.ovdk.from-orb`); its close orb starts exactly on the navbar's orb, as its nine dots, and settles into its x in the
1523 header (a glide of a few pixels and the same turn the main menu's orb makes). The navbar's own orb makes the same
1524 move underneath (`.ovnav-orb.is-dock`), so it is one button the whole way. The x wears the menu's colours.
1525- **Closing runs it back:** the x turns into the dots and returns to the bar while the sheet fades (240 ms), then the
1526 sheet is gone and the button is where it always was. `OpenVibeDock.close()` therefore hides a phone's sheet a moment
1527 later than it is called; with motion off, in a window of its own, or on a bar without the orb, it hides at once as
1528 before (and such a bar keeps the rise from the bottom).
1529- Calm keeps the move (the grow is smaller, by `--ov-move`); only motion off skips it.
1530
1531`test/menu-home-screen-chrome.test.js` holds both: the order, the bar's height, the shortcuts' links and targets; the
1532close orb starting on the navbar's orb, settling, folding back, and standing still with motion off.
1533
1534## 4.184.0 — 2026-11-10
1535
1536- **Server-rendered navbar links.** The shared shell emits the configured navbar links in the reserved `#navbar-mount`
1537 so crawlers can read site navigation from the response HTML. The client replaces that node's contents atomically
1538 on hydration; server and browser use the same configured or default link list, with the existing 52px reserve.
1539
1540## 4.183.0 — 2026-11-10
1541
1542**Motion: Calm reduces, only Off removes.** The owner, 2026-11-10: "Reduce motion is too much reduction, it ruins a lot
1543of stuff, so it should still have full motion but actually just reduce it instead of getting rid of it." Until now Calm
1544(the person's choice, or the device asking for less motion) gave every animation and transition no length at all.
1545
1546- **Four choices, one signal.** `display.motion` is `auto`, `full`, `reduced` or `off`, and `theme-loader.js` writes
1547 the answer on `<html>`, the device's own setting counted in: Automatic follows the device (Calm when it asks, nothing
1548 otherwise), Full plays everything even when the device asks (`data-ov-motion=full`), Calm is `data-ov-motion=reduced`,
1549 and Off (`data-ov-motion=off`) is the only choice that stops motion. `display.motion()` answers `'full'`, `'calm'` or
1550 `'off'`. The look panel's Motion row offers the four (Auto, Full, Calm, Off).
1551- **What Calm is.** `--ov-move`, the factor a moving thing multiplies its distance by: 1 in full, .35 under Calm, 0 with
1552 motion off (`calc(12px * var(--ov-move, 1))` in CSS, `display.move()` and `OVFx.move()` in script). The calm feel's
1553 lengths with no overshoot. And the loader slows every endless loop (a pulse, a drift, a shimmer) to 0.6 of its speed
1554 as it starts; anything that ends plays as it was written. The page itself does not glide when it scrolls; a rail or
1555 a sheet does.
1556- **The effects.** A lift rises a pixel and a reveal five; a tilt leans a third as far; a burst of confetti is a third
1557 of the pieces thrown half as hard; a count-up is quicker; the glint is fainter and slower; the mark keeps its intro
1558 and its hover and drops only the linger, and three leaves fall instead of seven; the ghost cursor glides slower and
1559 waits longer; a name's particle burst is three pieces, not six; a drawer slides a third of its width and fades; a strip nudges half as far and drifts at half the speed.
1560- **The files.** 75 rules across 45 files stopped an effect under `prefers-reduced-motion` or under
1561 `html[data-ov-motion=reduced]`: all are gone. What such a rule did to leave a thing finished (a drawn stroke, a
1562 static glow, a bar at rest) belongs to `html[data-ov-motion=off]`. The calmer keyframes that already existed (the
1563 menu's pop, the home screen's wiggle and card fade, a new message) stay as Calm's. Script that skipped an effect when
1564 the device asked skips it only with motion off; a live preview (a video that starts by itself) still waits for a tap
1565 under Calm.
1566- **For a site's own styles:** delete the rules that stop motion under `prefers-reduced-motion` or under
1567 `html[data-ov-motion=reduced]`; multiply a distance of 24px or more by `var(--ov-move, 1)`; keep a rule only where it
1568 swaps in a calmer keyframe, keyed on `html[data-ov-motion=reduced]`; a finished state belongs to
1569 `html[data-ov-motion=off]`. `test/motion-setting.test.js` holds this package to it.
1570- The loader grew about 2.9 KB raw (budget 66,500 characters). Network must accept `full` and `off` in the display
1571 record before a site offers them (OpenVibe.Network `server/themes/routes.js`).
1572
1573**Icons: every shape glyph has a solid drawing, and every product has its mark.** Icons shown at 18 px or smaller draw
1574their solid form; 142 of the 278 glyphs had none. 75 of those are made of lines only and thicken when small by design;
1575the other 67 are shapes, and all of them are drawn now (frames and cards, marks in a disc, the media keys, things,
1576people, slashed shapes), so 203 of 278 glyphs have a solid drawing and every glyph without one is a line glyph.
1577`glyphs.svg(name, { style: 'solid' })` gave a class-ln part no stroke of its own, so a line inside a solid drawing was
1578filled as a shape or not drawn; it carries the stroke inline now, as the sprite already did. Site marks: eleven
1579product ids had no mark and drew the bare "V" (computer, mail, lock/locker, git, barcode, party, studio, my, home,
1580restream, node), and four things sites name as marks did the same (shoe, heart, calendar, camera). Each has its glyph
1581in the ring, centred by measurement; `test/site-icons.test.js` covers every product id.
1582
1583## 4.181.0 — 2026-11-10
1584
1585**The Dock's agent view, timeline and Actor go through Network's frame relay.** `dock-agent.js`, `dock-timeline.js`
1586and `dock-actor.js` asked `openvibe.codes` and `openvibe.actor` directly from every site. Every site's `connect-src`
1587allows `openvibe.network` and refuses those, so 49 of 49 signed-in sites logged 1-4 console errors at 1280 on
15882026-11-09 (CSP on most, CORS on the sites without one). The three modules now ask Network's same-origin
1589`/api/frame/...` relay by default: `GET /agent-view`, its still and the four controls; `GET /agent/timeline` and its
1590writes; `GET /actor` and its subpaths. An explicit `base`/`agentBase`/`actorBase` (a test, or the extension's own
1591panel) still reaches the product itself, and nothing is asked without the person's token. The markup and styles are
1592unchanged. `test/dock-relay.test.js` fails if a Dock script fetches another product's origin directly again.
1593
1594## 4.180.0 — 2026-11-10
1595
1596- **A demo scope for motion: `data-fx-demo`** (`ov-fx.js`, `ov-mark.js`, `theme-loader.js`). Inside a container
1597 with this attribute the effects and the mark play even when motion is reduced, by the device or by Calm. It is for
1598 a page that exists to show motion, and only after the person presses a button that says so: openvibe.design's
1599 Motion page was completely still on a phone that asks for less motion, and never said why. Outside the container
1600 nothing changes: the setting holds for the rest of the page and for every other page.
1601 - `OVFx.confetti({ from: el })` names the element it comes from, so confetti inside a demo scope plays; without
1602 `from` it follows the setting as before.
1603 - Calm's blanket rule, the effects' reduced-motion rules and the mark's now exclude `[data-fx-demo] *`, and a demo
1604 scope gets its `--ov-dur-*` lengths back under Calm.
1605
1606## 4.179.0 — 2026-11-09
1607
1608**Share to OpenVibe Community.** The share sheet (`ov-share.js`) has a Community tile beside OpenVibe Chat: it opens
1609Community's new-topic form (`/forum/discuss?title=&body=`) with the title and the link filled in; the person picks the
1610forum and posts it themselves. OpenVibe's own places lead the grid and wear the accent. Not shown on Community's own
1611pages.
1612
1613## 4.178.0 — 2026-11-09
1614
1615**What's new reads like news (plan §9).** `shipped.js` uses the tags Blog now writes on every change (`kind`: new,
1616better, fixed, internal; `areas`; `plain` words) and the newest devblog Network's changelog proxy carries.
1617- The card (`render`): titled "What's new"; the newest devblog leads it with its picture, title and summary; each row
1618 shows its kind as a word tag and up to two area chips, in plain words with the commit subject as its title;
1619 internal changes count as housekeeping and stay out of the card, like pin bumps.
1620- The updates page (`log`): the same row; a second chip row filters by kind (All changes, New, Better, Fixed, and
1621 `?kind=` in the address); internal work gathers at the end of its day in one fold that says how many
1622 ("3 housekeeping changes"); pin bumps keep their grouped row ("Shared 4.177.0 reached 41 sites").
1623- Without the tags (older entries), rows read as before.
1624
1625## 4.177.0 — 2026-11-09
1626
1627- **`openvibe-shared/offer-card`: one offer from the network's shops** (`offer-card.js`, styles in `kit-data.css`). It
1628 shows the title and the shop, the all-in price formatted as money (`$90.00`, `€50.00`) with what it includes ("Free
1629 shipping", "+ $4.99 shipping", "Shipping not stated"), and how fresh the price is ("Checked 3 hours ago"). Nothing
1630 unknown is invented: no price or check time says so. A stale offer says "may have changed". Only http(s) links are
1631 kept. On a phone the price goes under the title.
1632- **A section's line sits under its title** (`showcase.css`). The line under a showcase section's heading was always
1633 indented to clear a heading icon, so a heading without one (Blog's "Latest from the OpenVibe blog") had its line
1634 pushed right. It is indented only when the heading has its icon.
1635## 4.176.0 — 2026-11-09
1636
1637- **The model chip and its choice sheet** (`ai-choice.js`, `ov-ai-choice.js`, `ov-ai-choice.css`): a chip beside any
1638 Generate button showing the maker mark and the model's name, opening a sheet with one list of the models in the
1639 order the person picks (Recommended, Fastest, Cheapest, Best quality; the owner's recommendation badged), then Your
1640 keys, Your machines and Uncensored 18+ (only where the person confirmed). Each row says what it costs a run and how
1641 fast it is; a check marks the choice. The footer saves "Use this for every <task>" and links to
1642 Exchange. Server rendering keeps the control named before hydration; the sheet sets no scroll behind it and an
1643 outside tap closes it.
1644
1645## 4.175.0 — 2026-11-09
1646
1647- **OpenVibe Link**: the shared navigation's Machines entry points at openvibe.link/machines instead of the old
1648 node host, and the shipped-services list names the product Link. The Contracts pin moves to v0.373.0. No API changes.
1649
1650## 4.174.0 — 2026-11-09
1651
1652- **Home fills its width** (`ov-home.js`; the owner, 11-09: the icons were "offset and need to utilize more of the
1653 space"). The apps on the menu's Home grow with their cell up to 72 px (they were a fixed 56 px in a wider cell), the
1654 glyph is about half the tile, folders scale their four minis with the tile, and the grid lines up with the cards
1655 above it. The names are a touch larger and brighter.
1656- **An Edit button on Home** (`ov-home.js`). A small pencil beside "Your home" starts arranging: move apps, remove
1657 them, make folders. Press and hold still works. While arranging, the button is a Done pill with a check, and Home
1658 stays where it was: the hint is under the grid and the cards above it stay (quieted), so a long-press that turns into
1659 a drag never has the grid move under the finger.
1660- **The site's tab matches Feed and You** (`search.js`). The first tab of the menu showed the site's coloured app mark
1661 (a disc and ring around the glyph); it now shows the glyph alone, in the label's colour and at the other tabs'
1662 weight. On a phone the three tabs are tighter, so a long name like "Community" fits whole.
1663
1664## 4.173.0 — 2026-11-09
1665
1666- **The wallet works on sites that keep the access cookie from page scripts** (`navbar.js`, `ov-wallet.js`). 4.170's
1667 chips and sheet sent only a Bearer token. The sites on `openvibe-sdk/sso` with `httpOnlyAccess` (Actor, Bench, Casino,
1668 Codes, Connect, Design, Exchange, Vision) hand the navbar none, so their chips sat at "…" and Wallet did not open.
1669 With no token, the chips and the sheet now ask Network's relay with the person's Network session cookie
1670 (`credentials: 'include'`; Network #724 turns it into the Bearer, only for allowed origins). A wallet that cannot be
1671 read hides the chips instead of leaving "…". `OpenVibeWallet.open` takes `cookieSession: true`. Sites whose navbar
1672 reads `ov_token` are unchanged.
1673- **The wallet sheet goes by its content hash** (`scripts/sibling-revs.js`): `ov-wallet.js` and `ov-wallet.css` were
1674 loaded without `?v=` since 4.170, so a fix reached phones up to a day late.
1675
1676## 4.172.0 — 2026-11-09
1677
1678- **Notices read as what happened** (`notification-ui.js`, the bell, its toasts and the Dock's Activity tab): an
1679 agent's notice shows its task, not the words it was given ("Done: <prompt>" is the prompt; "approve: <step>
1680 (<task>)" is the step with an "Approval" label), and rich_content's `task_title` and `step` win when Actor sends
1681 them; two finished tasks are two rows. A message holding a value's JSON (a watch on a feed sends the entry, cut at
1682 300 characters) reads as its title and summary, and JSON with neither is left out instead of shown as code.
1683- **Faces per service**: Actor's notices wear an agent, Watch's an eye, Connect's and Link's a computer, Billing's a
1684 coin and the welcome a spark, instead of every service sharing the grid; their rows say Agents, Watch, Connect,
1685 Link, Community, Wallet, Blog, Design, AI or Events. The bell script's budget is 25.5 KB brotli (25.24 measured).
1686- **The account menu asks only the Network for Vibes** (`navbar.js`): the direct `billing.openvibe.network/api/me/vibes`
1687 fallback (for a Network older than 10-28) and the `billingBase` option are gone. No site's CSP allowed that host, so
1688 it could only fail. A Network answer without Vibes shows no Vibes chip.
1689
1690## 4.171.0 — 2026-11-09
1691
1692- **The page head's icon and actions sit on the title's first line** (`kit.pageHead`): a title that wraps to three
1693 lines (a home page's hero, openvibe.space) kept them floating beside its middle. The row aligns to the top and
1694 both are centred on the first line (`--ovk-page-line`, in rem and vw, so a text button in the actions keeps its size).
1695
1696## 4.170.0 — 2026-11-09
1697
1698- **The wallet, one sheet on every site** (`ov-wallet.js`, opened from the navbar's balance chips): OpenCoins, Vibes
1699 and Balance with how to earn, buy or top up; the store (items travel with the account, priced in Vibes or
1700 OpenCoins); your items with Equip and Unequip.
1701- **Through Network**: every call goes to `https://openvibe.network/api/frame/wallet/…` (Network #718, nine fixed
1702 Billing and Inventory paths, the person's token only). Every site's CSP refuses billing. and inventory. hosts.
1703- **Checkout only on a named host**: the sheet follows a checkout URL only when it is https, carries no credentials,
1704 and its host is one Billing names for its providers (Billing #228) or billing.openvibe.network. Anything else is
1705 "Checkout is unavailable."
1706- Each purchase carries its own idempotency key and keeps it across a retry, so a double tap never charges twice.
1707- Designed here: no eyebrow, makers by name ("By Live"), the close ring for the keyboard only, 44 px controls,
1708 phone sheet from the bottom.
1709
1710## 4.169.0 — 2026-11-09
1711
1712- **kit.switchRow, redrawn** (ask 44, the paste form: "polish and align it: switches, sizes"): an icon tile, a title and
1713 one muted line, then an iOS-style switch; the whole 56 px row is the target and the switch is the checkbox itself
1714 (`role="switch"`), so a form still posts without JavaScript. The older `label`/`hint`/`on` names still work.
1715- **kit.segmented**: three or four equal choices in one 44 px control, icons before the words; radios underneath, so
1716 the value posts as a radio group's. Without a label it stands alone (a form puts its own label above it).
1717- Glyphs: `flame` and `eye-off` (Burn after reading, Hide).
1718
1719## 4.168.0 — 2026-11-09
1720
1721- **The Dock's Agents tab, content-aware** (plan 2026-11-09 §7–8; the owner, 11-09: "the Agents tab's 34 identical rows
1722 'Draft a blog post · Done · AI · gpt-5-nano', 25 failed rows 'AI is resting for a moment' and raw JSON errors; an empty
1723 Agents tab that is wasted space for no reason"). `dock-runs.js` (loaded with the tab) draws each run as a row: the
1724 skill's icon in its own colour, the subject as the title, and one status line — Running (the live step and a thin
1725 bar), Waiting (what it needs), Scheduled (when), Done (the result, the cost, the time, the model with its maker's
1726 mark) or Failed (the words and what happens next). Open, Retry, Stop and "run again with another model" are on the
1727 row. Six identical failures in a day are one row, `×6`, that expands. A failure never shows the provider's own text.
1728- **The Agents overview** (instead of an empty middle): the next scheduled run with a countdown, today's and this
1729 week's numbers (runs, done, failed, spend) as three small tiles, the last three results as rows, and "Things agents
1730 can do for you": a grid of six skills from Actor's catalogue that start a run in one tap. The foot stays one row:
1731 "All runs" and "Give an agent a task".
1732- **The Vibe tab has a conversation list** (`dock-vibe.js`): New conversation at the top, then each chat with its last
1733 line, its time and the site it began on; a back arrow in a conversation. The tab opens on the conversation you had
1734 open within the last twenty minutes, else on the list, and the choice is the Dock's synced state, so it follows you
1735 across pages and sites. "Vibe did not answer" says why and offers a retry. The composer shows at once, with the
1736 attach and the mic.
1737- **The Chats tab has an AI chats row** among the DMs, placed by date like the others: the Vibe mark, "AI chats", the
1738 count, the last conversation's title and when Vibe answered; tapping it opens the Vibe tab on its list. A group's row
1739 shows up to three faces, not a number sign.
1740- **The Dock keeps its size and corner per person** and its grip is always visible; the Vibe conversation and the
1741 Agents filter are restored with it.
1742- **Restyled here (Opus):** a run is a row, not a box: the state as a coloured word (Running blue with a moving bar,
1743 Waiting amber, Done green, Failed red), its words on two lines at most, the title opens it, Approve and Retry as filled
1744 pills, the rest under a dots button. The tiles lead with their number (11 runs) and dots for done and failed. The Vibe
1745 list gives each conversation its mark and its site's name (Blog, not openvibe.blog) and New conversation is a button.
1746 The AI chats row has a Vibe avatar like every other row; group faces are ringed in the panel's own colour.
1747- The one-tap skills are read through Network (`/api/v1/me/actions/skills`): every site's CSP refuses openvibe.actor.
1748- The extension's read-only Dock offers no approve, decline, stop or retry.
1749- **Early hints: the Link header only, by default** (`openvibe-shared/sw` `earlyHints`). nginx 1.28, the host's, takes an
1750 upstream 103 as the final response and sends the real 200 as its body (tried 11-09 against nginx:1.28-alpine). The 103
1751 goes out only with `{ send103: true }` or `OV_EARLY_HINTS=1`, for a proxy that passes it on (nginx 1.29+ with
1752 `early_hints`). Cloudflare's Early Hints read the Link header.
1753- **The Feed's composer takes pictures** (the owner, 11-09: "you can make posts and put images"). Up to four, each
1754 with a line saying what it shows (Community requires the alt text; Post stays off until each has one). Big phone
1755 photos are scaled to 2560 px before they go. Each picture uploads through Network (`POST /api/frame/attachments`,
1756 Network #719) and the post names them (`POST /api/frame/posts`). Previews are small data: images, because not
1757 every site's img-src allows blob:.
1758
1759## 4.167.0 — 2026-11-09
1760
1761- **The Feed, everything on the network, day by day** (the owner: "one huge feed of everything… all kinds of events and
1762 even global chat… everything date by date… filter it by different things"). Community #310's kinds draw as rows of
1763 what happened: people who joined (the count on a green tile), a busy chat room ("#general is busy", 15 messages this
1764 hour), a quest badge (the person's face with a trophy), a calendar event (a date tile, the start in your own time), a
1765 wiki page (its name, not its address), a stream that is over and the network's updates. Pictures, videos and posts
1766 stay cards; their views, likes and replies show as icons with numbers.
1767- A quiet **day line** (Today, Yesterday, the weekday, then the date) where the day changes; under an earlier day a row
1768 says its clock time, not "yesterday" again.
1769- New chips: **Following** (signed in: the people you follow), Chat, Events, Wiki, Quests.
1770- One centred column on a computer (two columns left holes beside tall cards).
1771- Fixed: a card with no picture (or a person with no avatar) drew a broken image of the page itself: `safe('')` resolved
1772 an empty address against the page. Fixed: the Feed's root class `ovf` was the footer's, so the Feed took the
1773 footer's margin, padding and top border (the gap above the chips); the Feed's classes are now `ovfd-*`.
1774- Past go-lives fold into one card again now that Community sends them as kind `stream`.
1775
1776## 4.166.0 — 2026-11-09
1777
1778- **The Feed on every site** (the owner: "I still don't see the custom doom scrollable feed"): the menu's Feed, wall and
1779 composer reach Community through Network's frame relay (`https://openvibe.network/api/frame/feed`, `/wall`,
1780 `/status`; Network #714). Every site's Content-Security-Policy allows openvibe.network and refused
1781 openvibe.community, so on every site but five the Feed fell back to its old sections. `o.frameBase` overrides the
1782 relay (tests, staging); the menu no longer calls openvibe.community directly.
1783
1784## 4.165.0 — 2026-11-09
1785
1786- **Faster between pages and sites** (the owner, 11-09: "when you change sites… faster across sites… better caching on
1787 the client"): the navbar adds a same-site prerender rule beside its prefetch, and prefetches links to the other
1788 OpenVibe sites; `openvibe-shared/sw` sends the shell's files in a 103 Early Hint. The shared worker
1789 (`/openvibe-sw.js`, also the Web Push worker) keeps **only content-addressed files** (a hash in the name or
1790 `?v=<hash>`: immutable, public) and answers them from its cache; pages and every other file go to the network as the
1791 browser sends them, so no page copy outlives a sign-out, nothing cuts a slow page off, and no stale script meets new
1792 HTML (security review). A kill switch (`/api/v1/sw-config` on Network) unregisters it everywhere. Sites opt in to
1793 registering it with `OpenVibeNavbar.init({ sw: true })`.
1794- `robots.txt` welcomes Perplexity-User, Claude-User and Claude-SearchBot by name (discoverability, ask 93).
1795- You: **Most used** links to **Your stats** on OpenVibe Report (`/me`: private by default, each section publishable).
1796
1797## 4.164.0 — 2026-11-09
1798
1799- **Chat's two views** (`openvibe-shared/messages`, `OVMessages.render(el, { messages, view: 'room' | 'bubbles', me })`;
1800 the owner, 11-09: "texting bubbles like Android/iOS Messages, and the live chat room… switchable"). Bubbles: runs join
1801 on the inner corners, the avatar beside the first with its column kept through the run, a coloured initial when there
1802 is no picture, the time under the last, read ticks, sticky day chips. Room: one line per message wrapping under the
1803 name on a phone, each person's name in a steady colour (tuned for dark and light). Only the newest message animates.
1804 `OpenVibeUserMenu.register()` lets a site add actions to the shared user card.
1805- **Page head** (`kit.pageHead({ back, icon, title, lede, actions })`; ask 64: "less websitey… an icon system"): a small
1806 named back chip (44 px to the finger), the page's icon tile and name, 44 px actions, and the lede once and whole on
1807 every screen. Product heroes can opt in (`showcase.hero({ pageHead })`).
1808- **Upload kit** (`ov-upload.js`/`.css`: `OVUpload.picker`, `.queue`, `.progress`): drag and drop or choose, each file a
1809 row with its picture or a file glyph, an × and its own progress (speed and time left smoothed); the total above for two
1810 files or more; done draws a tick; Calm drops the sheen. Sites bring the transport (`upload(file, { onProgress, signal })`).
1811- **The Feed, fixed for real data** (Community's /api/v1/feed is live): LIVE shows only while a channel is live now
1812 (`live_now`, with a breathing chip and a red edge); back-to-back go-lives that are over fold into one card of faces and
1813 names; a trending picture shows its site's icon and "Trending" (not a "?"); video cards no longer leave the flow (their
1814 class collided with the preview video's, `.ovf-video`; cards are `.ovf-k-<kind>` now).
1815
1816## 4.163.0 — 2026-11-09
1817
1818- **You, redone** (the owner, 11-09: "the U tab still sucks, and you didn't do all the improvements"). You on top: your
1819 picture (online), name and @handle linking to your profile, with **Display** beside it (the Look panel: theme, icons,
1820 appearance, text size, motion, opening in place; navbar passes `look` to the menu) and **Settings** (Account). Display
1821 leaves the foot of the tab for signed-in people.
1822- **Jump back in** is a row of cards you swipe: each place's own hue icon, a two-line title, a time that keeps up while the
1823 menu is open, and LIVE with a breathing ring for a stream that is on now. It replaces Recent, which repeated the grid.
1824- **Most used**: the pages you opened per site, as one bar and its key (five colours that tell apart in dark and light).
1825- **Your own AI** is one compact row (mark, words, Set it up, an ×): a third of the height.
1826- **Smart folders**: until you arrange your home, the sites past the first page sit in folders from the network's groups
1827 (Watch, People, Make, Read, Play, Shop). A folder shows four different icons (Quick tools showed `</>` four times).
1828- **Drag lands where you drop**: the drop is found by the grid's slots, not the sliding apps' drawn boxes (an app sliding
1829 under the finger took the drop, so it landed one place off now and then).
1830- **The video player's bar matches Live's**: 40 px buttons (44 on touch), centred icons.
1831- Calm keeps the cards' fade and a slower live breath.
1832
1833## 4.162.0 — 2026-11-09
1834
1835- `kit.head` accepts an icon tile; `kit.section` accepts an icon and an action row that wraps on phones.
1836 Rails accept an icon and use a labelled button for their action. Existing section markup is preserved when neither option is supplied. Empty-state actions already use primary
1837 icon buttons. `tools/plain-sections-sweep.js` audits public pages for plain UI headings, empty lines and action links.
1838- **The menu's You tab is a home screen** (owner, 11-09: "make it more like a phone"). `ov-home.js` (a sibling, loaded
1839 the first time You or Feed opens): your Actor on top (its state and step, or one invitation), Recent (the last five
1840 places opened), then Home: your places in a grid, the most popular until you arrange it, with Tools (every tool with
1841 its own icon) and Build as folders. A long-press (or Edit) starts arranging: drag an app to move it, arrow keys move
1842 the focused one, − takes it off Home, All apps (N) lists every app and puts one back with +. The arrangement is kept
1843 on the device and on Network (`GET/PUT /api/v1/me/home-screen`, once Network serves it). Recent searches are gone
1844 from You (the field shows them).
1845- **The Feed tab is a wall.** What's new with you (the composer, when Community says you can post), today's numbers
1846 (people, streams, posts, new videos, with 14-day lines), the live bar, then the network's pulse: newest first,
1847 back-to-back "went live" in one row of names, Show more. Every feed card shows until you turn some off (was the
1848 first two); What's new names each site, what changed in plain words, when, "New" since your visit and "Big update",
1849 and counts today's changes.
1850- **The first tab is the site's name, never a page's word for itself** ("Home", "Welcome").
1851
1852- **The Dock fills the phone, and its header is one row** (owner, 11-09: "go all the way to the top… wasted space", "I
1853 don't really understand what the pin does", "clicked off… it didn't close the pop over"). Phones: the Dock is the
1854 whole screen, over the bar, inside the notch area; tablets: a full-height panel on the right; desktops: a 420 × 640
1855 card by default. The header is the four tabs, then More and Close; More holds Start a timer, "Keep the Dock's button
1856 on every page" (a switch that says what off means) and Open in a window of its own. More and the timer close on a tap
1857 anywhere else, in the Dock or on the page, and on Escape. The Chats tab is **Messages**.
1858
1859- **No pill above the headline.** `sc.hero()` no longer draws an eyebrow (the owner, 11-09: the pill above every home's
1860 headline "looks really cheesy… generic"); the headline and its lede say what the site is. `eyebrow` is ignored.
1861
1862- **Changing pages feels like one app** (owner, 11-09). A page chosen in the menu closes it (folding into the orb on a
1863 phone) without spending Back; a menu still open when a page is swapped in place marks the new page; a link that leaves
1864 the page draws the top bar at once. The bar (navbar and Boost alike) is 3 px of the OpenVibe gradient from the theme's
1865 accents, with a soft glow and a bright running head; reduced motion keeps it still.
1866- **Loading fits a short player** (the owner, 11-09): under 300 px the mark, words and buttons shrink and the hint goes;
1867 Try again carries the retry arrows and other actions are icon buttons; the V ticks green before the screen fades.
1868- **The Dock's Actor tab** (#351, under the name Agents), **Dock chats from a site's session** (#364), **one sheet
1869 behaviour for scroll and dismissal** (#371), **six glyph styles** (#367) and **section headings with icons** (#369).
1870
1871## 4.161.0 — 2026-11-09
1872
1873- **Menus open from where you tapped** (the owner, 11-09: "custom animations… content aware… where you're clicking"):
1874 `OpenVibeNavbar.pop(el, trigger)` / `.unpop(el, done)`, a clip reveal with a small spring from the trigger and a quick
1875 close; the account menu, the Display panel, the composer's Options and Attach, and the Dock (from its pill on a
1876 computer) use it.
1877- **Calm is gentle, not still** (the owner: "the animations need to work even on calm mode"): the mark still animates a
1878 state (its loops play once) and menus pop small and short; only intros, hover plays and the season's leaves rest.
1879- **The menu's tabs show what they are**: this site's own mark, Feed with a live dot or a count of what changed since
1880 you last looked, You with your picture and an online dot; the tab is remembered per site.
1881- **You**: app icons (no ring; the glyph fills a squircle tinted with each app's own hue), Quick tools named apart from
1882 the Tools site with each tool's own icon, a grid that fills a computer's width, editing that never scrolls the page
1883 and scrolls the list near its edges, the AI invite redesigned; Jump back in with each site's icon, cleaner titles and
1884 Live now.
1885- **The account menu**: Wallet and Profile tiles; the footer keeps light, dark and auto with one Display button; the
1886 Look panel holds text size and motion with the theme and icons; the menu's Display row matches.
1887- **The Dock**: Close is the menu orb in its x form; More's dots sit on the centre line.
1888- **Sideways strips say they scroll**: kit rails and `[data-ovk-scroll]` fade at the edges where more is hidden, show a
1889 position bar, nudge once on a phone, and `drift` moves until the first touch.
1890
1891## 4.160.0 — 2026-11-09
1892
1893- **Feed v2: the menu's Feed is a real feed** (the owner, 11-09: "a real feed… you scroll through easy content… a filter
1894 system… clean, intuitive"). When Community's `/api/v1/feed` answers, the Feed tab is the composer, today's numbers, the
1895 live bar and one stream of cards by kind (live, clips and videos with a muted preview that plays only in view and never
1896 with reduced motion or data saver, pictures, posts, memes, games, compact updates), chips on top (For you, Live, Clips,
1897 Videos, Pics, Posts, Memes, Games, Updates), more as the end comes near, "You're all caught up" at the end; without
1898 the API, the wall and its sections stand as before.
1899
1900- **Every section heading has an icon, and sections stand apart** (the owner, 11-09: "these section titles should have an
1901 icon… look nicer and more consistent, separate things… a universal system"). The showcase kit's headings carry an icon
1902 tile (features: grid, steps, code, demo, compare, pricing, limits, stories, stats, cards, spotlight, questions, network,
1903 apps each have a default; `icon:` picks another); each section has a hairline and room above it.
1904- **More on OpenVibe** (was "Part of OpenVibe"): the related sites with their icons, a live "Happening on OpenVibe" strip
1905 (who is live and the sites' trending items, from Network's Frame, text only) when the page includes `sc.NETWORK_JS`,
1906 and "Every OpenVibe site →" to openvibe.network/sites.
1907
1908- **`openvibe-shared/ai-marks`: the AI makers' marks, one place** (the AI comparison plan, R3; the owner, 11-09: our
1909 hand-drawn family glyphs read as "what is it even supposed to be?"). `family(idOrName)` finds the maker from a model
1910 id, slug, family or provider name (16 makers: OpenAI, Anthropic, Google, Gemma, Meta, DeepSeek, Qwen, Mistral,
1911 Moonshot, Zhipu, MiniMax, Microsoft, xAI, NVIDIA, Cohere, Perplexity); `mark()` draws it monochrome in a theme tile,
1912 labelled with the maker's name; anyone else gets the neutral spark. Drawings from @lobehub/icons-static-svg (MIT),
1913 regenerated by `scripts/build-ai-marks.js`.
1914- **The composer's Options, the navbar's own buttons, the keyboard** (the owner, 11-09, on openvibe.codes: "too many
1915 options… hidden under a cog"; the sidebar button "to the left of the logo… the navbar SDK and API… other people can add
1916 icons"; the keyboard "takes up a lot of space"). `thread.composer({ options, examples })`: one Options button with the
1917 pickers behind it (a panel on a computer, a sheet on a phone with the page held still, a `<details>` without script),
1918 changed pickers as chips with ×, a dot on the button, and a placeholder that walks through examples.
1919 `OpenVibeNavbar.lead(spec)` and `.action(spec)`: a site's own buttons in the bar; a kit sidebar's toggle moves there on
1920 a phone. `html.ov-kb-open` and `--ov-kb` on every site; start pages fold `data-ovt-fold`; the Dock's phone sheet ends
1921 above the keyboard and no longer opens it by itself on a touch screen.
1922- **The Dock's pin is an upright pushpin** whose lines never cross (it read as a pin with a line through it).
1923- **The composer's attach, answers' actions and the quiet line** (the owner, 11-09, on Vibe: "the image button is broken…
1924 support all file types… a better file attach button… Copy and Try again should be actual icons… 'Vibe can get things
1925 wrong' should be under"): attach is one + button for every file type, and with `attach: { menu: true, library }` a
1926 menu of Files, Photos and videos, Take a photo (phones) and From your OpenVibe files; Copy and a site's actions are
1927 icon buttons named by their word (a tick when copied; Try again draws the retry arrows); the composer's note sits
1928 under the box.
1929- **A phone's drawer holds the page still** (kit.sidebar): the page behind cannot scroll and the drawer never hands its
1930 scroll on (the owner: a swipe on the drawer "scrolls the actual page").
1931
1932## 4.159.0 — 2026-11-09
1933
1934- **Homes v2: `openvibe-shared/showcase-stage`** (plan `OpenVibe_Homes_v2_Plan_2026-11-09.md`; the owner, 11-09: "make the
1935 home pages more unique… show the features… custom animations… infographics… a shared, modular system… still SEO
1936 friendly"). `stage()` is a hero with one H1, the lede and a live demo panel of the product (the site's own HTML in a
1937 window, phone or card frame) over the site's motif (13 drawn motifs: wave, planes, strip, shutter, bubbles, converge,
1938 rise, path, stickers, grid, pen, orbit, cursor); `reel()` features as cards that swipe on a phone with a small motion
1939 each; `numbers()` real numbers that roll up once with 30-day lines; `flow()` how it works with a line that draws.
1940 Server-rendered words and numbers; motion only adds, never inside an SVG's children, and stops for reduced motion and
1941 Calm. Its CSS (`CSS`) and starter (`MOTION_JS`) are separate, so homes that do not use it do not grow.
1942
1943## 4.158.0 — 2026-11-09
1944
1945- **The menu's Home.** A site whose links never name its home (Exchange: Models, Chat, Arena…) now gets Home first in
1946 "On <site>", and on the home page Home wears the highlight like every other page's link (owner, 11-09). A site that
1947 already names its home gets no second one.
1948
1949- **Footer legal links are 44 px wide to a finger.** On a touch screen, Terms, Privacy, DMCA and the compact links
1950 reached 36 px across (11-09 tap audit on six sites, `tools/tap-audit.js`); their hit area now extends 5 px each side
1951 (they sit 20 px apart, so the areas never meet).
1952
1953## 4.157.0 — 2026-11-09
1954
1955- **Every shell page has a tab icon.** `shell.page()` adds the service's OpenVibe mark (`openvibe-shared/app-icon`
1956 `favicon`, the same tint as its app icons) as an inline SVG `<link rel="icon">` when the page's `head` has none.
1957 Without one the browser asked `/favicon.ico`, which most sites answer 404, and the tab showed a blank page icon
1958 (11-09: openvibe.computer, openvibe.email and openvibe.studio). A page with its own icon keeps it; `favicon: false`
1959 opts out.
1960
1961## 4.156.0 — 2026-11-08
1962
1963- **Locker unlocks with a passkey on this device.** Setting up a locker can use a passkey instead of a passphrase:
1964 the passkey's PRF output (user verification required) seals the private keys, and only the credential id and its
1965 salt are kept in this browser; the 24-word recovery kit stays the way back on any other device. Unlocking offers
1966 "Unlock with this device's passkey" when one is set up. The kit can also be printed.
1967- **Shared files open as streams** (`OVLocker.openStream`): large locked files are read chunk by chunk, each one
1968 verified, without holding the whole file in memory. No private key is handed to a service or a caller.
1969- `ov-locker-core.js` is regenerated from openvibe-sdk 0.101.0 (bounded header checks against a stored byte count,
1970 key export after recovery).
1971
1972## 4.155.0 — 2026-11-08
1973
1974- **The menu names each site by a short product name** (the owner, 11-08: "the names of websites … not cut off …
1975 cleaner and nicer across all the websites"). On openvibe.website the first tab read "Websites that l…", the box
1976 "Search Websites that look finished, or a…" and the heading "On Websites that look finished": the page title's last
1977 part, a tagline. The name now comes, in order, from the site's own short label, the network's name for the address,
1978 the "OpenVibe.X" in the title, then the address itself (openvibe.website → Website, openvibe.party → Party), and a
1979 title part only when it is short. Website, My, Link, Party and Computer read right now; every other site keeps its name.
1980- **A site under a path is that site only on that path:** Inventory lives at openvibe.my/inventory, so openvibe.my
1981 itself is My, not Inventory (the match was by host alone).
1982- Tests: `test/search-site-name-chrome.test.js` (a tagline title, a long label, and a path-aware site; it fails on
1983 4.154.0 with the owner's exact text).
1984
1985## 4.154.0 — 2026-11-08
1986
1987- **Float your agent's screen** (the owner: overlays "usable for agents, Vibe chat, Codes"). The Dock's Agents tab
1988 gains **Float** beside Pause, Curtain, Mute and End: the agent's live screen (Live's agent embed, with its own
1989 renewing view ticket) moves into a floating card that follows the person around this site and on to other OpenVibe
1990 sites, and the Dock closes while they watch.
1991- **Link shares are not first-party** (ahead of Connect's shares moving to `<label>--<user>.openvibe.link`): a share
1992 is a person's own app, so `*--*.openvibe.link` and `*--*.openvibe.website` are never a card's source or a card's
1993 destination; only openvibe.link itself and connect.openvibe.link are.
1994- Tests: `test/overlay-chrome.test.js` (share hosts refused, Link's apex and Connect allowed).
1995
1996## 4.153.0 — 2026-11-08
1997
1998- **Signed in to the network, signed in here — on phones too** (the owner: "Why am I not auto signed into some domains
1999 even though I signed into the network?"; the audit: 3 of 56 sites signed a person in with third-party cookies
2000 blocked, 46 with them allowed). The navbar's quiet check asked Network from a hidden frame, which a browser that
2001 blocks third-party cookies never lets see Network's session. Now, when there is a reason to think the person has a
2002 session (this site signed them in before, or they arrived from another OpenVibe site, by its referrer), the navbar
2003 takes the top-level round trip (`/auth/login?silent=1` → Network `prompt=none` → back), where Network's session is
2004 first-party; a guest comes back with `sso=none` and is not asked again. A first visit from anywhere else keeps the
2005 quiet check and FedCM, so a newcomer from a search engine gets no redirect. At most once per tab per ten minutes,
2006 never for bots, never after a sign-out.
2007- **overlay.js:** a page that already plays what a carried card would (Live's channel page for its own embed) turns
2008 it away with `window.OVOverlayHere = (card) => true`; cards are restored after the page's own start-up.
2009- Tests: `test/navbar-auth.test.js` (referrer from OpenVibe, Shoes, a search engine, typed in, the same site, a
2010 look-alike host), `test/overlay-chrome.test.js`.
2011
2012## 4.152.0 — 2026-11-08
2013
2014- **Floating cards that follow you across OpenVibe** (`overlay.js`; the owner: a mini player you move and resize like
2015 YouTube on Android, with the room's chat, that keeps going on other sites, and an SDK any site, agent, Vibe chat or
2016 Codes can use). `OpenVibeNavbar.overlay({ id, src, title, href, live, side })` opens a card that frames a first-party
2017 page (Live's `/embed/<name>`, a chat, an agent): drag it and it settles in the nearest corner (a flick carries it),
2018 pull or throw it off the side to close it, resize it with its grip, two fingers or a double tap, and a tap shows
2019 its controls (LIVE, its name, sound for a Live embed, chat, open the full page, close). It sits between the navbar
2020 and the phone's app bar, never under either; its chat opens beside it, or as a sheet above the app bar on a phone.
2021 At most three at once, each in its own corner; corner and size remembered per kind on the device.
2022- **Across sites:** while a card is open (or a page declares one: `OVOverlay.declare(id, …)`, for Live's own mini
2023 player), a same-tab link to another OpenVibe site carries it in `#ovo=…`; the next site's navbar loads overlay.js,
2024 opens the same cards and takes the hash out of the address at once (it never reaches a server). Only first-party
2025 https sources open, checked again on arrival; a reload on the same site brings the cards back.
2026- **The next site starts sooner:** when a pointer, a finger or the keyboard reaches for a link to another OpenVibe
2027 site, the navbar opens the connection to it then (preconnect + dns-prefetch, up to 12 origins a page).
2028- Test: `test/overlay-chrome.test.js`.
2029
2030## 4.151.0 — 2026-11-08
2031
2032- **The menu marks the page you are on** (the owner, on openvibe.chat/messages: "highlight based on page"). In
2033 "On <site>", the link whose path is this page's, or the longest one this page sits under (/messages/abc under
2034 Messages), wears the accent and `aria-current="page"`; "/" only on the home itself; another site's link never.
2035- **On a phone the menu opens without the keyboard.** The orb opens a menu, not a search, so the field is no longer
2036 focused when it opens there (the keyboard covered half the menu); focus goes to the close orb until the person taps
2037 the field. On a computer, and when the menu opens with "/", the field is focused as before.
2038- Test: `test/menu-here-current-chrome.test.js`.
2039
2040## 4.150.0 — 2026-11-08
2041
2042- **The menu's button turns into its close, on top of the menu** (the owner: "make it render on top … a cool effect as
2043 it goes in and out of the menu … turns it into the close button when it's open"). On a phone the menu covers the bar,
2044 so it draws the orb in the button's own place, on top: as the menu grows from it, the orb glides into the search
2045 field's end and its nine dots turn into an x (the edges fold away, the corners and the centre stay). That orb is the
2046 close; the separate round x is gone. Closing plays it backwards: the orb glides back, its x turns back into dots, and
2047 the menu folds into it. Reduced motion: no movement.
2048- **The bell opens your notifications.** With the Dock on the page, its Activity tab is the bell's list, and the bell's
2049 own panel opened under the Dock's sheet, so a tap seemed to do nothing (the owner). The bell now opens the Dock on
2050 Activity, and a second tap closes it; without the Dock it opens its panel as before
2051 (`OpenVibeNotifications.setBellAction`).
2052- **The Dock on a phone shows every tab whole.** The open tab's name was clipped ("Ac") because four tools took the row.
2053 The header is two rows on a phone: the tools beside the grab handle, then the four tabs sharing the width, each icon
2054 over its name. The pop-out (a separate window) is not offered on a phone. The notification filters scroll sideways,
2055 and a fade now says so.
2056- **Press feedback** on the bar's orb, messages, bell and picture: a small spring on a tap.
2057- Budgets: dock.js 16.80 KB brotli (16.55 measured).
2058 Tests: menu-close-chrome (the orb is the phone's close, its dots an x, one close, it folds and closes),
2059 dock-size.
2060
2061## 4.149.0 — 2026-11-08
2062
2063- **The menu has tabs at the top** (the owner, 11-08: "we should have tabs at the top … a feed tab, which allows the
2064 user to set the feeds … remember whichever tab the user last had open … the main tab should just show links that
2065 exist on that site, and under that the full list of sites … make it nicer, more compact"). Under the search field,
2066 one segmented row: **this site** (labelled with its name), **Feed** and **You**. The menu reopens on the tab last
2067 chosen (this device); the legacy drawer's "All sites" still opens on this site's tab.
2068 - **This site:** its own pages, then every OpenVibe site in a compact grid (a small icon and the name, three to a
2069 row, two on a phone), grouped as the Frame groups them with this site's group first and this site marked. No fold.
2070 Who is live shows here only on openvibe.live; everywhere else it is the Feed's (it confused people elsewhere).
2071 - **Feed:** who is live (and a red dot on the tab while anyone is), the feeds the person chose (Video's trending,
2072 the forum, Games' new worlds…), what changed lately, places new to them and where people go next from here; the
2073 chooser ("Choose your feeds (2 of 4)") at its end.
2074 - **You:** their workspace (signed in), their places (else the popular ones), the pages they were on, tools, their
2075 searches, and the display controls (theme, text size, motion).
2076 - Tighter section headings; switching tabs on a phone does not raise the keyboard.
2077 Tests: menu-feeds-chrome (three tabs; the feeds are on Feed; it reopens on the last tab), navbar-launcher (the list
2078 of every site, compact, no fold; Display on You), search-loading-chrome.
2079
2080## 4.148.0 — 2026-11-08
2081
2082- **On a phone the menu button is the orb, in the burger's place** (the owner, 11-08: "replace the hamburger with
2083 that button… we don't need both"). 4.137.0 kept one menu button on a phone but drew it as a plain three-line burger
2084 and hid the orb, the network's own nine dots with their ripple, wave and turn. The phone's menu button, at the bar's
2085 right where the burger was, now draws the orb: the same dots, the first-visit ripple, the wave while the menu loads,
2086 the glow on a press and the turn into a ✕ while the menu is open. Still one button: the orb beside the site's name
2087 steps aside at 860 px and below, as before; a wide bar is unchanged. The button opens the menu on this site's pages
2088 first, and the menu's round ✕ (4.142.0) closes it.
2089- **One size for the bar's controls** (the owner: "it should be a little bigger so it matches the sign in button…
2090 the things need to be more consistent in the nav bar in size"). Sign In, the orb, messages, the picture, the
2091 balances and a featured link are all 36 px tall (34 px on a phone under 400 px); before, Sign In was 33, the orb 34
2092 (28 by 32 on a phone, its dots drawn at 22 px), messages 32 to 36, the picture 32 and the balances 30 to 32. The
2093 orb draws its dots at 26 px (24 on a phone), and Sign In's corners follow the orb's (10 px).
2094 Tests: navbar-phone-fit-chrome checks, 320 to 430 px, that the one menu button draws the orb's nine dots and no
2095 burger lines and that nothing in the bar overlaps; navbar-brand checks the markup.
2096- **The phone's menu button keeps the bar's edge.** The orb's tap area reaches 12 px past the button, which pushed the
2097 bar 6 px past a 390 px phone: the page zoomed out to fit it and the Dock's pill sat 10 px from the edge instead of
2098 16. The button's tap area now stops inside the bar.
2099- Budgets: navbar.js 45 KB brotli (44.3 with the phone's menu button drawn as the orb).
2100
2101## 4.147.0 — 2026-11-08
2102
2103- **A link in a Dock message shows its card.** OpenVibe.Chat now resolves a known OpenVibe link when a message is sent
2104 (a video on OpenVibe.Video, a party invite, a shared voice or a skill on OpenVibe.Actor) and the DM API carries it on
2105 the message as `card`. The Dock draws it under the message: a small picture when there is one, the title as a link,
2106 one line, and the one move ("Watch together", "Join the party", "Borrow this agent", "Install"). The new sibling
2107 `dock-card.js` draws it and is loaded the first time a conversation shows a card, so dock.js grows by about 200
2108 bytes. Every field is data: text set as text, links only https, pictures only from openvibe.media (checked in
2109 `test/dock-chrome.test.js` with a card that tries a script link, a picture from elsewhere and a script button).
2110
2111## 4.146.0 — 2026-11-08
2112
2113- **On a phone, a home's words and its main action come first.** `showcase.hero` with an orbit put the picture above
2114 the title on screens under 860 px, so a phone saw a 250 px diagram before it saw what the site is or its one main
2115 button (Work's "Search the listings" sat at about 560 px). The orbit now follows the actions: the main button moves to
2116 about 270 px at 390 px wide, and the orbit, still a row of links to the site's places, follows it. It is a little
2117 bigger on a phone (up to 300 px), and the centre's caption is smaller and narrower, so it no longer runs into the
2118 lower circles ("from public boards" overlapped "Your agent" and "Alerts" on Work). Wide screens are unchanged.
2119 Found in the track X review of the Work home.
2120
2121## 4.145.1 — 2026-11-08
2122
2123- **The Dock stays open when a tap on the page opens it, on a phone.** The phone sheet closes on a tap outside it, and
2124 the tap that opened it (the share sheet's OpenVibe Chat, Ask your agent, any page button calling the Dock) was still
2125 on its way up when the sheet opened, so it read as outside and the sheet closed at once. A click made before the
2126 sheet opened is never "a tap outside" now. Found in the production proof of a party's Invite → OpenVibe Chat.
2127
2128## 4.145.0 — 2026-11-08
2129
2130- **ov-canvas.js: an agent's live panel, drawn the same on every site** (`actor.canvas@1`, Contracts 0.340.0, ADR-059,
2131 plan HO9). `OVCanvas.render(host, doc, { onAsk, onCheck })` draws headings, text, lists, checklists, tables,
2132 metrics, bar and line charts (inline SVG, their numbers also a table for screen readers), pictures and links.
2133 Every block is built node by node and text is plain. A picture is drawn only from openvibe.media (one that fails to
2134 load is removed), a link only when it is https, with its host shown. A button only calls `onAsk` with its label (the
2135 page sends it to the agent as the person's message); a tick calls `onCheck`. Without the callbacks the canvas only
2136 shows. An older version never replaces a newer one. A wide table scrolls inside its own box, so a phone's page never
2137 scrolls sideways. A bar chart stands on zero; a line spans its own range.
2138
2139## 4.144.0 — 2026-11-08
2140
2141- **Send it on OpenVibe.** The share sheet (ov-share.js) offers **OpenVibe Chat** first to a signed-in person (the Dock
2142 is on the page): the sheet closes and the Dock opens on **Send to…**. The person picks one of their conversations,
2143 or writes to someone new by their OpenVibe name, and the title and link wait in that conversation's box, unsent,
2144 until they press Send. Every site's Share button gets it with no change of its own: a party invite, a video, a
2145 paste or a post goes to a friend on OpenVibe in two taps.
2146- `OpenVibeDock.share({ text, title })` (dock.js; Send to… itself is `dock-share.js`, a sibling loaded the first time
2147 something is sent, so the Dock on every signed-in page grows by 0.3 KB) is the same move for any page: Chats opens on Send to…; Cancel
2148 goes back to the list. A name nobody has says so (and the nearest name it found). Starting a conversation by name
2149 reads OpenVibe.Chat's `/api/dm/users/search` and `POST /api/dm/conversations` with the person's own token.
2150
2151## 4.143.0 — 2026-11-07
2152
2153- Add `ov-sync.js` (seven domains plan B4): `OVSync.attach(mediaEl, { room, socket })` follows a `common.sync-clock@1` timeline over Chat's room socket. It measures the server clock with five `time` samples (best round trip, refreshed every 60 s), computes the room's position from `position_ms + (serverNow - at_ms) * rate`, holds the element within ±80 ms (a 0.95–1.05 playback-rate nudge between 80 and 400 ms, a seek past 400 ms), pauses and plays with the room, and puts a non-holder's own seek, play, pause or rate change back with a short notice while a holder's change becomes a `sync`. No dependencies; served as `/shared/ov-sync.js` and imported as `openvibe-shared/ov-sync`.
2154- ov-sync follows an https source only; Chat already keeps a room's clock on OpenVibe's own hosts.
2155
2156## 4.142.0 — 2026-11-07
2157
2158- **The menu has a close button on a phone** (the owner, 11-07: "This needs a close button"). Since 4.129.0 the menu
2159 covers the whole phone screen, the navbar's burger included, but `search.js` still hid its Close button at 860 px and
2160 below on a site with a burger, so only Back closed it. Now a round x (40 px, a 48 px hit area, "Close the menu" for a
2161 screen reader) sits at the end of the search pill at 860 px and below; a wide screen keeps the word Close. Test:
2162 `test/menu-close-chrome.test.js` (a phone layout: shown, on screen, the hit area, a tap closes; wide: the word).
2163
2164## 4.141.0 — 2026-11-07
2165
2166- **The forms kit loads when a form control is drawn, not merely present** (the owner, 11-07: "make things faster and
2167 more optimized").
2168 - A closed dialog's or a hidden box's select, checkbox or file input no longer counts.
2169 - Live's shell carries hidden modal forms, so every Live page fetched `ov-forms.js` and `.css` (17 KB) for
2170 controls nobody saw.
2171 - Hidden controls are watched with an IntersectionObserver, and the kit loads when one is drawn (a dialog opens, a
2172 fold unfolds).
2173 - A page with a drawn control loads it once it settles, as before.
2174 - Test: `test/forms-when-seen-chrome.test.js`.
2175
2176## 4.140.0 — 2026-11-07
2177
2178- **Native: no more Material lavender.**
2179 - The Compose export (`tokens.compile(…, 'compose')`, `scripts/native-check.js`) now maps Material 3's container
2180 roles to Vibe surfaces: `surfaceContainer*`, `surfaceBright`/`Dim`, `surfaceTint`, `secondaryContainer` (the
2181 navigation bar's selected pill) and `inverseSurface`.
2182 - Before, Compose filled cards, the bottom bar, dialogs and chips with its own lavender. OpenVibe Link for
2183 Android's first screens showed it.
2184 - Flutter's scheme is unchanged: its constructor takes these roles only from 3.22.
2185 - No web change, so no pin round is needed for this release.
2186
2187## 4.139.0 — 2026-11-07
2188
2189- **The menu's feed rows lead with their picture.** A row with a thumbnail (Trending on Video, a live channel) no
2190 longer shows the site's icon beside it: the picture is larger (72 × 40) and the title moves left. In a group where
2191 some rows have a picture, the others carry their icon in a picture-sized tile, so the titles line up. A group with
2192 no pictures (New worlds) keeps its icons.
2193- `navbar.js`'s SIBLING_REV carries search.js's new hash.
2194
2195## 4.138.0 — 2026-11-07
2196
2197- **38 KB off most first visits** (the owner, 11-07: "make things faster and more optimized"):
2198 - the footer's "shipped" line (`shipped.js`, 12 KB) is fetched only when the footer comes near the screen;
2199 - brand logos (`brands.js`, 26 KB, the footer's social links) wait until a logo is near the screen, or opens
2200 from a fold;
2201 - most visits never scroll that far.
2202- **The rest of the files loaded from beside Shared's scripts go by content hash** (4.137.0 did the bar's):
2203 - `shipped.js` from the footer and from `frame.js`'s updates pages;
2204 - `brands.js` from the glyphs, through their build;
2205 - `route-transition.js` from Boost.
2206 - `scripts/sibling-revs.js` writes FOOTER_REV and BOOST_REV; `scripts/build-glyphs.js` writes BRANDS_REV.
2207 - Run the glyph build first, then sibling-revs.
2208
2209## 4.137.0 — 2026-11-07
2210
2211- **Releases reach phones at once.** The bar loaded the menu (`search.js`), the Dock, presence, panels, notifications,
2212 history, the site switcher, the app bar, the release watcher, the icons and the sign-in client without a version.
2213 - They went out as `max-age=300, stale-while-revalidate=86400`, so a phone could keep the old menu for up to a day
2214 after a release. The owner still saw the menu without feeds hours after 4.135.0.
2215 - Every one now loads by its content hash (`?v=`), which sites serve `immutable`: each release is new URLs, and no
2216 edge or browser has an old copy to give.
2217 - The Dock's add-ons (Vibe, the Action Center, the timeline, the agent view, the timer) and the notifications'
2218 realtime file go the same way.
2219 - `scripts/sibling-revs.js` writes all three tables. `test/sibling-revs.test.js` fails on any sibling loaded
2220 without its hash.
2221- **One menu button on a phone** (the owner, 11-07: "these buttons do the same shit"). Where the burger shows (860 px
2222 and narrower, on a bar with page links), the orb that opened the same menu steps aside. A bar without page links
2223 keeps its orb, its only way in.
2224- **One Close.** On a phone the menu's in-box "Close" steps aside for the burger's ✕.
2225- **The menu opens at once.** search.js is prefetched at idle once the page settles (not on Save-Data or 2G), and as
2226 soon as a finger or pointer reaches the burger or the orb. By its hashed URL it is cached until the next release.
2227- Budgets: navbar.js 44 KB brotli (43.5 with the hashes, which do not compress, and the warm-up), dock.js 16.0 KB.
2228
2229## 4.136.0 — 2026-11-07
2230
2231- **One Computer in the Dock** (plan 2026-11-07: "Surf, Computer and Actor are one experience"): a timeline step
2232 that works on a Computer shows its machine and cost hint (`computer.machine`, `computer.cost_hint`) — the same
2233 object the task record and Surf's card carry, never a copy. The Now card offers **Take over** (and **Give it
2234 back** while you drive) on a step that names its task (`ref`) and its Computer: one call to OpenVibe.Actor's
2235 `POST /api/v1/tasks/{id}/takeover` or `…/giveback`, the same hand-off the agent's own `hand_to_person` uses.
2236 No new layout and no new CSS: the line is an existing meta row and the button an existing Dock button.
2237
2238## 4.135.0 — 2026-11-07
2239
2240- **Ask your agent, from any page** (plan Z3; the owner, 11-07: "have your agent with you wherever you go"):
2241 - `kit.askAgent({ text })` draws a chip ("✦ Ask your agent"). Any control with `data-ov-ask="…"` works the same way.
2242 - A tap opens the person's Vibe in the Dock with the question waiting in its box, this page's context included. It is
2243 never sent for them: they read it, change it and press Send.
2244 - Signed out, the navbar sends them to sign in.
2245 - New calls: `OpenVibeNavbar.askAgent(text)` and `OpenVibeDock.ask({ text })`.
2246- **Your feeds follow you** (the menu's "Choose your feeds", 4.132.0):
2247 - signed in, the choice is saved to Network (`PUT /api/v1/me/menu-feeds`, network.menu-feeds@1);
2248 - it is read back on a new device the first time the menu opens there;
2249 - a first choice made before signing in is sent up then;
2250 - this device's copy paints at once and stands whenever Network does not answer, and the route is new, so until
2251 Network ships it nothing changes.
2252- **Every page opens its connection to openvibe.network early** (`seo.headTags()` adds
2253 `<link rel="preconnect" href="https://openvibe.network" crossorigin>`, except on the Network itself;
2254 `preconnect: false` turns it off). The phone-speed report of 11-07 found 102 of 108 pages asking openvibe.network for
2255 the bar, sign-in and the menu without one.
2256
2257## 4.134.0 — 2026-11-07
2258
2259- **Fixed: the Dock's Agent panel could not read the timeline.** The navbar hands the Dock the person's token as a function (it may change when they sign in again); the panel used it as a string, so every call went out as `Bearer function…` and the panel said the agent was not reachable. The token is now resolved when a call is made.
2260- The Dock can open at a step: `OpenVibeNavbar.openDock('agent', { task: 'tsk_…' })` marks that entry in the timeline and brings it into view, so a site can send the person to the exact task it started for them.
2261
2262## 4.133.0 — 2026-11-07
2263
2264- The menu's network cards draw an entry's picture when the site sent one (`frame.menu-card@1`'s optional item `image`, openvibe-contracts 0.320.0): a 64×36 rounded thumbnail, `object-fit: cover`, `loading="lazy"`, `decoding="async"`, empty alt, before the title and after the site's icon. The `src` is checked again in the menu (https only); a card or entry without a picture renders exactly as before.
2265
2266## 4.132.0 — 2026-11-07
2267
2268- **Your feeds in the menu** (the owner, 11-07: "integrate OpenVibe.Today and OpenVibe.News … the user should be able to
2269 configure which data feed streams are shown"):
2270 - every card a site publishes (`frame.menu-card@1`, collected by Network into the Frame's `cards`) is a feed you can
2271 choose;
2272 - "Choose your feeds (N)" under them lists each with a tick and two arrows: tick to show it, arrows to order it, four
2273 at most;
2274 - the choice is kept on this device (`ovs-feeds`). With none chosen, the Network's first two show, as before.
2275- **Fixed: the menu's Frame fields were dropped.** The navbar's Frame cache kept only `nav`, `groups`, `soon` and
2276 `footer`, so since 4.128.0 the menu never saw:
2277 - `cards`: the sites' cards (Live now, Trending, New worlds);
2278 - `next`: where people go next;
2279 - `discover_ids`: New to you;
2280 - `menu.beacon`: the click beacon.
2281
2282 It now keeps them, and search.js still checks every entry. The navbar budget is 43 KB brotli (42.8 KB).
2283
2284## 4.131.0 — 2026-11-07
2285
2286- **Your online status, on every site** (the owner, 11-07: "we could have online status, especially in DMs … Discord
2287 … people should be able to easily change it off"). New `presence.js`, loaded by the navbar beside itself for a
2288 signed-in person (`presence: false` turns it off):
2289 - **a dot on your picture** in the bar: online, away, do not disturb, invisible, or live — Network's own answer
2290 (`network.presence@1`, the self view), saved per person so every tab paints at once;
2291 - **a heartbeat** (`POST /api/v1/me/presence/heartbeat`) while a tab is visible, once a minute across tabs, and
2292 `idle: true` after five minutes without input so Network shows Away; the first input after that beats at once;
2293 - **Your status** in the account menu: Online (automatic), Do not disturb, Invisible, and a status line of your own
2294 (`PUT /api/v1/me/presence`). A card under the bar on a computer, a sheet from the bottom on a phone. Only people
2295 you follow or message can see it (Network's rule).
2296
2297## 4.130.0 — 2026-11-07
2298
2299- **Your things on every site** (the owner, 11-07: "make sure DMs are accessible across all sites … same with open coins
2300 and vibes … visible everywhere too"). For a signed-in person, every site's bar now shows:
2301 - **Messages**, beside the bell, with OpenVibe.Chat's unread count (`GET /api/dm/unread`, on load and when the tab
2302 comes back, at most once a minute). A click opens your conversations in place, in the Dock's Chats tab, read and
2303 answered there; with the Dock off it is a link to openvibe.chat/messages.
2304 - **The OpenCoins and Vibes balances** (the `wallet` chips of 4.24), folded into one pill on a phone as before.
2305
2306 Both are on unless a site says `wallet: false` / `messages: false`, or brings its own (Live's chips, Chat's
2307 messages keep theirs, with their own counts). A signed-out page shows and asks nothing.
2308
2309## 4.129.0 — 2026-11-07
2310
2311- **One menu** (the owner, 11-07, on the right-hand drawer: "sloppy … maybe combining this menu and the other menu and just
2312 putting our main button on the right-hand side"). The burger now opens the OpenVibe menu, grown from the burger:
2313 - this site's pages lead, all of them (the drawer's job);
2314 - then the live bar, your places and workspace, the network, What's new, tools and Jump back in;
2315 - **Display** (Light/Dark/Auto, text size, Motion/Calm) sits at the foot, drawn by the same controls the drawer had.
2316
2317 The orb by the logo opens the same menu with the places first. The old drawer remains only for a site that turns the
2318 menu off (`search: false`) or asks for it (`drawer: 'legacy'`). The burger's `aria-expanded` follows the menu.
2319
2320## 4.128.0 — 2026-11-07
2321
2322- **The menu, rethought around real use** (the owner, 11-07: Jump back in came first, people opened Sites every time just to
2323 reach Live, and it "doesn't spread traffic … not smartly done based on what's popular"). Network's data that day: Video,
2324 Live and Community take nearly all the page views, 48 of 67 signed-in people used one site, and people move Live ↔
2325 Community, Live ↔ Tools, Chat → Live, Live → Bot and Live → Video. One calm scroll, no tabs, in this order:
2326 - **Who is live**: "3 streams live now · 41 watching" and the live channels when Network names them (`pulse.live`).
2327 - **Your places**: a dock of eight big icons, the places this person uses most (else the network's most used, without
2328 developer services). One tap to Live from any site.
2329 - **On <this site>**: the bar's own links, two to a row, the rest behind "More on <site> (N)".
2330 - **Next from <this site>**: where people really go next (Network's moves; the Frame's `next` when it sends one).
2331 - **New to you**: two places this person never used, picked fairly from the ranked network so every place gets its
2332 turn; developer services only for people who use one. The pick holds for the whole opening.
2333 - **You** (signed in): your agents and Vibe (they open in the Dock), account, wallet, themes, machines.
2334 - **Tools**, **Recent searches**, then **Every OpenVibe site (N)** as one counted fold (this site's group first), and
2335 **Jump back in** last.
2336- `open({ tab: 'sites' })` opens the list of every site and `tab: 'tools'` scrolls to the tools, so old callers keep
2337 working.
2338- **A click beacon, only when Network asks for it** (`frame.menu.beacon`): the section, the position, where it led and
2339 the kind; no person and no page, so the menu can learn what helps and the ranking can spread people across the network.
2340- Touch screens get 44 px chips and header links.
2341- **Sites put their own content in the menu** (the owner, 11-07: "content aware … sites can interact and create their own
2342 content in the bar"), two ways:
2343 - **On their own pages, a card:** a site pushes `{ id, title, render(el, api) }` onto `window.OpenVibeMenuCards`
2344 (a queue that works before `search.js` loads) or calls `OpenVibeSearch.registerCard()`. The card draws itself
2345 under the workspace: Download's drop zone, Live's go-live status, Codes' new task. It is the site's own code on
2346 its own page. A card that throws is left out.
2347 - **On every site, from Network:** the Frame's `cards` (Network collects each site's
2348 `/.well-known/openvibe-menu.json`). The menu draws at most two, as a rail, rows or chips: Live's channels with
2349 their viewers, Games' new worlds, Video's trending. They are text and links only, re-checked here, and never
2350 another site's code.
2351- **Back closes the menu first** (the owner: Back on a phone left the page). Opening adds one history entry; Back pops
2352 it and only closes the menu. Choosing a link leaves cleanly.
2353- **The whole screen on a phone,** opened from the bar's orb as a circle that grows from the orb, under the safe area.
2354 No motion when reduced motion is asked for.
2355- **Your workspace** (signed in), right under your places: Agents and Vibe (open in the Dock), Codes, Files, Machines,
2356 Wallet, Account and Themes, one tap from every site (the owner: "access codes through this menu easily, and talk to
2357 your agent … less having to hop between sites").
2358- **What's new**: the newest real change on each site from the network changelog (library bumps left out), newest first,
2359 marked **New** when it shipped after your last visit there, each linking to that site's `/updates`.
2360- **The full list of sites**: the flagships first (Live, Video, Codes, Design, Space, Games, Chat, Tools), then every group;
2361 each site with its icon, a two-line description (no more cut-off words on a phone) and its own domain.
2362- **Every site has its own icon** (`ov-icons.js`, regenerated from Font Awesome Free 6 by `scripts/build-nav-icons.py`):
2363 Cam a camera, Casino dice, Surf waves, Fun masks, Meme a laughing face, Tours a compass, Vision binoculars, Shoes, Today,
2364 Report, Design, Domains, Exchange and Bench. Until now they borrowed a neighbour's (Cam showed Video's, Casino and Surf
2365 the gamepad, Fun Quest's trophy).
2366- **No spinning rings anywhere** (`ov-icons.js`; the owner, 10-31 and again 11-07 on Surf's and Space's cards: "things only
2367 spin if they're active"). The ring icons' comet spun forever on every icon on every site. Now the comet, the pulse and
2368 the bounce run only while `data-state="busy"`; at rest an icon is its ring and glyph with a small lift on hover.
2369 `data-fx` now says what *busy* looks like. Every site loses the idle spin with this release.
2370## 4.127.0 — 2026-11-07
2371
2372- **The Dock's Agent panel** (design 2026-11-07, one agent behind the Dock): Now says what the agent is doing this moment (the mode, the step in plain words, how long, and Stop/Resume), Needs you lists every approval and question from any mode with its buttons in place, and the timeline below shows one entry per step — the mode, the title, where it ran and why, the outcome, the time, the cost and a link to the step's own page. A mode change is a line of its own.
2373- **Folding and filters:** today's entries are open, earlier days are counted folds ("Yesterday (14)"), and a long run of one mode folds into "12 more Code steps". A chip for each mode and for Needs you sits in the URL of the panel, and a filter never hides without a count.
2374- **Only me by default:** the panel shows the person's own entries only; a step's title, outcome and link, never a prompt or a diff.
2375- **It wears the site's theme:** the panel uses the site's own tokens and font (the navy of a dark site, the paper of a light one), with a 12 px gutter. Approve is the one filled button on a card and Stop is red. A running step's pill breathes; nothing else moves. Needs-you cards are tinted and labelled with their mode.
2376- **The Dock's tabs always fit:** in a narrow Dock the open tab shows its icon and name and the others their icon and count, their names kept for screen readers. "Activity" was cut off at the strip's edge.
2377
2378## 4.126.0 — 2026-11-07
2379
2380- **Audio description in the player (X1)**: a video with a `<track kind="descriptions">` gets an **AD** button beside
2381 Captions. It turns the descriptions track on and is remembered per person in localStorage, like the volume; **D**
2382 toggles it from the keyboard and its label says whether it is on. The active cue is shown over the picture and
2383 spoken through the browser's speech synthesis, so a blind viewer hears what is on screen between the speech. No
2384 descriptions track, no button. `player.js`, `player.css`, `test/player.test.js`.
2385
2386## 4.125.0 — 2026-11-07
2387
2388- **The owner's Setup page and clear settings** (`openvibe-shared/config`; OWNER-SETUP-AND-ADMIN-CLARITY-DESIGN, 2026-11-07). New schema annotations, passed through to the summary: `x-owner-input { group, unlocks, get_it_at, get_it_how, test }` marks a field for Network's Setup tab (the groups are `config.OWNER_INPUT_GROUPS`), `x-required-for` makes an unset field **Needed**, `x-enum-labels` gives every option of a choice words and a line, `x-advanced` folds a field under a counted "Advanced settings (N)", and `x-example` is a placeholder. A secret-class property loses `x-example` and `x-enum-labels` in the summary, as it already lost its default, examples and enum.
2389- **A wrong Setup annotation never refuses the store.** `x-owner-input`, `x-required-for`, `x-enum-labels`, `x-advanced` and `x-example` only change how a field is presented, so a wrong shape is left out of the summary with one warning (and `lintSchema` names it) instead of taking a service down at boot after a pin bump. A group given by its display name (`Voices`) is read as its id (`voices`), and `lintSchema` asks for the id. The D11 annotations (`x-env`, `x-danger`, `x-bind`, `x-unit`) still refuse the store: they decide where a value comes from and how it is guarded.
2390- **`config.lintSchema(schema)`** for each service's own test: it lists a field without a title, a description under 30 characters, or a choice without a label for every option. Empty means the admin panel can explain every field.
2391- **The summary classifies every schema property**, set or not: an unset secret key was missing from `classification`, so a form drew it as a plain field and the service then refused the value (422 `config.secret_write_only`). `classification` still names every key of `values` too.
2392- **A test hook in the admin routes:** `POST <base>/:namespace/test/:key` calls the service's `test` option with the stored value on the server and answers `{ ok, message }` in one line, scrubbed of every secret of the namespace; 409 `config.not_set` with no value, 404 `config.test_not_available` without a tester. `authorize` sees the action `'test'`.
2393
2394## 4.124.0 — 2026-11-07
2395
2396- **The Dock's agent view gets the four controls** (Agent Streams S5, Co-pilot): Pause and Resume, Curtain now, Mute and Unmute, and End, each sent through Codes to Live. The owner's own view may always resume; Live still refuses a moderator resuming the owner's pause. The line says "Paused by @name" when someone else paused the stream.
2397
2398## 4.123.0 — 2026-11-06
2399
2400- **The "not on the list" gate from markup**: `<div data-ov-share-gate data-kind data-id data-signed-in-as data-sign-in-url>` is started by `ov-share.js` when it loads (and by `OVShare.startGates()` for markup added later), once per element. Sites no longer write an inline script for it: an inline script needs a CSP nonce on most sites, and a value written into one with an HTML-escaping template turns its quotes into `&quot;` and breaks the script, so the gate never drew. The values are plain attributes, escaped like any other.
2401
2402## 4.122.0 — 2026-11-06
2403
2404- A thread start page (`page({ center })`) keeps its head at the top on wide screens: only the welcome and the composer are centred, by auto margins. Centring the whole column floated the head into the middle of a tall screen above an empty band (seen on openvibe.exchange/chat at 1280 px). Phones are unchanged.
2405
2406## 4.121.0 — 2026-11-06
2407
2408- **The Dock's agent view works in the window too** (Dock K-D4, on K-D3). The pop-out keeps the Agents tab's stream:
2409 the agent view's own style goes into the document it is mounted in, so a tab opened after the pop-out is styled in
2410 the window (and its style is not left on the page behind it), and a view already open when the Dock pops out carries
2411 its style with the rest of the Dock. Closing the window brings the same stream back to the page.
2412- No new file and no new request: `dock-agent.js` creates its nodes and its style from `container.ownerDocument`.
2413 `test/dock-chrome.test.js` covers the pop-out with the stream.
2414
2415## 4.120.0 — 2026-11-06
2416
2417- **The Dock's agent view** (Dock K-D3; `dock-agent.js`, `dock.js`; Agent Streams S1). The Agents tab now opens on the
2418 person's own desk stream: Live's embed (`/embed/agent/<ags_id>?vt=wvt_…`, the `AI agent` label, the curtain state and
2419 the viewer count on the picture), one line for the task with the time it has worked, and the owner's **Curtain
2420 now** and **End**. Above the list, and it hides itself when there is no stream, so the tab is what it was.
2421 - The view ticket is minted server-side by OpenVibe.Codes with the person's own token (`GET
2422 /api/v1/me/agent-view`) — a third-party iframe has no Live cookie — and the embed renews its own ticket while it
2423 plays, so nothing is re-mounted on a poll.
2424 - Nothing live: the last frame, read with the token and shown as a local object URL (an `<img>` cannot carry a
2425 bearer), with the task and the worked time.
2426 - No step line yet: steps are Agent Streams S2, and the view shows none until Live serves them.
2427 - `dock-agent.js` is a sibling loaded the first time the tab opens (3.9 KB brotli); `dock.js` grows 222 bytes and its
2428 budget goes to 15.50 KB. `navbar.js` passes `dockAgentBase` (default `https://openvibe.codes`).
2429
2430## 4.119.0 — 2026-11-06
2431
2432- A featured link keeps a place on the phone's bar: where the links row folds into the menu, the featured link (4.117.0) also shows as one tinted icon button beside Sign In or the avatar (38 px, 34 px under 400 px, a 44 px hit area on the element), named by its label for screen readers. The Node on the Network is one tap away on a phone. Only a featured link with an icon gets one; no animation.
2433
2434## 4.118.0 — 2026-11-06
2435
2436- Locker reset and unlock errors requiring fresh authentication now link to Network’s password reauthentication with the complete current page as the return destination, including `locker.proof_required`.
2437- Touch footer fold links are 44 px tall on the links themselves; the navbar TLD link is at least 44 px wide.
2438- Regenerate the Locker browser core from openvibe-sdk v0.92.0.
2439
2440## 4.117.0 — 2026-11-06
2441
2442- **A navbar link can be the site's one featured piece** (`navbar.js`; the owner, 2026-11-06: the Node on the
2443 Network is "a central piece", on its own subdomain). A link with `featured: true` draws as a quiet accent pill
2444 with its glyph on the bar — one hairline, one tint, the glyph, and nothing that moves — and leads the phone's
2445 menu. Every other link keeps its place and its plain look.
2446
2447## 4.116.1 — 2026-11-06
2448
2449- **A transfer of a people-locked item carries the recipient's slot.** The sheet wraps the content key for the new
2450 owner's key, saves the envelope with their slot (so they can open it while the offer is pending, before they accept),
2451 and sends the offer with that `key_slot` — which `openvibe-sdk/share` requires for a people lock. Someone without a
2452 locker is told so, and nothing is offered.
2453
2454## 4.116.0 — 2026-11-06
2455
2456- **The Share sheet's Locked part** (`ov-share.js`, `ov-share.css`; Locker design 2026-11-06, the pilot; docs/share.md).
2457 A locked item (`lock: people | password | link` in the share API's answer) gets its own part between Send it and Who
2458 can open it:
2459 - the lock in plain words, and a link lock says when the link in front of it is missing its `#k=` key;
2460 - a people lock shows each person's eight-word fingerprint, warns when their key changed since the slot was made,
2461 and offers **Use their new key**;
2462 - adding someone asks Network for their public key and wraps their content key on this device (the member row and
2463 the slot are saved together); someone without a locker is told so, not added;
2464 - removing someone seals the content again under a fresh key for those who stay, uploads the new envelope with the
2465 members, and says "They had the key before you removed them: they may still have a copy of what they already
2466 opened."
2467 - `ov-locker.js` loads on the first press of the part, never with the sheet.
2468- **`ov-locker.js`** gains the calls the sheet needs, all keeping the keys in the tab: `ensure()`, `keys(who)` (Network's
2469 key directory), `publicKeys()`, `rewrap(bytes, { add, remove, keys })` and `inspect(bytes)` (docs/locker.md).
2470- **`ov-locker-core.js`** regenerated from openvibe-sdk 0.89.0 (`seal.js` and `server.js` moved since 0.86.0).
2471 openvibe-sdk devDependency 0.86.0 → 0.89.0.
2472- test/ov-share-lock-chrome.test.js: the Locked part with the real crypto — lazy load, fingerprints, adding wraps for
2473 them (and the envelope that goes up opens for them), removing re-seals (and the removed person's key opens nothing),
2474 and a link lock keeps its key after `#`.
2475
2476## 4.115.1 — 2026-11-06
2477
2478- **Share sheet:** its headings no longer take a site's own `h2` rule (Wiki's bottom border and margin showed under the sheet's title). Every property a site might set on an `h2` is reset inside the sheet and the access gate.
2479
2480## 4.115.0 — 2026-11-06
2481
2482- **Locker in the browser** (`ov-locker.js`, `ov-locker.css`; plan Q2; docs/locker.md). The owner: "completely
2483 encrypted on our end … decrypted on the client's end, so we don't have access to any of the user data."
2484 - `OVLocker.open(el, { bytes | url })`: a locked item's card. A link lock opens with the key after `#k=`, and a link
2485 missing it says so. A password lock opens with its password, on the device. A people lock asks to unlock your
2486 locker, and someone not on the list is told so.
2487 - `OVLocker.setup()`: make your locker. A passphrase with a strength meter, a 24-word recovery kit shown once and
2488 checked with two of its words, then the eight-word fingerprint. Network keeps the public keys, a self-signature
2489 and two password-locked envelopes of the private keys.
2490 - `OVLocker.unlock()`: unlock with the passphrase or the recovery kit. "I lost both" starts a new locker (reset)
2491 after a plain warning.
2492 - Secrets stay in the tab's memory: never sent, never stored.
2493- **`ov-locker-core.js`**: `openvibe-sdk/locker` 0.86.0 for the browser, generated by `scripts/build-locker-core.js`
2494 from the SDK's own files (WebCrypto only).
2495 - test/ov-locker-core.test.js makes the SDK's vectors again in a browser-only VM, with the same bytes, and crosses
2496 envelopes between the browser and Node.
2497 - test/ov-locker-e2e-chrome.test.js uses the real crypto: make a locker, unlock it on a new device by passphrase
2498 and by recovery words, open what was locked for you, refuse what was not.
2499- openvibe-sdk devDependency 0.82.0 → 0.86.0.
2500
2501## 4.114.0 — 2026-11-06
2502
2503- **One Share sheet on every site** (`ov-share.js`, `ov-share.css`; plan track Q; docs/share.md). Press Share
2504 anywhere and the same sheet opens: a sheet from the bottom on a phone, a dialog on a computer, no tabs.
2505 - **Send it** (everyone): the link with Copy, which turns into a tick; on a phone, "Send with your phone" (the
2506 phone's own share sheet, with its contacts and apps); 16 places (X, Bluesky, WhatsApp, Reddit, Discord, Telegram,
2507 Threads, Mastodon, Facebook, email, Messages, LinkedIn, Pinterest, Tumblr, Hacker News, LINE), recent ones first,
2508 the rest behind "More (n)"; a QR code made on the device; "Start at 1:23"; embed code. Nothing is added to a
2509 shared link.
2510 - **Who can open it** (the owner, through `openvibe-sdk/share`'s router): Everyone, Anyone with the link, Specific
2511 people (by @name, checked as you add them; Can view or Can edit; an end date), Only me; people who asked;
2512 transfer of ownership, bound to the recipient, "Remove my access" by default. Saves send If-Match. A 401, 403 or
2513 404 leaves only Send it.
2514 - `OVShare.offer()` is the recipient's Accept/Decline banner; `OVShare.gate()` is the not-on-the-list page (Ask
2515 for access, or Sign in). Every name and title is text, never markup.
2516 - The navbar loads it on the first press of a `[data-ov-share-sheet]` button, so nothing loads before that. The
2517 kit's `[data-ovk-share]` items open it too.
2518 - Events: `ov-share:sent` `{ target, url }` and `ov-share:change`.
2519 - **A Share component** in OpenVibe Design (`openvibe-shared/components`, `kit.share({ title, url, kind, id })`): a
2520 kit document's `{ type: 'share' }` renders the button, and the spec gives its anatomy, states, keys, and the
2521 Android (Intent chooser with Direct Share) and iOS (`ShareLink`) views, so native apps share the same way.
2522 - **`ov-qr.js`**: a QR code from a link, made on the device with no dependency and no network (a Locker link's key
2523 stays on the device). `OVQR.matrix(text, { ecc })` and `OVQR.svg(text, { ecc, margin, dark, light, title })`;
2524 byte mode, versions 1 to 40, all eight masks. Ported from Nayuki's QR Code generator (MIT); checked module by
2525 module against bwip-js on 1,196 matrices (test/ov-qr.test.js). The sheet loads it on the first QR code.
2526 - **Brand logos**: Tumblr, Hacker News and LINE join brands.js (49 brands, Simple Icons 16.36.0, CC0).
2527 - Tests: test/ov-share-chrome.test.js (headless Chrome).
2528
2529## 4.113.0 — 2026-11-06
2530
2531- **Bot API calls can be counted at ingest, not stored row by row.** `AnalyticsTrackerPg` takes
2532 `countBotApiAtIngest` (off by default): a bot's API call increments `analytics_bot_api_hourly` and
2533 `analytics_bot_api_daily` by endpoint group, status class and bot family at flush instead of writing a raw
2534 `analytics_events` row. The hourly and daily rollups add those counters to raw rows, so a day's `api_calls`,
2535 `bot_hits` and `error_count` are what the raw rows gave before, and a day recomputed at close counts each call
2536 once. The admin's bot breakdown, status classes and sub-day summary include the counters; a raw page view by a
2537 bot still keeps its row, and the option off changes nothing. New tables are expand-only: `analyticsSchema()`
2538 creates them for a new install, and an existing service adds them in a migration.
2539
2540- **Phone minimums in the kit** (the fleet phone sweep of 11-05/06): `.ovk-badge` is 12 px (was 11.2 px), and a room's
2541 own links (`.ov-room > a`) are 44 px tall on touch screens (were 40.6 px).
2542
2543## 4.112.1 — 2026-11-05
2544
2545- **The shared chrome meets the phone minimums** (the fleet phone sweep of 11-05, 322 pages at 390 px). Nothing
2546 changes on a computer.
2547 - **Text of at least 12 px:**
2548 - the footer's column headings (11.52 px on 305 pages of 55 sites);
2549 - its compact meta line (11.84 px);
2550 - the phone app bar's labels (11 px on 146 pages).
2551 - **44 px to the touch, measured** (`pointer: coarse` only):
2552 - the footer's legal links: their hit area was 39 px tall and is now 45;
2553 - the footer's "shipped" pill: 24 to 44 px;
2554 - the shipped list's rows: each row is 44 px and its subject fills it, so the hit boxes no longer overlap the next
2555 row's (4.46.0's padding-in/margin-out made 33.5 px boxes that did);
2556 - "All updates" and the patch-notes line: 18 px to 44;
2557 - the update log's commit hashes.
2558
2559## 4.112.0 — 2026-11-05
2560
2561- **`openvibe-shared/config`: `x-unit: "seconds"` is an owner-setting unit.** An integer property may now be
2562 annotated `seconds` (alongside `nano_usd`, `ms` and `bps`), and `summary()` carries the annotation unchanged so a
2563 settings form can show the input in seconds with the label "s". An unknown unit is still refused when the store is
2564 created, naming the unit.
2565
2566## 4.111.1 — 2026-11-05
2567
2568- **The tab icon is the Pair on every site.** `theme-loader.js` repaints the favicon on each theme apply
2569 (`<link rel="icon" data-ov-icon>`), and it still drew the retired ring mark: every site's inline Pair favicon turned
2570 into the old ring once the page loaded. It now draws `app-icon.js`'s `favicon()` (the heavy form, the site's glow,
2571 Live's red light and Games' yellow one) with the theme's `--bg-primary` as the tile; the theme accent no longer
2572 recolours the mark. The drawing is generated into the loader's `FAVICON` block by `scripts/build-theme-loader.js`
2573 from `mark.js` and `app-icon.js` (`--check` fails when stale), so it cannot drift again.
2574- Tests: `test/favicon-pair.test.js` (each site equals `favicon()`, the lights, the tile follows the theme, the block
2575 is current).
2576
2577## 4.111.0 — 2026-11-04
2578
2579- **`openvibe-shared/vibes-pay` (`vibes-pay.js`): Vibes Pay for any page** (MONEY.md, D12).
2580 - `OpenVibe.vibes.pay(payUrl)` opens the sheet in a 520 px popup on a computer and as the full page in a new tab on
2581 a phone (a blocked popup falls back to navigating the page). It resolves `{ status }` (plus `charge_id` and
2582 `pay_url` when known) from a `postMessage` whose `origin` is `https://openvibe.network` and whose data is
2583 `{ type: 'ov-vibes-pay', status, charge_id? }`; a popup closed without a payment resolves `{ status: 'closed' }`.
2584 The same result is dispatched as a bubbling `ov-vibes-pay` event on the document. Only
2585 `https://openvibe.network/pay/…` URLs are accepted; anything else throws before a window is opened.
2586 - `[data-ov-vibes-pay]` elements bind once: the label is "Pay with Vibes", the Vibes green with dark ink and a
2587 focus ring (light and dark themes), a 44 px target, disabled while the sheet is open, and re-enabled on the
2588 result. `bind(document)` is there for buttons added later; a `MutationObserver` picks them up automatically.
2589 - The server side creates the charge with `openvibe-sdk/vibes`. `docs/vibes-pay.md` is the guide; the button is
2590 not added to openvibe.design's `/components` page from this repository (that page is OpenVibe.Design's source).
2591All notable changes to `openvibe-shared`. Versions follow [semver](https://semver.org/): a
2592breaking change to any exported module, browser global or served file name is a new major.
2593A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
2594
2595## 4.110.0 — 2026-11-04
2596
2597- **`openvibe-shared/config`: sealed secrets, write-only secret routes, env pins and the owner settings'
2598 schema (D11 S2).** Every new option is opt-in: without a KEK the store stores, reads and redacts
2599 exactly as before and reports `sealed: false`.
2600 - **Sealed secrets.** A secret-class value is stored as `v1:<kek_id>:<fingerprint>:<hint>:<bind>:…`:
2601 a random 256-bit data key seals the value with AES-256-GCM, the KEK seals that data key, and both
2602 use the AAD `service|namespace|key|bind`. `values_json` never holds the plaintext again; `get()`
2603 opens the envelope for the service, and a summary, snapshot, history or rollback shows only its
2604 marker. The KEKs come from `opts.keks` (`{ current, keys }`) or `OV_CONFIG_KEKS` /
2605 `OV_CONFIG_KEK_CURRENT`; a value that is not base64 of exactly 32 bytes refuses startup with an
2606 error naming the variable, never the key. Only `node:crypto` is used. The namespace fingerprint
2607 key is plain only when no KEK is configured; with one, `config_keys` holds that same key wrapped
2608 under the current KEK (AES-256-GCM, AAD `openvibe-config-fingerprint|<service>|<namespace>`), so
2609 a database dump has no key that reproduces a fingerprint. It is rewrapped on boot and never
2610 changes, so markers and checksums are stable across a KEK rotation and when a KEK is first
2611 enabled; `reseal()` recomputes each envelope's fingerprint with it.
2612 - **`x-bind`.** A secret property can name another key (`x-bind: "base_url"`); its value joins the
2613 AAD, so changing it locks the secret until it is entered again. A copied envelope fails on another
2614 key or namespace.
2615 - **`reseal()`.** Plaintext secrets and envelopes under an older KEK move to the current KEK,
2616 revision by revision, preserving fingerprints and never resurrecting a changed bind. It reports
2617 `{ resealed, current, plaintext, locked, errors }` and is a 409 `config.no_kek` without a KEK.
2618 - **Write-only routes.** `PUT <base>/:namespace/secrets/:key { value, reason }` and `DELETE …` answer
2619 a marker (`fingerprint`, four-character `hint`, `set_at`, `set_by`, `bound_to`), never the value.
2620 A normal `POST` may no longer write or unset a secret-class key (422 `config.secret_write_only`); a
2621 marker round trip is still allowed.
2622 - **Env pins and the summary.** `x-env: "NAME"` makes the env value effective and read-only
2623 (`summary().pins`, 409 `config.pinned_in_env`). `summary()` now carries `sealed`, `kek`, `pins`,
2624 `secrets` and `schema`; a secret property's `default`, `examples`, `enum` and `const` are removed
2625 from the schema copy. The annotations `title`, `description`, `x-group`, `x-env`, `x-danger`,
2626 `x-bind` and `x-unit` are checked at creation.
2627 - **Hooks.** `onChange({ namespace, keys, actor, revision })` runs after every successful activation
2628 (a throwing hook is logged, never fatal); `requireFresh(req, { namespace, keys })` on `adminRoutes`
2629 runs before secret writes and `x-danger` keys and answers 403 `config.fresh_required` when false.
2630 - `test/config-secrets.test.js` covers the envelopes and their AAD, x-bind and copied envelopes,
2631 write-only routes, pins, the summary schema, the hooks, reseal and rotation, that a dump of
2632 `config_keys` and the snapshots has no key that reproduces a fingerprint, that the fingerprint is
2633 stable across a k1→k2 rotation and a KEK first enabled, that no-KEK markers are unchanged, and
2634 that no value reaches a summary, snapshot, history, error, log or `values_json`; the existing test
2635 still proves the no-KEK call sites and redaction are unchanged. `npm test`: 129/130 files passed,
2636 1 skipped (floating-promises, acorn not installed).
2637
2638## 4.109.2 — 2026-11-03
2639
2640- **A select inside a modal dialog or a fullscreen player opens over it.** The list (menu or phone sheet) used to be
2641 added to the body. A modal `<dialog>` such as the kit's sheets made it inert, and a fullscreen element hid it.
2642 - It now goes inside the dialog or the fullscreen element the select is in.
2643 - Where the browser supports popovers, it opens in the top layer, above any z-index.
2644 - The browser's own popover box is undone.
2645 - `test/ov-forms-chrome.test.js` covers a select in a modal dialog: the list is seen, a choice lands, the dialog
2646 stays open.
2647
2648## 4.109.1 — 2026-11-03
2649
2650- **`data-ov-select="bare"`: a select inside a chip or a toolbar keeps that look.** It gets no box, chevron or badge of
2651 its own, and only its list (the menu, or the phone sheet) is OpenVibe's. Codes' composer chips use it.
2652 `test/ov-forms-chrome.test.js`.
2653
2654## 4.109.0 — 2026-11-03
2655
2656- **OpenVibe's own select and form controls, on every site (`ov-forms.js`, `ov-forms.css`, docs/forms.md).** The owner
2657 asked for "a custom system for those select inputs … shared across the network and all websites rather than using
2658 default stuff", one that works on phones and computers alike.
2659 - **The select.** Every `<select>` becomes a button that opens OpenVibe's list: a menu on a computer, a sheet from
2660 the bottom on a phone.
2661 - The hidden select keeps the value, so forms, `select.value` and `change` listeners work as before.
2662 - An option can carry a description, a toned badge, a state dot and a "Set up" link (`data-desc`, `data-badge`,
2663 `data-state`, `data-href`).
2664 - The list has search from 9 choices, groups, multiple choice and the full keyboard.
2665 - **The other controls.** Checkboxes, radios and sliders take the site's accent, and `role="switch"` draws a switch.
2666 `.ov-field`, `.ov-input` and `.ov-choice` give the full field look to a page that asks for it.
2667 - **Loading.** The navbar loads both files once the page settles, only on a page with a form control.
2668 - Turn it off with `<html data-ov-forms="off">`, `navbar.init({ forms: false })`, or `data-ov-native` on a control.
2669 - `test/ov-forms-chrome.test.js` covers it.
2670
2671## 4.108.1 — 2026-11-03
2672
2673- **A page named after a domain keeps its Markdown twin in llms.txt.** 4.108.0 counted any last segment with a dot as a
2674 file, so Help's `/sites/openvibe.blog` and Host's `/catalog/domains/co.uk` kept their HTML URLs.
2675 `/sites/openvibe.community` got its twin only because ".community" is longer than eight letters. Now only a real
2676 file ending (json, xml, txt, png, html, …) makes a file. `test/llms-markdown-links.test.js`.
2677
2678## 4.108.0 — 2026-11-03
2679
2680- **llms.txt can link a site's own pages at their Markdown twins, and only those** (AGT-5). `seo.llmsTxt({ markdown:
2681 true })` used to append `.md` to every link, so `/openapi.json` became `/openapi.json.md`, and so did a feed or an
2682 API path. Now only the site's own pages get a twin: `/hot` → `/hot.md`, `/` → `/index.md`. Files, `/api/` and
2683 `/.well-known/` paths, other schemes and, given the new `base` (the site's origin), other sites' links keep their
2684 URL. A page with no twin still takes `html: true`. `optional` writes the llmstxt.org `## Optional` section.
2685 `test/llms-markdown-links.test.js`.
2686- **The fire name effect reads at WCAG AA on a red-tinted row** (`items.css` #ff7a1f, from #282: it was 4.42:1 on Chat).
2687
2688## 4.107.4 — 2026-11-03
2689
2690- **The profile panel and the user card give a lookup eight seconds, and a failed one is not kept.** A request held
2691 while Network restarted in a deploy round stayed in the panel's map for the page's life, so that person's panel never
2692 opened again until a reload (11-03, found with Live's name card). Now each lookup has an eight-second limit and a
2693 failure is forgotten, so the next click asks again. `test/card-fetch-limits.test.js`.
2694
2695## 4.107.3 — 2026-11-03
2696
2697- **On a phone, a site's own room picture sits below the hero, not behind its text.** Under 760px the room drew its
2698 picture behind the hero's text at 0.28 opacity. For the default motif (the orbits) that is a backdrop. A site's own
2699 picture is usually a sample card of words and numbers, and on 11-03 it ran through the headline on Report ("OpenVibe,
2700 honest" over "2 h 14 m"), Domains, Surf, Shoes and Tours. Now a picture that is not the default motif sits below the
2701 hero, whole and at full strength; the default motif stays a faint backdrop. `test/room-pic-phone-chrome.test.js`
2702 checks both at 360, 390 and 700px.
2703
2704## 4.107.2 — 2026-11-03
2705
2706- **The last small touch targets in the shared bar and footer reach 44px.** A re-run of the tap audit over every
2707 site's home (11-03: 31 of 45 sites with a small target, down from 40 of 42 on 10-29) found these:
2708 - Sign In was 40px tall; its invisible hit area now reaches 45px;
2709 - the OpenVibe menu's orb was 36.9px wide on a phone; it grows to the right, toward the open bar, so it measures
2710 44px while the site's name keeps the taps on its own last letters;
2711 - the footer's brand was 33px tall, now 45px;
2712 - the orbit's big nodes on a phone were 42px, now 44px.
2713 Nothing is drawn differently. `test/tap-areas-chrome.test.js` measures each to a finger with
2714 `elementFromPoint`, from 320 to 430px, signed in and out, and checks that the name and the right-hand group keep
2715 their own taps.
2716
2717## 4.107.1 — 2026-11-03
2718
2719- **Legal pages draw the brand in its own font on every site.** `legal.page()` loaded the theme loader, the navbar and
2720 the footer from openvibe.network, so the navbar asked openvibe.network for its brand font too, and sites whose CSP
2721 allows fonts only from themselves (Connect, Cam, Surf, Tips, Zone) refused it: their `/terms`, `/privacy` and
2722 `/dmca` showed the name in the system font. Every site serves openvibe-shared at `/shared/`, and the page now loads
2723 the three scripts from there.
2724- **The public and service pages carry the day they were written.** They said "Last updated September 18, 2026", the
2725 template's date, though their words are from 2026-11-03. A profile written later carries its own date
2726 (`PROFILE_UPDATED`); the older profiles keep theirs.
2727
2728## 4.107.0 — 2026-11-03
2729
2730- **Two legal profiles for sites the old ones did not fit.** Ten sites served no `/terms`, `/privacy` or `/dmca`
2731 because no profile told the truth about them: `info` says the site is not open yet, and `tools` promises uploads
2732 deleted within the hour. `public` is an open, read-only site with no account (search, reports, domain prices).
2733 `service` is a personal tool or API that keeps what you make with your account. A service site says in its own
2734 words what it does (`what`) and keeps (`makes`), whether it fetches addresses people give it (`fetches`) and whether
2735 it sends what they give it to AI models (`ai`), and names any extra processors (`processors`). `legal.PROFILES` lists
2736 every profile. `test/legal-profiles.test.js` covers them.
2737
2738## 4.106.3 — 2026-11-02
2739
2740- **A site on its own open.<tld> domain is named for it.** open.shoes read "OpenVibe.Shoes" in the bar and its name
2741 linked to openvibe.shoes (the owner's screenshot). The navbar now reads `<sub>.open.<tld>` hosts as Open.<Tld>, and a
2742 config can say `brand: { core: 'Open' }` itself off the domain (localhost, previews). The brand carries its front door
2743 as `apex` (open.shoes, openre.stream, openvibe.<tld>), and the name's links go there. The footer's fallback and the
2744 installed app's name (`icons/sites.json`, from the manifest's first domain) say Open.Shoes too.
2745
2746## 4.106.2 — 2026-11-02
2747
2748- **The site's name reads whole on a signed-in phone.** On openvibe.live a phone showed "Li…" beside the balances, the
2749 messages, the bell and the picture (the owner's screenshot). Up to 480px the two balances now stack in one pill, each
2750 with its icon, so both still show: the pill is about 53px instead of 90-103px and the name has that room. At 320px the
2751 OpenVibe menu's orb no longer runs into the balances. `test/navbar-phone-fit-chrome.test.js` now signs in with the
2752 messages button and the owner's figures (161k, 400).
2753
2754## 4.106.1 — 2026-11-02
2755
2756- **The OpenVibe mark opens the site switcher again, on every site.** `site-switcher.js` (4.48.0) was never in
2757 `files.js`'s `BROWSER` list, and sites serve `/shared/*` from that list only: the mark loaded it on the first tap, the
2758 site answered 404 and the switcher never opened. It is on the list now. `test/browser-files-complete.test.js` fails
2759 when a browser file loads a file of this package that no list serves.
2760
2761## 4.106.0 — 2026-11-02
2762
2763- **The ghost cursor** (`motion.js` `[data-ov-ghost]`, MOTION-1): a scene shows how it is used. An arrow walks to each
2764 `[data-ov-ghost-step="N"]` inside it in order, "clicks" (a ring that widens and fades, never a spinner), marks that
2765 step `data-ov-ghost-on` so the page can draw it chosen, and starts over after the last. It runs only while the scene
2766 is in view, never under reduced motion (the OS setting or `data-ov-motion="reduced"`), and is gone for good the
2767 moment the reader touches, clicks, types or focuses inside the scene. The cursor is aria-hidden and takes no focus or
2768 pointer events. `test/motion-ghost-chrome.test.js` checks it in Chrome.
2769
2770## 4.105.1 — 2026-11-02
2771
2772- **OpenVibe Connect draws the plug in the launcher** (`ov-icons.js` ALIASES `connect: 'plug'`), not the fallback mark.
2773 Connect went live with Contracts 0.279.0 (Website Connect v0). `test/site-icons.test.js` checks that every live
2774 site's launcher icon is a real glyph.
2775
2776## 4.105.0 — 2026-11-02
2777
2778- **Eight more solid drawings**, the next by use with a shape to fill: umbrella, parking, medical, drive, skull,
2779 hot-tub, group and gavel (136 of 277 now). The parking sign and the first-aid case keep their letter and cross as
2780 cut-outs; the drive keeps its case outline as a line over a filled base with two lights; the gavel's head is one
2781 block apart from its striking bar, so the gap still shows at 16 px. Drawn beside the line drawings, checked at 96,
2782 24 and 16 px on a dark ground and by Design's `validate.cleanSolid`. The glyph files and fonts are rebuilt.
2783- **Taglines end without a full stop, every one** (contracts 0.276.0): `icons/sites.json` (the site switcher) and the
2784 share cards of AI, Bench, Events, Inventory, Pics, Play, Run, Search and Zone are rebuilt from the manifests; Pics'
2785 domain card line in `social.js` follows. Ten of 55 had one.
2786- **The release browser check reads the words** (`browser-harness.js`, check `text`): the visible text has no
2787 "[object Object]", bare undefined/NaN, `{{ }}`, markup or an entity shown as text, or a space before a full stop.
2788 It warns and never fails (a developer page may show such words in code). Each was on a live site on 11-02.
2789- **A title that names its site gets no " · Site" suffix** (`seo.headTags` `titleSuffix`): every update log read
2790 "What shipped on OpenVibe.Work · OpenVibe.Work".
2791
2792## 4.104.0 — 2026-11-02
2793
2794- **Eight more solid drawings**, the next by use with a shape to fill: download, upload, expand, shirt, shapes,
2795 shelter, archive and keyboard (128 of 277 now). An arrow is filled whole and its tray stays a line; expand is
2796 compress's twin, four corners pushing out. Drawn beside the line drawings and checked at 48, 24 and 16 px on light
2797 and dark grounds. The glyph files and fonts are rebuilt.
2798
2799## 4.103.0 — 2026-11-02
2800
2801- **Eight more solid drawings**, the next by use on the sites (Design's usage count): retry, water, food, history,
2802 compress, bridge, text and steps (120 of 277 glyphs now have one). An arrow keeps its arc as a line with a filled
2803 head; a list keeps its rows as lines beside filled boxes. Each was drawn beside its line drawing and checked at 48,
2804 24 and 16 px on light and dark grounds. `ov-glyphs.js`, `ov-glyphs.svg` and the glyph fonts are rebuilt.
2805
2806## 4.102.0 — 2026-11-02
2807
2808- **A sidebar row with two lines** (`kit.sidebar` items): `sub` is a quiet second line under the name, a
2809 conversation's last message or where a search matched, with the mark beside both lines. `attrs` gives a row `data-*`
2810 attributes so a page's script finds it again (Chat's support inbox, `data-conv`); any other name is dropped. A
2811 one-line row's markup is unchanged.
2812- **Boost lets each parsed page go.** After a swap, boost empties the page it parsed. Chrome held that page's own
2813 `<video>` (the swap shows a copy) as a pending activity, and with it the whole parsed page: one document and every
2814 node more on each visit. On openvibe.cam, `/` ↔ `/app` grew by 3 documents and 1,729 nodes in three laps and failed
2815 the release check's navigation test. `boost-chrome.test.js` visits a page with a video five times in place and checks
2816 the document count stays put.
2817
2818## 4.101.0 — 2026-11-02
2819
2820- **A puzzle glyph** (`puzzle`, with a solid drawing): a jigsaw piece, knobs out on top and right, notches in, for a
2821 browser extension, an add-on or a plug-in. `fa-puzzle-piece` drew nothing on Coupons and Design since Font Awesome
2822 left. A sweep of every `fa-` name the sites use found no other gap except brands (drawn by brands.js) and words in
2823 Design's docs.
2824
2825## 4.100.2 — 2026-11-02
2826
2827- **The page prefetch runs under a nonce policy.** The navbar's inline speculation rules (4.70.0) were refused on
2828 every page whose Content-Security-Policy names a nonce: openvibe.chat logged it on each page. Chrome 156 ignores
2829 `'inline-speculation-rules'` beside a nonce. The navbar now gives its rules the page's own nonce, read from any of
2830 its nonced scripts. Checked on production's openvibe.chat/vibe with this navbar.js swapped in: no violation, and
2831 the rules carry the nonce.
2832
2833## 4.100.1 — 2026-11-02
2834
2835- **The browser harness counts a counted fold's text as on the page.** A closed `<details>` opens with one tap, with
2836 or without JavaScript, and search engines read it; the network's rule is to fold, never to hide. The release check's
2837 JSON-LD rule (each entity's name in the page's text; 80% of a list's items) failed Network's home: its "50 sites"
2838 list names the sites under "Every other neighbourhood". The contents of closed `<details>` (all but the summary) are
2839 now part of the text the checks read.
2840
2841## 4.100.0 — 2026-11-02
2842
2843From a phone sweep of every site's home (11-02):
2844- **A card's day as its cover.** `kit.card({ date })`: a thing without a picture that happens on a day (a drop, an
2845 event, a deadline) shows the weekday and month with the day large, instead of the same calendar icon on every card.
2846 The day stays clear of the corner's price. Shoes' "Dropping this week" was a row of identical calendar icons.
2847- **The aura's mark leaves the heading alone on a phone.** On a phone the aura hero (`sc-has-vis` without an orbit) is a
2848 watermark behind the heading, and its mark landed at the end of a long heading: AI's read "one API.V", Download's
2849 and Watch's the same. The glow stays; the mark goes there (the navbar shows it). Computers keep it in its column.
2850
2851## 4.99.1 — 2026-11-02
2852
2853- **The conversation's head follows the navbar on a phone.** The navbar slides away while you read down. The thread's
2854 sticky head stayed where the bar had been, leaving a gap with the thread showing through. ov-thread.js now finds the
2855 bar on each measure (navbar.js may draw it after the kit starts) and measures again once the slide ends.
2856- docs/thread.md: `OVThread.tool` and `toolDone` take the tool call's `key`.
2857
2858## 4.99.0 — 2026-11-02
2859
2860- **The conversation UI shows several agents, approvals and what the work produced** (plan L55 C4 stage 3, for Codes):
2861 - `turn({ who })` heads a reply with an agent's face (initials in its colour) and name;
2862 - `divider()` draws a hand-over between agents;
2863 - `card({ form, code, icon })` asks and posts without script (an approval: Allow or Deny);
2864 - `panel()` sits beside the conversation: a column from 1180 px, a drawer from the right below;
2865 - `page({ panel, main })` places the panel, or a plain page, in the conversation's place;
2866 - `sidebar({ pinned })` keeps chats above the groups, and an item takes a `dot` and a quiet `meta` word;
2867 - `sidebar({ findSubmit })`: Enter in Find a chat runs the site's own search (every message). Without it the form is
2868 filter-only, as before; the kit keeps Enter on the page only for a filter-only form, so a site that draws
2869 `kit.sidebar` itself (Codes) searches again on Enter;
2870 - `you.caption` puts a small word over the person's bubble;
2871 - a work row takes an `icon` (`read`, `find`, `edit`, `run`, `web`, `sub`), and a finished tool with no output is a
2872 plain row rather than one that opens on "Running…";
2873 - `composer({ id, attrs, foot })`: the form's id and the site's data attributes, and markup under the bar. The note
2874 line is always drawn and is the form's status line (`role="status"`);
2875 - `page({ center })` is a start page: the welcome and the composer sit together until there are turns. `cls` adds
2876 the site's classes;
2877 - `OVThread.tool(id, { name, input, icon, key })` and `OVThread.toolDone(id, ok, key)` stream a tool row built from
2878 text; a result finds its own row by key (tools run side by side), and `ok: null` is a step that ended with no result.
2879- 4.96.0's own sidebar code is gone from thread.css and ov-thread.js. `thread.sidebar` has drawn `kit.sidebar` since 4.97.0.
2880## 4.98.0 — 2026-11-02
2881
2882- **Solid drawings for ten more glyphs (plan L49/L50).** The places and things the sites use most that had none
2883 (Design's usage count): `tree`, `tent`, `train`, `mountain`, `city`, `id`, `wallet`, `chip`, `restroom`,
2884 `brain`. A swapped icon at 18 px or less now shows the filled shape instead of heavier lines, and
2885 openvibe.design/icons.css gives them an `is-solid` class. ov-glyphs.js and ov-glyphs.svg rebuilt.
2886
2887## 4.97.0 — 2026-11-02
2888
2889- **The conversation UI uses the network's one sidebar.** 4.96.0's `thread.sidebar` drew a second sidebar beside
2890 `kit.sidebar`, which is the network's one (plan L35, the owner 10-31: "the navigation makes more sense in a
2891 vertical way… shared styling across everything") and which Codes already uses. Now:
2892 - `thread.sidebar` returns `kit.sidebar`: New chat is its action, the chats are its sections (the open one marked, a
2893 working one with a dot), and the site's places can be its `nav` rows.
2894 - Find a chat filters as you type (ov-thread.js); without script it is the kit's GET form.
2895 - `thread.page` lays the kit sidebar out as its first column from 761 px, with the kit's scrim.
2896 - `thread.head` carries `kit.sidebarButton`, so kit-web.js opens the drawer on a phone.
2897 - Draw with kit.css and kit-web.js beside thread.css and ov-thread.js.
2898
2899## 4.96.0 — 2026-11-02
2900
2901- **One conversation UI for the network** (plan L55 C4). Actor's chat proved the shape; Vibe, Codes and the Dock use
2902 the same parts:
2903 - `openvibe-shared/thread` builds the markup: the chats in a sidebar (grouped Today / Yesterday / This week /
2904 Earlier), the sticky head, turns, the work folded under an answer, the answer with its sources and a footer, cards,
2905 and the composer. Complete without script, every string escaped.
2906 - `/shared/thread.css` draws it with theme tokens: a page that scrolls as a whole, a composer at the bottom above a
2907 phone's app bar, and the chats as a column or a drawer.
2908 - `/shared/ov-thread.js` adds the drawer, Find a chat, the growing composer, Enter to send, Copy, staying at the
2909 bottom, and `OVThread.add / replace / live / work` for streaming turns. A turn that finishes types its answer in
2910 word by word (`OVThread.reveal`, from Actor #227); code, tables and pictures come in whole.
2911 - The composer takes the site's markup above the box (`top`: picked files, a problem line) and a `maxlength`; Copy
2912 leaves out anything marked `data-ovt-nocopy` (a citation's number).
2913 - Nothing spins: a working turn breathes, and reduced motion stills it.
2914 - `docs/thread.md`; `test/thread.test.js`.
2915
2916## 4.95.0 — 2026-11-02
2917
2918- **A box that scrolls can be scrolled from a keyboard** (WCAG 2.1.1). A code block, a wide table or a list held to a
2919 height, with nothing focusable inside it, could not be reached with Tab, so its hidden part was out of reach without
2920 a mouse. axe (scrollable-region-focusable) flagged such pages on six sites in the 11-02 sweep. Once the page settles,
2921 and again a little later and after a resize, the navbar gives each such box `tabindex="0"` (and
2922 `data-ov-scroll-focus`, with a focus ring). A box with a link or a button inside, a page's own tabindex, and
2923 `overflow: hidden` are left alone. `OpenVibeNavbar.focusScrollers()` runs it on demand for a page that draws one
2924 later. `test/keyboard-scrollers.test.js` checks it in a real Chrome.
2925
2926## 4.94.0 — 2026-11-02
2927
2928- **A labelled thing's poster may be named by username** (`data-ov-labels-owner="@name"`). The owner rule (their own
2929 content always shows to them) matched only a subject (`usr_…`), and a server-rendered page such as openvibe.video's
2930 cards knows the maker's username, not their subject. `ov-labels.js` now also matches `@username` against the
2931 signed-in person's username, in any case. A bare name is never an owner, so a username that looks like someone's
2932 subject matches nothing.
2933- **On a thing's own page, hide covers it instead of emptying the page** (`data-ov-labels-opened`). A watch page or a
2934 picture's page is somewhere the person went on purpose; with hide chosen for one of its labels, the player vanished
2935 and left a title over nothing. Marked opened, it is covered with the calm "Show" button that names the labels, as
2936 blur does. Cards and lists still hide.
2937- `navbar.js`'s `SIBLING_REV` moves with `ov-labels.js`.
2938
2939## 4.93.2 — 2026-11-02
2940
2941- **A folded footer column says how many links it holds** (from #208, 10-31). On a phone the full footer's columns are
2942 folds (4.53.0), and a fold showed only its heading. Each now carries its count on its line ("9 links", "1 link"), the way
2943 the network's other folds say what they hold. On a desktop, where every column is open, the count is hidden. Every link stays in the
2944 page.
2945
2946## 4.93.1 — 2026-11-02
2947
2948- **An `ov:` icon the ring set lacks draws as its glyph** (`showcase.icon`). Before, it drew the bare OV mark: Today's
2949 "Your agenda" (`ov:calendar`) showed a "V". When the ring icons do not know a name and the glyph set does, the glyph
2950 is drawn in a ring of its own (the accent, a soft fill and a thin ring), the same size as the ring icons.
2951
2952## 4.93.0 — 2026-11-02
2953
2954- **Content labels, each viewer's own choice** (plan L1, `common.content-labels@1`, `network.label-prefs@1`).
2955 - **`ov-labels.js`**: a page marks what carries labels with `data-ov-labels="nudity,18+"`, and optionally the media
2956 to cover (`data-ov-labels-media`) and the poster (`data-ov-labels-owner`). The navbar loads the file the first time
2957 such an element appears, as it does `ov-glyphs.js`.
2958 - It reads the person's choices once from Network (`GET /api/v1/me/label-prefs`, with the navbar's token, cached
2959 five minutes) and applies them. **show**: as it is. **blur**: the media is covered by a calm button naming the
2960 labels with "Show" (a playing video pauses), until tapped. **hide**: not shown.
2961 - Signed out, everything shows (the web default). The poster always sees their own. Things that arrive later (a
2962 feed's next page) are covered too.
2963 - **`kit.labels(labels)`**: the chips beside a title, in words ("Strong language", "Flashing lights"), 18+ in the
2964 warning colour. Only the labels in force: a suggestion is the creator's to confirm and is never shown to viewers.
2965 **`kit.labelsAttr(labels)`** writes the matching `data-ov-labels` attribute.
2966 - **`OpenVibeNavbar.session`**: who is signed in and the Network token, read only, for files loaded beside the
2967 navbar.
2968
2969## 4.92.0 — 2026-11-02
2970
2971- **The page starts the footer it renders** (`shell.page`, `shell.scripts`).
2972 - Six sites rendered the shared footer and never started it: Video, Pics, Run, Events, AI and Website. On a phone
2973 every fold stood open, and there was no shipped line.
2974 - The page's boot now starts it a moment after the site's own scripts, with the page's footer config.
2975 - A site that starts it itself (`OpenVibeFooter.init`) keeps its own: a started footer carries `data-ovf-live`, and
2976 the shell leaves it.
2977- **The compact footer centres its links** under its centred brand. On a phone the row sat at the left, and "Report a
2978 problem" wrapped alone.
2979
2980## 4.91.1 — 2026-11-01
2981
2982- **A numbers strip can be reached by keyboard** (`kit.stats`): on a phone the strip scrolls sideways, and a strip with
2983 no link in it had no way in (axe `scrollable-region-focusable`, a WCAG AA sweep of every home). It carries
2984 `tabindex="0"` beside its label.
2985
2986## 4.91.0 — 2026-11-01
2987
2988- **Buttons say they are working, then done or failed, with the mark** (`ov-mark.js`, which the navbar loads on every
2989 site; the plan's "buttons' busy, then done or failed, from the mark's states").
2990 - `OVMark.busy(button)` → `{ done(text?), fail(text?), reset() }`: the button shows the mark busy beside its label,
2991 keeps its width and takes no second press; done turns the V into the green tick and says "Done" (or the text),
2992 fail into the red "!" and a word; then the label comes back.
2993 - A form posted the old way (POST) does this by itself until the next page arrives, and a second submit does nothing.
2994 Its submitter is never disabled, so its name and value still post. A GET form, a form whose own script took the
2995 submit, and `data-ov-busy="off"` are left alone; the back/forward cache resets it.
2996 - `navbar.js` loads the new `ov-mark.js` by its content hash (`SIBLING_REV`).
2997
2998## 4.90.0 — 2026-11-01
2999
3000- **An audio-only recording shows the streamer, not a black box** (`player.js`, `player.css`; plan R4).
3001 - A browser broadcast with no camera is recorded as sound only. When the metadata says there is no picture track, the
3002 player shows a cover: the face from `data-ov-avatar` (else the poster) in a circle over a soft blur of it, with an
3003 "Audio only" tag. With no usable picture, a speaker glyph. The sound plays under it with the usual controls.
3004 - The player keeps a 16:9 shape (a sound file has no picture size, and the browser drew it 150 px tall), and hides
3005 picture-in-picture. A new source decides again.
3006 - Pages: put the streamer's avatar on the video as `data-ov-avatar="…"`.
3007
3008## 4.89.0 — 2026-11-01
3009
3010- **A card with no picture but a person shows their face** (`kit.card`, `room.css`).
3011 - A card with no `image` (and no `preview`) but an `avatar` draws the avatar large in a circle over a soft blur of it.
3012 A lone type icon in an empty box read as a missing image: Report's episodes and Tours' recordings of streams with no
3013 public recording.
3014 - The avatar is fetched at 160 px (`mediaW`), is `aria-hidden` (the body's mark and the title already name the
3015 person), and a script or bad URL falls back to the icon as before.
3016
3017## 4.88.1 — 2026-11-01
3018
3019- **An aborted view transition is silent on the account hub.**
3020 - Signed out, `/my` and `/themes` send the visitor to `/login`, a page that does not load the kit and so does not opt
3021 in to cross-document view transitions. Chrome aborted the transition and left an uncaught `InvalidStateError` on
3022 the sign-in page: "Transition was aborted because of invalid state. ViewTransition opt-in disabled".
3023 - The account switcher now handles `finished`, `ready` and `updateCallbackDone` in `pageswap`/`pagereveal`, and skips
3024 a `replace` navigation's transition before it starts: a redirect is not a page move, and a transition that cannot
3025 land has nothing to animate.
3026
3027## 4.88.0 — 2026-11-01
3028
3029- **Name effects read on light themes.**
3030 - The fifteen name effects in items.css were all drawn on dark pages: bright colour plus glow. On a light theme some
3031 names were unreadable: Neon and Glitch at 1.3:1, Ice at 1.6:1, and Divine was mostly white (axe flagged Community's
3032 pastes and Pulse).
3033 - Under `html[data-theme-mode="light"]`, each effect now has a variant with the same hue and motion, at a depth that
3034 reads on white (most stops about 4.6:1 or more).
3035 - Fire, Ice and Neon animate their light colours with their own keyframes. Shadow's mist and Void's glow are lighter.
3036 - Reduced motion still stops them all.
3037
3038## 4.87.0 — 2026-11-01
3039
3040- **Share cards for a service's other domains.**
3041 - openvibe.pics and openvibe.download (MediaHub), openvibe.homes (Rent) and openvibe.website (Space) previewed as
3042 their service: a link to openvibe.pics showed the OpenVibe.Video card.
3043 - `social.DOMAINS` names them (id, service, host, name, tagline, what); `social.domainOf(id)` and
3044 `social.domainForHost(host)` look them up.
3045 - The builders draw `social/<id>.jpg` like the service's card (the same mark, Pics in its pink) and write the
3046 domain's `icons/sites.json` entry.
3047 - `shell.page()` shares the domain's card on any page whose canonical URL is on that host, so the sites need no
3048 change.
3049- **The footer's "Nobody live right now" line reaches 4.5:1.** `.ovf-pulse-quiet` was the theme's muted text on the
3050 pulse's tint: 4.34:1 on Daylight, on every page with a footer (axe flagged 42 inner pages). It is now the
3051 secondary text.
3052- **Room kit pictures load at the size they are drawn.** `kit.mark` and `kit.card` put a Media object's original
3053 upload in the page: Report and Community drew a 3072 px, 3 MB avatar at 34 px, and Community a 2460 px picture in a
3054 280 px card. They now ask Media's resize ladder for the width (`?w=96` or `160` for marks, `640` for card
3055 pictures); `kit.mediaW(url, width)` does the same for a site's own images.
3056
3057## 4.86.0 — 2026-11-01
3058
3059- **Status colour names every site can use.**
3060 - About eighteen sites colour error and status words with `--error-text`, `--error`, `--ok` or `--warn`, and accent
3061 words with `--accent-text`. No theme sets those names, so each site's fallback literal showed: `#ef4444`,
3062 `#34d399` and `#fbbf24` are pale on light themes.
3063 - theme-loader.js now maps them onto the theme's own readable colours (`--danger-text`, `--danger`,
3064 `--success-text`, `--warning-text`, `--accent-strong`). The mapping sits in `:where(html)`, so a site that sets
3065 one of these names itself keeps its own value.
3066- **Room kit marks reach 4.5:1 for every hue.** `kit.mark`'s white letters sat on `hsl(hue 45% 38%)`, about
3067 3.5:1 for greens and yellows (axe on Community, Report, Tours, Shoes and Bot). Lightness is now 32%: the worst
3068 hue is 4.75:1. `test/kit-mark-contrast.test.js` checks all 360.
3069- **A room's text colour reaches 4.5:1 on its own chips.** `--room-strong` (room.css) was the bare room accent:
3070 3.9-4.4:1 on the room's tinted chips (Bench's benchmark ids, Exchange's listing labels). It is now the accent with a
3071 quarter of the theme's text mixed in, 5.2:1 or more there, the hue unchanged.
3072- **LIVE badges are white on a deeper red.** `kit.css`'s `--ovk-live` and showcase's `.sc-live` filled with the
3073 theme's `--live-red` under white text: 3.8:1 on `#ef4444`. The fill is that red at 80% over black (5.5:1); every
3074 built-in theme's red reaches 4.5:1 except Catppuccin's pastel pink.
3075
3076## 4.85.0 — 2026-11-01
3077
3078- **Share cards for fourteen more sites.**
3079 - Bench, Cam, Casino, Design, Domains, Exchange, Fun, Meme, Report, Surf, Today, Tours, Vision and Shoes had no
3080 `social/<service>.jpg`. A shared link to their homes showed no picture, because shell.page's og:image is
3081 `social.url(service)`.
3082 - Drawn by `scripts/build-social-cards.js --only …` from each manifest's `site` block (OpenVibe.Contracts main), the
3083 same way as every other card.
3084 - `icons/sites.json` names their app icons (`scripts/build-icons.js`): installable apps and the app-icons test
3085 need an entry for every site with a card.
3086 - The update log (`shipped.js`) knows the fourteen as sites, so each one's own feed is shown. A Services subdomain
3087 (`ai.`, `bench.openvibe.services`) and `open.shoes` resolve to their own site, the way Network's subdomains do.
3088
3089## 4.84.0 — 2026-11-01
3090
3091- **A page with no stylesheet of its own reads in the network's type.**
3092 - `shell.page` set no font. On five sites (Events, Run, AI, Bot, Games), `/updates` loads no stylesheet, and its text
3093 fell back to the browser's Times New Roman.
3094 - The head's reserve style now gives the body `var(--ov-font, 'Inter', system sans)` through `:where(body)`. At zero
3095 specificity, any site's own body rule wins, so nothing changes on a page that already sets its font.
3096
3097## 4.83.0 — 2026-11-01
3098
3099- **Status words read on light themes.**
3100 - The kit wrote a status in the theme's fill colours (`--success`, `--warning`, `--danger`). On Daylight, the badges
3101 "paid" and "expiring" on openvibe.design/kit were green and amber on white at 2.28:1 and 2.15:1.
3102 - `--ovk-ok-text`, `--ovk-warn-text` and `--ovk-bad-text` follow the theme's readable `--success-text`,
3103 `--warning-text` and `--danger-text`. They are now 5.4:1 on Daylight, and unchanged on the dark default.
3104 - The kit uses them for words: badges, the danger button, done and danger icon buttons, the danger fold, the menu's
3105 danger item, notice icons, and kit-data's rank deltas and pills.
3106 - Fills and borders keep the fill colours. `room.css` carries the same tokens.
3107
3108## 4.82.0 — 2026-11-01
3109
3110- **The motion tokens are on every page, and Reduced motion reaches every token-timed transition** (plan L45).
3111 - Before this release, `--ov-dur-1..4` and `--ov-ease-out`/`-spring`/`-in-out` were set only by `ov-fx.js`, which
3112 loads on pages with effects. Everywhere else a site's `var(--ov-dur-2, .2s)` got its fallback, so the person's
3113 Reduced setting could not reach it.
3114 - `kit.css` now sets the same tokens, with the same values, and the same feels: calm looks are slower and never
3115 overshoot, loud ones are quicker.
3116 - `html[data-ov-motion=reduced]` sets every duration to 0, and so does a device that asks for reduced motion
3117 (`:root:root`, above `ov-fx.js`'s own `:root`).
3118 - A site that writes its transitions with the tokens now follows the look and the person's choice. The design lint
3119 (openvibe.report/design) counts hand-set timings.
3120 - `room.css` carries the same tokens.
3121
3122## 4.81.0 — 2026-11-01
3123
3124- **Shape and elevation are tokens, and a look has a shape** (plan L45 step 2: "Paper is quiet and square, Neon glows
3125 and bounces").
3126 - `--ov-radius-1..3` (small things, cards, sheets) and `--ov-elev-1..3` (on its surface, a bar over scrolled content,
3127 a menu above the page) are in `kit.css`. Their defaults are the kit's own corners and shadows, so nothing moves.
3128 - Every kit corner and shadow now reads them: `--ovk-radius` is `--ov-radius-2`, and a 12 px corner is
3129 `radius-1 + 2px`. Only pills and circles stay literal.
3130 - The quiet looks are square, with hairlines where shadows were: Paper, Solarized Light, Nord, Nord Light, Matrix,
3131 Hacker and High Contrast.
3132 - Neon Tokyo and Vapor are round, and their raised things glow in the accent.
3133 - A site's own CSS can use the same tokens to follow the look: `border-radius: var(--ov-radius-2, 14px)`.
3134 - `room.css` carries the same tokens, and the personalities at zero specificity.
3135
3136## 4.80.0 — 2026-11-01
3137
3138- **33 more glyphs have a solid drawing for small sizes** (plan L50; the owner, 2026-11-01: "really tiny icons… a
3139 solid colour rather than the border lines"). The next most-used shapes on the sites after 4.73.0's sweep: globe,
3140 circle, plane, mail, eye-off, key, gauge, server, user-check, mic-off, palette, dish, layers, phone, money, care,
3141 news, hand, tack, help, invite, ticket, cart, box, store, map, sun, leaf, headphones, tv, bug, terminal and database.
3142 Drawn the same way as the others: filled shapes, holes cut with `fill-rule="evenodd"`, a part that has to stay a
3143 line as `class="ln"`. An "off" glyph (eye-off, mic-off) keeps its slash as a line and cuts the shape back from it,
3144 so the slash still reads at 14 px. 101 of 276 glyphs now have one; a glyph made only of lines (check, code, wifi,
3145 share) still thickens when small instead. `ov-glyphs.js` carries them (76.3 KB raw); the sprite gains
3146 `ovg-<name>-solid` for each.
3147
3148## 4.79.0 — 2026-11-01
3149
3150- **What Vibe did, as chips you can undo** (plan L48 V2). Under a Vibe answer that set or stopped a reminder or a
3151 timer (OpenVibe.Chat's `results` on the message), the Dock's Vibe tab shows a chip: "Reminder set: Stretch · Every
3152 2 hours", "Timer: 25 min focus". **Undo** for a day (Chat's `POST /api/vibe/undo`), **Open** for a reminder; an undone
3153 one reads "stopped" and is not offered again.
3154- **A timer Vibe starts shows at once.** When Vibe's answer started a timer ("Vibe, a 25-minute focus timer"), the Dock
3155 reads its state again, so the pill counts down without waiting for the next poll. The Vibe tab's mount takes
3156 `refresh`.
3157
3158## 4.78.0 — 2026-11-01
3159
3160- **`kit-layout.css`: the fluid scale, the layout primitives and a classless base** (plan L54 K1/K2). A file of its
3161 own, after `kit.css` (2.5 KB brotli); a page that uses none of it loads neither.
3162 - The fluid scale: `--step--2` … `--step-5` for type and `--space-3xs` … `--space-3xl` for spacing, by `clamp()`
3163 between 320 and 1240 px (16 → 18 px base, ratio 1.2 → 1.25). No breakpoints.
3164 - Eight primitives that lay out by their own width: `.ov-stack`, `.ov-cluster`, `.ov-sidebar` (`--end`),
3165 `.ov-switcher` (all side by side or all stacked, never two and one), `.ov-grid`, `.ov-rail`, `.ov-cover`,
3166 `.ov-frame`. Set by `--gap`, `--min`, `--side`, `--item`, `--ratio`.
3167 - `.ov-base`: plain HTML (headings, text, links, lists, quotes, code, tables, details, forms) looks finished on the
3168 site's theme. Every selector is inside `:where()`, so a site's own rule always wins.
3169 - Documented in docs/kit.md; `test/kit-layout-chrome.test.js` measures each in Chrome at 320, 1280 and 1920 px.
3170
3171## 4.77.1 — 2026-11-01
3172
3173- **Tabs below the fold no longer scroll the page to themselves.** kit-web.js kept the open tab in view with
3174 `scrollIntoView({ block: 'nearest' })`, which also scrolled the page when a tab strip sat below the fold (a settings
3175 section lower down, openvibe.design/kit's samples). Only the strip moves now, sideways.
3176
3177## 4.77.0 — 2026-11-01
3178
3179- **Talk to Vibe in any browser** (plan L48 V2). Where the browser has no speech recognition of its own (Firefox),
3180 the Dock's mic records instead: hold it (or tap, then tap again), up to 30 s, and OpenVibe.Chat's
3181 `POST /api/vibe/listen` turns the recording into text on the network's own speech-to-text (OpenVibe.AI, nothing
3182 kept). What was said is asked like typed text. The microphone is released as soon as the recording ends.
3183 - The Dock's Chat call (`chatCall`) can send a body that is not JSON: `{ body, type }`.
3184- **The glyphs load when a page first shows an icon** (plan L54 K4). The navbar loaded `ov-glyphs.js` only if a page
3185 had a Font Awesome-style icon when it mounted, so sites loaded it themselves for icons their own scripts add later.
3186 Now the navbar watches for the first one (a mutation schedules one look 250 ms later; the watch ends once the
3187 glyphs load), so a page without icons pays nothing and the sites' own `<script src="/shared/ov-glyphs.js">` can go.
3188
3189## 4.76.0 — 2026-11-01
3190
3191- **A timer in the Dock, on every site** (plan L48 R2; the owner, 2026-11-01: "a reminder system… timer or however
3192 often they want").
3193 - The Dock's header has a timer button with 5, 10 and 25 minutes, focus rounds (25/5 ×4, 50/5 ×2), or your own
3194 minutes and what it is for.
3195 - The timer lives in the Network's Dock state (`timer`, openvibe-contracts 0.271.0, Network #482), so it counts
3196 down on every OpenVibe site the person has open and follows them to the next.
3197 - **The pill** shows the minutes left (an agent that needs you still comes first). **The open Dock** shows a strip
3198 with the time, what it is for, a line that fills, +5 min and Stop.
3199 - **At its end:** a soft chime on a page that is showing, a notification when the person allowed them (asked once,
3200 when they start one), and "Time's up" in the pill. A focus round moves on to its break and its next round by
3201 itself; every page computes that from the same end time, so two pages writing it agree.
3202 - `dock-timer.js`, a new served file (3.7 KB brotli), loads only while a timer runs or its menu opens. `dock.js`
3203 grows by 0.45 KB (budget noted).
3204- **The Dock's header fits four tabs and four buttons.** The tabs are a sideways rail that never runs under the
3205 buttons, and a narrow Dock tightens both.
3206
3207## 4.75.0 — 2026-11-01
3208
3209- **The Dock keeps the Vibe tab across sites.** The Network's Dock state knows `vibe` since openvibe-contracts 0.270.0
3210 (Network #481), so the open tab follows the person from site to site like the others.
3211
3212## 4.74.0 — 2026-11-01
3213
3214- **Vibe in the Dock, on every site** (plan L48 V2; the owner, 2026-11-01: "access your agent or the AI… from across
3215 all websites… use a voice feature to tell it to set a reminder… have a conversation with your Vibe AI").
3216 - The Dock's first tab is Vibe: the same conversation as openvibe.chat/vibe (OpenVibe.Chat's `/api/vibe/chat` and
3217 `/api/vibe/ask`, with the person's own token), so a chat started on Live carries on on Chat and back.
3218 - Starters show before the first message, and Vibe's answer arrives in place. The tab polls quickly for a minute
3219 and a half after a message, then every 15 s while it is open. An https link is a link, and "Open in Chat" goes
3220 to the same conversation.
3221 - The mic is the browser's own speech recognition. Hold to talk, click to start and stop, or press Alt+V from any
3222 page, which opens the Dock on Vibe. It listens only while the person asks: no wake word, never always on.
3223 - `dock-vibe.js`, a new served file, holds the conversation and the mic (4.1 KB brotli). It loads the first time
3224 the tab opens. `dock.js` grows by the tab, its loader and Alt+V (0.55 KB; the budget note says so).
3225 - The Network's Dock state keeps only the tabs it knows. The Vibe tab is remembered on the site until the Network
3226 learns it.
3227
3228## 4.73.0 — 2026-11-01
3229
3230- **Solid drawings for the most-used shapes** (plan L49 step 3; the owner, 2026-11-01: "really tiny icons… a solid
3231 colour rather than the border lines"). Twenty more glyphs get the drawing `ov-glyphs.js` shows at 18 px and below,
3232 picked by how often the sites draw them: copy, scissors, volume, mute, robot, film, clipboard, computer, sliders,
3233 pencil, save, plug, chart, gear, search, game, idea, wrench, target and nodes (68 in all).
3234 - Glyphs that are only lines (check, close, plus, the arrows and chevrons, link, code, ban, stop) keep no solid one:
3235 their strokes thicken when small, which reads better than a filled shape would.
3236 - The same 24-unit grid as the line drawings, so an icon does not jump when it crosses the size.
3237
3238## 4.72.0 — 2026-11-01
3239
3240- **Brand logos without Font Awesome** (plan L54 K4; the owner, 2026-11-01: "get rid of Font Awesome completely").
3241 `brands.js`, a new module and served file, carries Simple Icons' drawings (CC0-1.0, v16.36.0): 46 brands for 59
3242 Font Awesome `fa-brands` names. That covers every brand an OpenVibe site names, except Microsoft, Windows, LinkedIn
3243 and AWS, which Simple Icons no longer carries; those stay with Font Awesome.
3244 - `ov-glyphs.js` draws `<i class="fa-brands fa-youtube">` as the brand. Only the small name map is inline; the
3245 drawings load from beside it the first time a page shows a brand.
3246 - Brands are filled in the text's colour. `ovg-brand-color` gives the brand's own colour (`--ovg-brand`), except
3247 where it is near-black and would vanish on a dark page.
3248 - `scripts/build-brands.js` rebuilds `brands.js` from a Simple Icons package.
3249
3250## 4.71.0 — 2026-11-01
3251
3252- **One hand in the bar** (the owner, 2026-11-01: "the icon set doesn't feel consistent… it seems weird that there's
3253 both solid and border-line icons… the navbar needs improvement"). Every icon the navbar draws is the OpenVibe set's own
3254 (`openvibe-shared/glyphs`): 101 of the 102 names in `nav-icons.js` are `[0, line, solid]` on the set's 24-unit grid,
3255 and Font Awesome's filled path stays only for the one name the set does not have yet. `scripts/build-navbar-icons.js`
3256 converts the entries itself, and `--check` fails while a covered name still has Font Awesome's path. The bar is line,
3257 in one stroke (1.9; 2.3 in the small balance chips). The bell, the messages bubble, the coins and the wallet are the
3258 same set's drawings. The app bar's tab you are on wears the solid one.
3259- **The name sized to the mark:** 19 px on wide screens, 17 px on phones and 16.5 px on the narrowest.
3260- **Icons: Auto, Line or Solid, per person** (plan L56; the owner: "a setting to prefer a certain icon type… we
3261 override it where we must… configurable for users in most cases"). A display setting, `icons`, beside motion, text
3262 size, contrast, the readable font, colour-blind-safe colours and OLED black. It is saved to the account, travels with
3263 links like the rest of the look, and is painted before first paint as `<html data-ov-icons>`. The Look panel has the
3264 choice, with a sample bell in each. In the glyphs' CSS the switch point is a variable per area (`--ovg-solid-below`,
3265 18.4 px by default), and the person's preference (`--ovg-pref`) wins over it. `.ovg-solid` and `.ovg-line` still
3266 force a style where a site must. The navbar and the bell follow the preference too.
3267
3268## 4.70.0 — 2026-11-01
3269
3270- **Line or solid, on purpose** (plan L49; the owner, 2026-11-01: "both sets for any size… swap between them…
3271 whichever fits the area"). `.ovg-solid` and `.ovg-line` force one at any size. An active control (the current tab,
3272 a pressed or selected button, `aria-current`, `aria-pressed`, `aria-selected`, `.is-here`) wears the solid drawing,
3273 as an app's tab bar fills the tab you are on. Kept low in specificity (`:where`) so a site can override it.
3274- **The app bar feels like an app's** (plan L52; "improve the mobile button so it feels seamless and smooth"):
3275 - it is drawn once and kept between pages;
3276 - one pill slides to the tab you are on;
3277 - a tap lights its tab at once, before the page arrives;
3278 - a press gives, and the new tab's icon pops.
3279- **Placeholders shaped like the content** ("placeholder elements while it loads, a fake thumbnail, so it is not
3280 jarring"):
3281 - `kit.skeleton({ kind, count })` with seven shapes: card (16:9), row, tile, chip, line, chat and stat;
3282 - kit.css shimmer, never a spinner, still under reduced motion and Calm;
3283 - announced as busy.
3284- **Moving between pages** ("the logo changing while it loads… smarter loading"):
3285 - `OpenVibeNavbar.busy(on)`: the mark works while something loads;
3286 - a plain same-site link starts the mark at once, and the next page's mark takes over (a download gives up after
3287 10 s);
3288 - speculation rules prefetch same-site pages on hover or press (`moderate`), never /auth, /api, sign-out or a
3289 download (`speculate: false` turns it off);
3290 - cross-document view transitions keep the navbar and app bar still while a server-rendered page cross-fades (off
3291 under reduced motion).
3292
3293## 4.69.0 — 2026-11-01
3294
3295- **Icons that read at every size** (plan L49; the owner, 2026-11-01: "really tiny icons… a solid colour rather than
3296 the border lines… so all our icons look nice across different sizes and devices"). 48 glyphs most often shown small
3297 have a solid drawing (`glyphs.SOLID`, `glyphs.solid(name)`, `glyphs.inner(name)`): filled shapes with holes cut
3298 through. ov-glyphs.js draws both, and pure CSS chooses: at 18 px and under the solid one shows, and every other
3299 glyph draws its lines heavier (`max(0px, (18.4px - 1.15em) * k)`: inside the SVG a px is a grid unit and an em is
3300 the icon's own size). The sprite carries `ovg-<name>-solid` symbols.
3301- **An open book** for fa-book-open (the closed one read as an empty square at 14 px).
3302- **Scroll areas** (kit.css): `.ov-scroll` hands the swipe on to the page at its ends, `.ov-scroll-fill` holds it
3303 (sheets, modals, full-screen chats), `.ov-scroll-x` scrolls sideways only. On a phone the kit's sidebar sections
3304 no longer trap the page.
3305
3306## 4.68.0 — 2026-11-01
3307
3308- **83 more glyphs, 446 Font Awesome names** (plan L49 step 1; the owner, 2026-11-01: "why Font Awesome? We should be
3309 using OpenVibe Design's icon system for all our icons"). Every non-brand name Live uses now has a glyph: trend up and
3310 down, sort, share, move, at, box, bug, megaphone, burst, the camera, chat, cloud, link, phone and user "off"
3311 variants, record, plus and minus circles, clapper, captions, commit, compass, cookie, couch, cubes, ear, eraser,
3312 laugh, fish, finish flag, flask, folder tree, skip, gem, ghost, grip, hammer, handshake, wizard hat, broken heart,
3313 hourglass, lungs, mask, mug, paw, walk, piggy bank, airplane, print, quote, radio, ranking, recycle, RSS, scroll,
3314 signature, square, sword, columns, thumbs up, undo, user circle, lock, minus, pen and tag, weight, wind, window,
3315 align left, and slash, plus 40 names mapped onto glyphs that already stand for the same thing. Brand logos stay
3316 Font Awesome Brands. ov-glyphs.js, the glyph font (22.3 KB woff2), its Font Awesome stylesheet and the sprite are
3317 rebuilt; existing code points are unchanged.
3318
3319## 4.113.0 — 2026-11-06
3320
3321- **Bot API calls can be counted at ingest, not stored row by row.** `AnalyticsTrackerPg` takes
3322 `countBotApiAtIngest` (off by default): a bot's API call increments `analytics_bot_api_hourly` and
3323 `analytics_bot_api_daily` by endpoint group, status class and bot family at flush instead of writing a raw
3324 `analytics_events` row. The hourly and daily rollups add those counters to raw rows, so a day's `api_calls`,
3325 `bot_hits` and `error_count` are what the raw rows gave before, and a day recomputed at close counts each call
3326 once. The admin's bot breakdown, status classes and sub-day summary include the counters; a raw page view by a
3327 bot still keeps its row, and the option off changes nothing. New tables are expand-only: `analyticsSchema()`
3328 creates them for a new install, and an existing service adds them in a migration.
3329
3330## 4.67.0 — 2026-10-31
3331
3332- **Share this look** (plan L45 step 4). The Look panel has a Share button beside "saved to your account". It copies
3333 a link to this page with `?ov-try=<theme>` (the phone's share sheet on Android and iOS). Anyone who opens it sees the
3334 page in that theme with theme-loader's Use it / Stop bar, and nothing is saved until they choose. Built-in themes
3335 only: custom colours never ride a link. `OVLook.shareLink(id)`.
3336- **The theme's save is dated**, as the display settings are since 4.64.0. The PUT carries `at`, and Network (since
3337 #458) keeps the newest copy.
3338- **A feel for each look** (plan L45 step 1). ov-fx.js's motion tokens follow the theme:
3339 - the quiet paper and light themes move calmly: a little slower, and the spring settles without overshoot;
3340 - the neon and terminal ones snap: quicker, with a livelier spring;
3341 - the rest stay lively;
3342 - Calm still sets every length to zero. `OVFx.FEEL` lists the two groups.
3343
3344## 4.66.0 — 2026-10-31
3345
3346- **`ov-fx.js`, the network's effects in one kit** (plan L45; the owner: "reusable modular sharable systems and
3347 interface"). The mark's motion is now available for anything on any OpenVibe page:
3348 - **API:** `data-fx="glint glow lift press ripple tilt reveal count confetti shimmer"`, and
3349 `OVFx.confetti({ x, y, count, colors })` from script;
3350 - **reveal:** children rise in as they come on screen (`data-fx-stagger`). Anything already on screen when it is
3351 wired is never hidden;
3352 - **count:** a number counts up the first time it is seen and ends exactly on its own text;
3353 - **tilt:** mouse only;
3354 - **confetti:** one canvas, about 1.6 s, then gone;
3355 - **motion tokens:** `--ov-dur-1…4`, `--ov-ease-out`, `--ov-ease-spring` and `--ov-ease-in-out`. Calm sets the
3356 durations to 0;
3357 - **rules:** nothing runs by itself. Under reduced motion or Calm, nothing is hidden, nothing counts and no confetti
3358 falls;
3359 - **loading:** navbar.js loads it from beside itself once the page settles, and only on a page with `data-fx`
3360 (about 2.6 KB brotli). Also exported as `openvibe-shared/ov-fx`.
3361- **The static mark has the new depth** (plan L44):
3362 - `openvibe-shared/mark` takes `depth`, on by default for the brand tone. Each stroke runs from a lighter to a deeper
3363 tone of its colour, the same mixes `ov-mark.js` uses (`tones(o, v)`), in user space so a straight "!" still paints.
3364 One-colour tones stay flat;
3365 - `app-icon.js` draws with it, so the installable icons (`icons/`) and the share cards (`social/`) are rebuilt with
3366 the deeper mark.
3367
3368## 4.65.0 — 2026-10-31
3369
3370- **The mark, with depth and motion** (plan L44; the owner: "the stylistic choices of the logo image need more
3371 improvement and cooler animations and effects"). `ov-mark.js`:
3372 - **depth:** each stroke runs from a lighter to a deeper tone of its own colour (gradients in user space over
3373 `--ovm-o`/`--ovm-v` with color-mix), so states, seasons, tones and a page's own colour still decide it. Tones stay
3374 flat; browsers without color-mix show flat strokes;
3375 - **intro:** the O rises and draws itself round, the V drops in on a spring, then a glint of light crosses the mark,
3376 masked by its own shape (about 1.7 s; `OVMark.intro` replays it for 2 s);
3377 - **hover and focus:** the V leans and the O hops as before, plus the glint and a soft glow of the two colours behind
3378 the mark. `data-glow` keeps the glow on, for heroes and brand pages;
3379 - **press:** the mark gives to 90% and springs back;
3380 - still: nothing spins, it moves only for its intro, on hover and for states, and keeps still under reduced motion
3381 and Calm (the glow appears without moving).
3382- `ov-mark.js` is about 4 KB larger (raw).
3383
3384## 4.64.0 — 2026-10-31
3385
3386- **One look, everywhere, with no flash** (plan L42; the owner: seasonal off on Network was still on, and orange, on
3387 Live; "make it reliable and consistent… instant load… change the theme quickly from that user drop-down… works for
3388 non logged in users"). Each OpenVibe domain keeps its own copy of the look; now:
3389 - every copy is dated (`ov_display.at`, `ov_theme.at`), and the newest wins between this browser and the account.
3390 An account copy without a date still wins, unless a change made here in the last minute may not have reached it;
3391 when this browser's copy is newer, the account catches up with that date;
3392 - a click on a link to another OpenVibe site carries the look (`?ov_look=`: what is painted, the display settings
3393 and the theme, each dated). The next site adopts it when newer, before its first paint, and takes it out of the
3394 address. It is read only after an OpenVibe page, so a link someone sends cannot set your look, and only colours
3395 pass (no `url()`, no `expression()`). Guests get it too: nothing needs an account;
3396 - theme-loader.js keeps what it painted in `ov_paint`, for pages whose loader comes late.
3397- **`openvibe-shared/look-boot`**: the inline script for the top of `<head>` on a page whose theme-loader.js is deferred
3398 (Live). It paints `ov_paint`, or a look a link just carried, before the first frame: colours, `data-theme` and the
3399 `data-ov-*` display attributes, text size, background and `color-scheme`. A season whose dates are over is left for
3400 the loader. `tag({ nonce })` gives the `<script>`; 1.6 KB.
3401- **The Look panel** (`ov-look.js`, loaded the first time it opens, 2.5 KB brotli): the palette in the account menu,
3402 and Look in a guest's menu, open it in place instead of sending people to openvibe.network/themes. Eleven themes and
3403 the season's own, the season switch, where the look is kept (your account, or this browser), and "More looks on
3404 openvibe.design", the home of every theme.
3405- **Calm keeps the O.** Reduced motion hid `.ov-mark .o` along with the old comet; the Pair's ring shares that class,
3406 so Calm took the O out of the mark. Only the icon's comet hides now.
3407- theme-loader.js's size budget is 62,000 characters (15.45 KB brotli, was 15.41).
3408
3409## 4.63.0 — 2026-10-31
3410
3411- **37 more glyphs, for the Font Awesome names the network still used most** (plan L32 I4; openvibe.design/icons'
3412 "Still Font Awesome" list, counted 10-31):
3413 - restroom, shelter, hot-tub, umbrella, parking, skull, group, medical, shapes, headphones, drive, check-double, tv,
3414 toolbox, table, scale, road, money, heart-pulse, heading, fist, fast-forward, rewind, file-code, dice, contrast,
3415 briefcase, timeline, seedling, rocket, list-ordered, lifebuoy, infinity, inbox, door, card and storm, drawn in the
3416 mark's hand;
3417 - 75 more Font Awesome names map to glyphs (aliases include `info-circle`, `people-arrows`, `right-left`,
3418 `microphone-lines`, `folder-open` and `money-bill-*`).
3419 - 192 glyphs for 311 Font Awesome names. The fonts gain the new glyphs at U+E09C–U+E0C0; no code point moved.
3420- **The glyphs' own motion** (L32 I9):
3421 - `ovg-draw` draws a glyph's lines in once (`--ovg-draw-duration`, `--ovg-draw-delay`), the way the mark's tick does;
3422 - `ovg-once` plays any animation a single time (`fa-shake ovg-once`: a bell that rings once);
3423 - under reduced motion, a drawn-in glyph is simply there.
3424- `ov-glyphs.js` is 11.1 KB brotli and still loads after first paint.
3425
3426## 4.62.0 — 2026-10-31
3427
3428- **Font Awesome's features, kept, on the glyphs** (plan L32 I6; the owner: "basically want to keep Font Awesome, so all
3429 their features"). `ov-glyphs.js` now carries its own rules for every documented Font Awesome class, with Font
3430 Awesome's CSS variables, so a site can drop Font Awesome's stylesheet and keep its markup:
3431 - sizes `fa-2xs` … `fa-2xl` and `fa-1x` … `fa-10x`, `fa-fw`, `fa-border`, `fa-pull-left` / `-right`;
3432 - `fa-rotate-90/180/270`, `fa-rotate-by` (`--fa-rotate-angle`), `fa-flip-horizontal/vertical/both`;
3433 - `fa-ul` / `fa-li`, `fa-stack` (`-1x`, `-2x`, `fa-inverse`), `fa-layers` with `fa-layers-text` and
3434 `fa-layers-counter`;
3435 - the animations `fa-beat`, `fa-bounce`, `fa-fade`, `fa-beat-fade`, `fa-flip` and `fa-shake`. `fa-spin` and
3436 `fa-spin-pulse` breathe instead, since nothing on the network spins (L27). Reduced motion stops all of them;
3437 - `fa-thin` and `fa-light` as thinner strokes of the same drawing (`--ovg-stroke`, `ovg-bold`), and
3438 `--fa-primary-color` on duotone icons;
3439 - `data-fa-transform` (grow, shrink, up, down, left, right, rotate, flip-h, flip-v), in Font Awesome's sixteenths;
3440 - a `title` names the icon to screen readers (`role="img"`).
3441 - A page that resets an icon's class list but keeps its name keeps the glyph. The drop-in is 9.1 KB brotli and loads
3442 after first paint.
3443- **The glyphs as fonts** (plan L32 I5; the owner: "exporting like web fonts, regular font files"):
3444 - `ov-glyphs.woff2` (12 KB, served at `/shared/ov-glyphs.woff2`), plus `glyph-fonts/ov-glyphs.ttf` and
3445 `glyph-fonts/ov-glyphs.otf` for desktop apps and design tools. Each glyph's strokes are expanded to outlines
3446 (picosvg, skia-pathops) by `scripts/build-glyph-fonts.py`, so the font draws what the SVG does.
3447 - `glyph-codepoints.json`: each glyph's Private Use Area code point from U+E001, given once and never reused.
3448 - `ov-glyphs-font.css`: `<i class="ovi ovi-eye">`. `ov-glyphs-fa.css`: Font Awesome's class names on the font, for a
3449 page whose every name is covered.
3450 - `test/glyph-fonts.test.js` fails when the fonts no longer match the registry. Builds are reproducible
3451 (`SOURCE_DATE_EPOCH`).
3452- **The sprite:** `ov-glyphs.svg`, every glyph as a `<symbol id="ovg-<name>">`, served at `/shared/ov-glyphs.svg`
3453 (`files.IMAGES`; `serve.js` answers `image/svg+xml`).
3454
3455## 4.61.0 — 2026-10-31
3456
3457- **The network's own icons: `openvibe-shared/glyphs` and `ov-glyphs.js`** (plan L32; the owner: "our own custom
3458 icon system… replace Font Awesome… through openvibe.design").
3459 - **One registry, in the mark's hand** (24-unit grid, 2-unit round strokes): the kit's 84 icons plus 71 drawn for
3460 the names the network asked Font Awesome for most. 155 glyphs in all, and 236 Font Awesome names mapped. That
3461 covers 179 of the 188 names used eight or more times; the rest are brand logos (which stay Font Awesome's) and
3462 a few rare amenity icons.
3463 - **`ov-glyphs.js`** (built by `scripts/build-glyphs.js`; 7.4 KB brotli) swaps `<i class="fa-… fa-x">` for the
3464 glyph in place, in the text's colour and size.
3465 - It follows class changes (play ↔ pause) and markup added later.
3466 - It leaves `fa-brands` and names it does not know to Font Awesome.
3467 - It turns Font Awesome's own glyph and spin off on what it swaps. A spinner becomes three dots that take turns.
3468 - **The navbar loads it** after first paint on any page that uses Font Awesome (a site opts out with
3469 `glyphs: false`). `kit.PATHS` exposes the kit's drawings to the registry.
3470- **The files beside the navbar load by content hash** (`SIBLING_REV`, `scripts/sibling-revs.js`): ov-mark.js,
3471 ov-glyphs.js and the brand face get a new URL each release. After 4.59.0 an edge served the old mark for minutes.
3472- navbar.js is smaller than in 4.60.0 (its longest comments tightened), so no home page grows: 40.7 KB brotli.
3473
3474## 4.60.0 — 2026-10-31
3475
3476- **One left sidebar for every product: `kit.sidebar`** (plan L35; the owner: "the navigation makes more sense in a
3477 vertical way… part of the openvibe.design shared styling across everything").
3478 - **The order, top to bottom:**
3479 - the main action (New chat, Go live, New post) and search;
3480 - the navigation as vertical rows: an icon and a name, the page you are on marked, a count or a dot;
3481 - the sections (Pinned, Today…), which scroll on their own;
3482 - a foot.
3483 - **On a computer:** a column beside the page. **On a phone:** a drawer over everything, which `kit.sidebarButton`
3484 opens and the scrim, its close button, Escape and a page move close. The handlers are on the document in
3485 kit-web.js, so a page that boost swapped in needs no wiring (Codes' rail lost its handler that way, Codes #209).
3486 - **New builders and icon:** `kit.withSidebar` puts the sidebar and the page side by side; a new kit icon,
3487 `sidebar`.
3488 - **In the component contract** (openvibe.design/components): `sidebar`, with a Compose `ModalNavigationDrawer` and
3489 a SwiftUI `NavigationSplitView` reference view, and a kit-document form (the navigation).
3490
3491## 4.59.0 — 2026-10-31
3492
3493- **The OpenVibe mark is the Pair** (the owner, from OpenVibe Mark Systems: "Pair, halo … I like this icon the most.
3494 This should be our new logo"): a ring O in blue and a round V in violet, with a gap of the ground around the V
3495 where it crosses the ring. The gap is a mask, so the mark sits on any ground. The V is as tall as the O and the mark
3496 is centred in its box, so it sits level beside a name in the navbar and centred in a tile.
3497 - **`openvibe-shared/mark`** (new): the mark as static SVG for servers, icons, share cards and anyone else:
3498 `svg({ form, tone, wide, size, title })`, `art()` to place it inside another SVG, `geo()`, `shapes()`, `COLORS`.
3499 - **`ov-mark.js`** draws the same numbers (`test/mark.test.js`). Two forms, the pair and the heavy (22 px wide and
3500 under; the old `data-form="solid"` draws the heavy). `data-wide` gives a box that hugs the mark; `data-tone="ink"`
3501 or `"white"` gives one colour.
3502 - **Every state remade for it, none of them a spin:** the intro pops the O and grows the V out of its point; hover
3503 leans the V back; busy bobs the V while the O breathes; ok turns the V into a tick, green; error into "!", red,
3504 with a shake; no into "×", grey; notify pops a dot by the V; live makes the O the record light with ripples; sleep
3505 closes the O like an eye. The V's shapes morph, and the gap in the ring follows them.
3506 - **The page-load progress is the ring:** following a link, the navbar's ring empties and draws itself round again
3507 at this site's learned pace (the same pace as 4.58.0), the V nodding; on arrival the ring closes and the V hops.
3508 `data-progress` (island.js) draws the ring to its level. `OVMark.intro(el)` replays the intro on one mark (a
3509 brand page, a splash).
3510 - **Variants:** Live, VIP, Tips, Deals and Coupons carry a light in the O (Live's red record light). **The season:**
3511 the O is a pumpkin with its stem and leaf in October, with caps of snow in December.
3512 - **The navbar** shows the mark in the wide box (`data-size="34" data-wide`), the brand button hugging it; the footer
3513 at 38 (28 small).
3514- **The brand face for the names: Nunito** (SIL OFL 1.1, `OFL-Nunito.txt`), cut to Latin and weights 700–900:
3515 `ov-brand.woff2`, 13.5 KB. A new `files.FONTS` list serves it at `/shared/ov-brand.woff2` (`serve.js`: `font/woff2`,
3516 CORS as for every shared file); it is not a browser script, so the size report and the bundle test do not count it.
3517 `navbar.js` declares it from beside itself (`font-display: swap`, its characters only) and sets the site name in
3518 it; the footer's brand and the site switcher's names use it; every one falls back to the system face. A site with
3519 its own `/shared` route serves `files.FONTS` beside `files.BROWSER` (`test/brand-face.test.js`).
3520- **App icons, favicons and share cards** are the Pair: `app-icon.js` draws it (the heavy on the favicon's tile; the
3521 maskable icon keeps it inside the 40 % safe circle), and `icons/*.png` and `social/*.jpg` are rebuilt; the cards set
3522 the names in the brand face. **The loading screen** (`loading.js`) shows the Pair at work, and its V becomes "!"
3523 when loading fails.
3524
3525## 4.58.0 — 2026-10-31
3526
3527- **The OpenVibe mark is the Loop** (the owner chose it from OpenVibe Mark Systems: "use the one that's easiest to see
3528 across all these resolutions and add lots of animations to it"). `ov-mark.js` draws today's open loop, refined: a
3529 heavier stroke, the V dipping to the middle, the dot balanced in the opening, centred in its box.
3530 - **Two forms of one shape:** the line (28 px and up: the navbar, headers) and the solid coin with the V cut through
3531 it (16–24 px, where a line would blur). The form follows `data-size`, or set `data-form="solid|line"`.
3532 - **Its fill is the progress.** Following a link, the navbar's mark fills as the next page loads, paced by how long
3533 this site's boost moves and full loads usually take in this browser (a running average per site in localStorage).
3534 It reaches about four fifths at the usual time, never the top before the page is here, then fills and drains back
3535 to the line. Single-page apps call `OVMark.navStart()` / `OVMark.navEnd()`. island.js's `data-progress` fills
3536 the coin to its level instead of drawing a ring around the mark.
3537 - **States, none of them a spin:**
3538 - busy: the dot hops in the V;
3539 - ok: the coin fills green;
3540 - error: it shakes and the dot drops into the V;
3541 - no: a small shake in grey;
3542 - notify: the dot pings;
3543 - live: ripples go out from the ring;
3544 - sleep: grey, the dot sinks.
3545
3546 The intro draws the line in and drops the dot. Hover fills the line into the coin. The motion budget and the
3547 seasons are unchanged.
3548- **Nothing spins any more.** The kit's spinning rings are gone:
3549 - the loader shows the mark with its dot hopping;
3550 - the player's buffering is three hopping dots;
3551 - the Action Center, the Dock, the notifications list and agent sessions use three dots, and the Action Center's
3552 refresh icon pulses;
3553 - the showcase's and the room kit's decorative rings rest.
3554
3555 The owner, 2026-10-31: "a spinning circle around an icon… is so lame… things only spin if they're active."
3556
3557## 4.57.2 — 2026-10-31
3558
3559- **One page is marked in the navbar after a boost page move.** A site that marks its links on the server (Codes'
3560 workspace) marked them for the page it served; after `openvibe-shared/boost` swapped in the next page, the old
3561 page's link stayed lit beside the new one (Usage and Agent catalogue both lit on openvibe.codes). The navbar now
3562 moves a server mark to the link that leads to the new page (or that the new page sits under).
3563
3564## 4.57.1 — 2026-10-31
3565
3566- **A tool's terms line wraps cleanly.** The facts in `kit.toolHead({ facts })` are spaced apart, not joined by dots,
3567 so a wrapped line no longer starts with a dot that reads like a list bullet.
3568- **`kit.seg` wraps on a narrow screen.** A segmented control with four to six options (Tools' formatters) stays
3569 inside a 320 px screen instead of running off it. Tools had a page-scoped rule for this; the kit does it now.
3570
3571## 4.57.0 — 2026-10-30
3572
3573- **The Dock can use a refreshing token and a read-only scope set** (the browser extension's one-click
3574 sign-in). `OpenVibeDock.mount({ token, scopes })` and the Action Center accept a `token` that is a string, a
3575 sync function or an async function, resolved before every request; the Dock's read, chat and notification
3576 calls all use it. `scopes` is optional: a site's Dock passes none and keeps every control. The extension
3577 passes `['dock','activity','chat.read']`, so the Agents tab keeps **Details** but hides approve/decline
3578 (`actions.decide` absent) and a thread hides the composer (`chat.write` absent) while still reading and
3579 marking read. `notification-ui.js` and `notification-live.js` resolve a token function per request too, so the realtime ticket uses the refreshed token.
3580- `dock.js` is 14,219 bytes brotli; the budget is 14,336 (14.0 KB), 117 bytes of headroom.
3581
3582## 4.56.0 — 2026-10-30
3583
3584- **A tool states its terms.** `kit.toolHead({ facts })` adds one short line under the tool's sentence:
3585 - what it costs;
3586 - where the work happens ("Runs in your browser: nothing is uploaded", or "Your file goes to our server and is
3587 deleted after 1 hour");
3588 - its limits.
3589
3590 This is the first outcome of the market research for tool sites, where people's top complaint is limits they are
3591 never told. Facts are escaped text, or `{ text, icon }`; blanks are left out. docs/kit.md says to state them from
3592 the tool's real configuration, never from a guess.
3593
3594## 4.55.0 — 2026-10-30
3595
3596- **The tool frame: one order for every tool page** (owner ask 11). It has six new kit builders: `kit.toolHead`,
3597 `toolInput`, `tries`, `answer`, `family` and `tool` (docs/kit.md, "The tool frame").
3598 - **The order:**
3599 - the head, with a crumb to the family, the name, and one sentence saying what the tool does;
3600 - the box you type in, on the first phone screen, at 17 px so phones do not zoom in, with its clear button and
3601 the tool's one button;
3602 - two or three **Try** examples: a tap fills the box and runs the tool, and without JavaScript each one is a
3603 `?q=` link;
3604 - the answer, as labelled rows with the fact that matters marked, then the next step;
3605 - the family's other tools: one sideways row on a phone, a column beside the tool from 1024 px;
3606 - the About, questions and API folds, then "People also use".
3607 - **One look.** The family's tint is only on the icon tile, as a hue number that never reaches the style attribute
3608 as raw text; buttons keep the site's own accent. That ends the cyan, purple and orange tool pages.
3609 - Everything is plain HTML with escaped text, so it works without JavaScript. It is part of Kit 2: the look is in
3610 `kit-data.css`, and the clear button and the examples are in `kit-data-web.js`. `kit.css` and `kit-web.js` weigh
3611 what they did.
3612- **Fixed: kit-web.js never wired copy buttons, sheet buttons, confirms or titled share links.** Its `once()` marked
3613 an element under the same dataset name as the attribute that asks for the behaviour: `data-ovk-copy="#id"` is
3614 `dataset.ovkCopy`. The markup had therefore already "marked" every one of them:
3615 - copy buttons stayed hidden;
3616 - a sheet button opened nothing;
3617 - a kit form with `data-ovk-confirm` ("Delete it?") was sent **without asking**.
3618
3619 The mark now has a name of its own (`…Wired`), in `kit-web.js` and in `kit-data-web.js`.
3620 `test/kit-web-chrome.test.js` checks each one in Chrome; it fails on 4.54.0.
3621- **The Dock keeps "in a window" to the page.** The pop-out window belongs to one page in one browser and closes with
3622 it, and no other device can open it again (the browser wants a click). So the Dock no longer sends `popped` to the
3623 Network. That also ends the refused PUT, and its retry, that 4.54.0 made on every pop-out: the Network's
3624 `network.dock-state@1` contract has no such key.
3625
3626## 4.54.0 — 2026-10-30
3627
3628- **The Dock pops out into a window of its own** (Dock plan K-D4). On a desktop browser that can (Chromium's Document
3629 Picture-in-Picture), the Dock's header carries a "pop out" button: the panel moves into a window of about 360 × 520
3630 with its tabs and its styles, and the page keeps its pill, which now reads "In a window" — press it and the panel
3631 comes back. Close the window and the panel returns to the page.
3632 - **Where the browser cannot open such a window, nothing changes**: no button, no new behaviour.
3633 - **The window opens only from a click.** A Dock that opened a window by itself would be a window nobody asked for.
3634 - **The Network is told where the Dock is** (`open`, `tab`, `popped`), as it is today. A Network whose state
3635 contract does not know `popped` yet refuses the body; the Dock asks again without that one key, so the rest of
3636 the state still reaches it.
3637 - **The same Dock, the same look.** The panel's own DOM and styles go with it, along with the page's theme colours.
3638- `test/dock-chrome.test.js`: the pop-out button only where the browser can open a window, the panel moving into it
3639 and back, the page's pill saying where the Dock went, and the state reaching the Network.
3640- `test/dock-size.test.js`: the new size budget for `dock.js` (13.77 KB brotli: 12.27 KB on 2026-10-30 plus 1.5 KB
3641 for the pop-out).
3642
3643## 4.53.1 — 2026-10-30
3644
3645- **The patch-notes link under "Recently shipped" wraps on a narrow screen.** It carries a whole post title ("Patch
3646 notes: 81 changes across 43 sites (Oct 30) →") and was kept on one line, so it ran past a 320 px screen (found on
3647 openvibe.host, which had overridden it). The short "What's in it" link in a grouped row stays whole.
3648
3649## 4.53.0 — 2026-10-30
3650
3651- **The full footer folds on phones** (the short-pages rule: a phone page is one or two screens, and the footer alone
3652 was 1.1–1.4 of them). Each link column (the site's own, Network, Popular tools, Legal) is a `<details>` fold.
3653 - **On a phone** (560 px and under) each column is a 48 px row with its heading and a chevron, folded. The footer
3654 measures 506 px at 390 px wide instead of 891 px.
3655 - **A desktop is unchanged.** The heading is only a heading: not a control and not in the tab order.
3656 - **A fold the person opens stays open** through a re-render. Turning a phone sideways (wider than 560 px) opens
3657 every column again.
3658 - **Every link stays in the page.** The server writes every fold open, so crawlers and pages without script see
3659 them all; the client folds them on a phone.
3660- `test/footer-folds.test.js`: the folds' markup, the desktop and phone rules, and the compact footer's whole footer.
3661
3662## 4.52.0 — 2026-10-30
3663
3664- **The Dock's Chats tab** (Dock plan K-D2). The person's conversations and DMs, on every OpenVibe site.
3665 - **Where they come from:** OpenVibe.Chat's own API with the person's Network token (`GET /api/dm/conversations`,
3666 `/unread`, `…/messages`, `…/read`; Chat checks they are in each one).
3667 - **The list:** named for the other person or the group, with the last line ("You: …" when it was theirs), the
3668 time and the unread count.
3669 - **A thread opens in place:** their bubbles and yours; Enter sends and Shift+Enter is a new line. It polls for new
3670 messages every 5 s while it is open, is marked read as it is shown, and links out to Chat.
3671 - The open chat is the Dock's state, so it follows you between sites. The tab counts Chat's unread messages.
3672 - **Safety:** what people wrote is always text, never markup, and only https pictures are shown.
3673- **A tap that redraws its own panel no longer closes the phone sheet.** Opening a chat replaced the list, so the
3674 tapped node was detached by the time the tap-outside check ran; it now reads the event's path at dispatch.
3675- **Narrow Docks** (under 440 px) drop the tab icons, so three tabs, their counts, the pin and × fit.
3676- `dockChatBase` overrides Chat's origin for tests and staging. dock.js is 12.3 KB brotli, for signed-in people only.
3677- `test/dock-chrome.test.js` adds the Chats scenario: the list and its counts, the thread (text, never markup), Enter
3678 sends, read receipts, the state, and the phone sheet staying open.
3679
3680## 4.51.0 — 2026-10-30
3681
3682- **The Dock** (`dock.js`; the Dock plan of 2026-10-30, K-D1; master plan L13). The owner, 10-30: your agent and your
3683 chats "more accessible across sites … sleek, modern, minimal". It is one surface at the edge of every OpenVibe page.
3684 - **The pill:** the mark (busy while an agent works), a word and a dot (amber, with a ping, when something needs
3685 you). It shows only while an agent works, one needs you, or the person pinned it, in the corner the Dock sits in,
3686 above a phone's app bar.
3687 - **The Dock:** a floating card on a desktop and a sheet on a phone, with two tabs. **Agents** is what needs you,
3688 then what runs, with Approve and Decline in place. **Activity** is the bell's list.
3689 - **On a desktop:** it is not modal; the head (or a tab, after 6 px) drags it to a corner and the free corner
3690 resizes it.
3691 - **State:** it is the Network's, so it follows the person between sites:
3692 - `GET /api/v1/me/dock`, then one debounced `PUT` per burst of changes;
3693 - this site's copy paints it first;
3694 - an open Dock reopens on a desktop, never on a phone;
3695 - a 404 keeps it away for a day.
3696 - **Weight:** navbar.js grows by 0.6 KB brotli (the loader, the account menu's Dock tile, `openDock`; its budget
3697 is now 41 KB). The pill and the Dock are `dock.js`, 8.6 KB brotli, loaded for signed-in people only.
3698 - **Test:** `test/dock-chrome.test.js` covers when the pill shows, both tabs, the PUTs, drag and resize within the
3699 limits, restore on desktops only, the 404, signed out, and 320 px.
3700- **`OpenVibeActionCenter.mount(container, { token, apiBase, buckets, onCounts })`:** the Action Center's rows in
3701 another surface's container, with their own data and timer.
3702- **`OpenVibeNotifications.renderDock(container)`:** the bell's list without a frame, with Mark all read in its foot.
3703- **Desktop toasts** stack above the Dock's pill in the bottom-right corner (`--ov-dock-clear`).
3704
3705## 4.50.1 — 2026-10-30
3706
3707- **The seasonal fall lands on the bar.** Once a day the mark's leaves (snow in winter) fell 120-280 px from the brand,
3708 over a page's heading on phones (Codes' conductor, 10-30). They now drift down to the bottom edge of the bar the mark
3709 sits in (`.openvibe-navbar`, `header` or `nav`), settle there and fade, inside the screen's width; a mark outside a
3710 bar gets a short fall of 36-64 px. `test/ov-mark.test.js` covers the landing, the short fall, once a day and no fall
3711 without a season.
3712
3713## 4.50.0 — 2026-10-30
3714
3715- **Rich embeds in the agent session.** The owner, 2026-10-30: "When it generates or reads or analyzed images or visuals
3716 or files it should show a rich embed in chat for them like in Claude, Codex, ChatGPT." A step or a message may now
3717 carry `attachments: [{ url, name, mime, size?, kind, role }]`; the component draws them and never builds a URL itself.
3718 - A step's attachments show in its group's header, visible while the group is folded: images as 136 × 92 thumbnails
3719 with their name, everything else as a chip with an icon. A message's show under its text.
3720 - A tap opens a preview sheet: the image at full width (pinch to zoom on a phone), video and audio in their players,
3721 a PDF in a frame, and text as code, Markdown, a CSV table or pretty JSON, highlighted — plus Open and Download.
3722 - The thumbnails load lazily, a broken image hides itself, and the sheet is keyboard reachable (Escape closes it).
3723 - Safety: an attachment loads only from the product's own origin or Media's, never from a third-party address the
3724 model printed; SVG is only ever an `<img>`; text is set as text, never as markup.
3725 - `test/agent-session-embeds-chrome.test.js` covers it in a real Chrome.
3726
3727## 4.49.0 — 2026-10-30
3728
3729- **Shared motion tokens for production patterns.** Adds standard and back easing curves plus fast, base, slow, and reduced durations (180, 260, 360, and 0 ms).
3730
3731## 4.48.0 — 2026-10-30
3732
3733- **The OpenVibe mark opens a site switcher.** The owner: "clicking the icon … its own drop-down … all the sites, so
3734 you can hop between them fast". `site-switcher.js` loads on the first tap of the mark, as search.js does for the
3735 menu.
3736 - What it shows: a compact grid of every open site, with Recent, then Pinned, then the frame's groups, most used
3737 first. The page's own site is marked.
3738 - How to use it: type to filter, use the arrow keys to move, Enter to go and Esc to close. Hold a site, or
3739 right-click it, to pin it.
3740 - On phones it is a sheet under the bar that keeps its own scroll.
3741 - Recent and pinned follow the account through Network `/api/themes/me/sites`. Signed out, they stay on this site.
3742 The navbar records one visit per site per 30 minutes for a signed-in person, never under GPC or DNT.
3743 - The site's name in the bar stays its link home. `switcher: false` keeps the mark as a home link.
3744 - `test/site-switcher.test.js` covers it in a real Chrome.
3745- **The OpenVibe menu, cleaner** (the owner: "the search input is styled all weird … For you kind of sucks … just get
3746 to basic tools"):
3747 - the search field is a rounded field with a soft focus ring; a site's own focus style can no longer draw a box
3748 on it;
3749 - the placeholder and the headings use the site's short name ("Search Download, or ask Vibe", "On Download"),
3750 not the page title, which was cut off;
3751 - "For you" is now **Start**: this site's places, **Quick tools** (QR codes, images, PDFs, word counts first,
3752 then the most used), recent searches, then popular sites;
3753 - opening, the groups rise in one after another.
3754
3755## 4.47.3 — 2026-10-30
3756
3757- **A tap on the logo no longer opens the menu.** On a phone, the menu button's invisible touch area (4.46.0) covered
3758 the whole navbar. The touch rules made the button `position: relative`, but its own `all: unset` rule came later and
3759 reset it, so the 44 px hit area was placed against the bar instead. A tap on the mark, the site's name or the orb
3760 opened the burger menu on every site on 4.46.0–4.47.2 (found on openvibe.network). The burger now keeps its own
3761 position. `test/navbar-hit-areas.test.js` checks that every touch hit area stays on its own button.
3762
3763## 4.47.2 — 2026-10-30
3764
3765- **The updates log reads as changes, not commits.** `shipped.js`'s log folds the entries of one day that are the same
3766 change into one row: a pin bump by package and version ("Shared 4.45.0 reached 31 sites", with a link to what's in
3767 it), anything else by its subject without the PR number or a leading name of the entry's own site ("Touch targets on
3768 phones (fleet audit)" on fourteen sites). The row shows how many sites, the time span and the sites by name; every
3769 commit is one tap away in its fold. Groups continue across "Load more". openvibe.network/updates went from 50 rows
3770 to 9 with the same 50 commits.
3771- The OpenVibers account every commit shares is no longer named on each row; any other author still is.
3772- `groupEntries(entries, dayKey)`, `groupTitle(g)` and `groupNotes(g)` are exported, so a server-rendered page groups
3773 the same way.
3774
3775## 4.47.1 — 2026-10-30
3776
3777- **No page is wider than a phone.** A sideways row (`.sc-row`, `.ovk-tabs`, `.ovk-stats`) is `position: relative`, so a
3778 hidden label (`.sr-only`, `position: absolute`) inside a card past the edge stays in the row instead of widening the
3779 page. The phone hero's watermark (`.sc-hero-vis`) sits inside the hero's corner instead of 54 px past the screen;
3780 five homes were 434-451 px wide on a 390 px phone.
3781- docs/kit.md "Scrolling on touch screens": the rules for boxes, sheets and sideways rows on touch screens, and the
3782 width check.
3783
3784## 4.47.0 — 2026-10-29
3785
3786- **The kit renders natively, on Android and iOS.** `scripts/native/compose/KitRenderer.kt` (`KitView(doc: KitNode)`, a
3787 Composable that parses the document with org.json) and `scripts/native/swiftui/KitRenderer.swift`
3788 (`KitView(doc: KitNode)`, a SwiftUI `View` that decodes it with Codable) draw a kit document with the kit's own
3789 reference views, wearing the generated tokens (the Material 3 colour scheme and `OvScale` in Compose, `OvTokens` and
3790 `OvScale` in SwiftUI). A tap comes back as an action, so the app decides what a link or a button does.
3791- A sample document (`scripts/native/sample-doc.json`: a hero, a card rail, a list, stats and a notice) passes
3792 `components.validate()`, and the native CI compiles the Kotlin against Compose and type-checks the Swift against
3793 SwiftUI together with the tokens and the reference views. A component added to `components.js` without a native
3794 view now fails the kit check, so the two languages cannot fall behind the web.
3795
3796## 4.46.1 — 2026-10-29
3797
3798- `showcase.apps`: a set of six or nine is three a row (`sc-apps--c3`), on a phone and from 600 px. Four columns left one
3799 tile alone on the last row (nine was 4 + 4 + 1).
3800
3801## 4.46.0 — 2026-10-29
3802
3803- **Touch targets from the fleet audit** (effective hit areas at 390 px: a 7x7 grid of `elementFromPoint` probes over
3804 the 44 px square around each target, `research-2026-10-29/tap-targets.md`). On touch screens (`pointer: coarse`)
3805 only:
3806 - navbar: Sign In, the menu button and the orb get an invisible 44 px hit area (drawn as before). The brand mark's grows
3807 to 44. The site's name is 44 tall. The orb grows only 4 px sideways, so the name 8 px away keeps its own taps;
3808 - footer: column links are rows of 32 px (the footer is ~180 px taller on a phone; WCAG 2.5.8 AA asks 24). The social
3809 links are 44 px, and the legal links in the bar get a taller hit area;
3810 - the shipped strip's lines get a taller hit box with no layout change (padding in, margin out);
3811 - kit: `.ovk-btn--small` is 44 px tall, `.ovk-go` reaches 44 px invisibly, and the find bar's field and select are
3812 44 px tall.
3813
3814## 4.45.1 — 2026-10-29
3815
3816- **The generated native code is compiled now, not just read.** `scripts/native-check.js` writes the theme's Compose
3817 and SwiftUI output (the default theme and the light half it pairs with) and the kit's reference views (the
3818 preamble and all eleven components) out as `.kt` and `.swift` files, and a new workflow
3819 (`.github/workflows/native.yml`) compiles the Kotlin with kotlinc against the Compose runtime and UI artifacts and
3820 type-checks the Swift with swiftc against SwiftUI. A generator that writes code nobody can compile fails the build
3821 here, instead of waiting to be found in an app.
3822- **Two things that check found in the Swift reference views:** the card's ratio was `16 / 9` (integer division, so
3823 the picture area came out square) and the numbers list keyed its rows by a tuple's field, which Swift cannot do.
3824
3825## 4.45.0 — 2026-10-29
3826
3827- **Comfort settings beside the theme.** Four display preferences join motion, text size, seasonal and scheme in
3828 theme-loader.js (`OpenVibeThemeLoader.display`), and follow the account the same way:
3829 - `contrast: 'more'`: secondary and muted text and the borders move toward the text colour, links in running text are
3830 underlined, and focus rings are thicker;
3831 - `font: 'readable'`: Atkinson Hyperlegible where it is installed, else Verdana or Tahoma, with a little more letter,
3832 word and line spacing;
3833 - `colors: 'cb-safe'`: success, warning and danger in Okabe-Ito blue, yellow and vermillion (with a light half), so
3834 red and green never carry a meaning alone;
3835 - `oled: 'on'`: true black backgrounds on dark themes.
3836 Each sets one attribute on `<html>` (`data-ov-contrast`, `data-ov-font`, `data-ov-colors`, `data-ov-oled`). The rules are
3837 `!important` custom properties, because the theme's own values are inline. Nothing is fetched (no font file). They
3838 change how any theme reads, never which theme it is. OpenVibe.Design's "Your look" sets them; OpenVibe.Network keeps
3839 them on the account.
3840
3841## 4.44.0 — 2026-10-29
3842
3843- **`showcase.fold({ id, title, hint, html, open })`** folds read-once material (how it works, the code samples, what
3844 every page shows) under its own heading. The heading stays visible in the summary and keeps its id, so links still
3845 land and the outline does not change; other showcase sections nest inside and drop their own width and padding. It
3846 takes the sections' width and font, which the eleven local folds written on 2026-10-29 had to repeat (three of them
3847 missed: one rendered in the browser's serif, edge to edge).
3848- **`.sc-row`** on any list makes it one sideways row on a phone (scroll snap, the next card peeking, every item still in
3849 the HTML) and leaves it as the page made it on wider screens. `--sc-row-w` sets the card width (82% by default). It
3850 wins over a page's own grid and never inherits a `flex-wrap` (the video home's figures wrapped into 2×2).
3851
3852## 4.43.0 — 2026-10-29
3853
3854- **A theme for one site.** `shell.page` writes `<html data-ov-service="<site>">` (the page's service id, when it is a short
3855 lowercase name), and theme-loader.js asks the account for `/api/themes/me/active?service=<site>`. When the person has
3856 set an exception for that site in OpenVibe.Design ("Games in Neon Tokyo", OpenVibe.Network#419), the site wears it;
3857 everywhere else, and on a Network that does not know the parameter yet, the account's theme as before.
3858 `OpenVibeThemeLoader.site()` names the site.
3859
3860## 4.42.0 — 2026-10-29
3861
3862- **OpenVibe components (`openvibe-shared/components`, also `/shared/components.js` as `window.OpenVibeComponents`).** The kit's components as a contract that is not HTML. Eleven components (button, card, rail, row, list, section, fold, tabs, numbers, notice, empty state), each described by its anatomy and the tokens every part wears, its typed props, variants, states, accessibility and phone/tablet/computer behaviour, with a reference view for Jetpack Compose and for SwiftUI on the same tokens. **Kit documents**: a screen as a JSON tree of components, validated strictly (types, enums, required props, children, depth, size, https or same-site links) and rendered through kit.js exactly as the direct builder calls would. The server-driven UI shape a native app can render with its own views. docs/components.md.
3863- **tokens:** the Compose, SwiftUI and Flutter files put the kit's scales (radius, space, size, type, motion) in one theme-independent `OvScale`; the theme objects carry the colours.
3864
3865## 4.41.0 — 2026-10-29
3866
3867- **Try a theme on any OpenVibe page: `?ov-try=<theme>`** (theme-loader.js). It paints a catalog theme on the site without saving it: built-in themes at once, gallery themes from Network's catalog. The try is kept for the tab (sessionStorage) as the person moves around, and every repaint wears the tried theme (the account's sync, another tab, the device's light/dark). A bar at the bottom, above the app bar and 44 px to the touch, says "Trying <name>" with **Use it** (saves to the account, as the studio does) and **Stop** (back to the account's theme; `?ov-try=off` does the same). Only a catalog name is read from the URL, never colours, and nothing is saved until Use it. `OpenVibeThemeLoader.try.start(id)` / `.stop()` / `.current()` do the same from a page. OpenVibe.Design links to it ("Try it on Live"). test/theme-try.test.js.
3868
3869## 4.40.0 — 2026-10-29
3870
3871- **OpenVibe Tokens (`openvibe-shared/tokens`, also `/shared/tokens.js` as `window.OpenVibeTokens`).** A theme as a W3C Design Tokens (DTCG) document of semantic tokens (`color.bg.base`, `color.accent.strong`, `color.status.ok.text` … plus the kit's radius, space, size, type and motion scales), compiled to CSS variables (with the network's names as aliases), Tailwind, Jetpack Compose (tokens and a Material 3 colour scheme), SwiftUI, Flutter, Figma variables and DTCG JSON. A theme's dark and light halves compile into one file. Values are validated (colours and shadows only) and names become identifiers, so nothing typed in the studio reaches generated code. This is the first piece of making the design system native-ready: real Android and iOS apps wear the same theme, without a web view. docs/tokens.md.
3872
3873## 4.39.1 — 2026-10-29
3874
3875- **room.css stands on its own.** A front page that loads room.css without kit.css (Media's, to skip kit.css's 25 KB)
3876 had no `--ovk-*` tokens: cards lost their corners and colours, a card without a picture had no background, and its
3877 `kit.stats` strip was a bare list. room.css now carries the same tokens (as `:where(:root)`, so kit.css's and a
3878 site's own values always win) and the numbers strip. The copies are identical, so loading both files changes nothing, and
3879 test/room-kit.test.js keeps them that way. room.css: +2.8 KB raw.
3880
3881## 4.39.0 — 2026-10-29
3882
3883What the front-page passes of 10-29 (Media, Quest, Actor, Rent, Services, Community) each had to add locally:
3884
3885- **`kit.rail({ wrap: true })`**: phones still swipe; from 760 px every item shows in a grid, with no arrows and no
3886 kit-web.js. Media and Quest drew this with their own rules.
3887- **`kit.card({ sub, preview, whoHtml })`**: one plain muted line under the title (Rent showed the city as a badge and the
3888 kind only on hover); a text thing's first nine lines in the media instead of an icon (Community's code pastes); the
3889 name as escaped markup with its decorations. **`kit.row({ rel })`** for links people posted.
3890- **No tile alone on a row in `showcase.apps`**: two to four tiles (or eight, twelve…) get a column count that fills
3891 their rows. Four are four across on a phone, two by two from 600 px and four from 960 px (Actor had local rules).
3892- **44 px to the touch**: the rail's See all (32 px), tab pills (36 px), a list's heading link and the launcher's See
3893 all, on touch screens and below 760 px.
3894- **A number that is a link is not underlined**: the whole stats tile is the target, with a hover border and a focus ring.
3895- **The Markdown twin reads kit markup as a person sees it**: a card's who, count and time keep their spaces
3896 ("Maticus12 viewsTue" was what agents read), buttons, icons, players and the ⋯ menu are left out, and a stats strip
3897 is `- label: value` lines.
3898- `scripts/kit-gallery.js --rooms`: a gallery page of front-page pieces (cards, both rails, stats links, launchers).
3899
3900## 4.38.0 — 2026-10-29
3901
3902- **`showcase.apps`: a launcher.** A set of apps or sites as a phone's home screen shows them: icon tiles three or four
3903 to a row with the name under the icon on a phone, rows with a one-line tagline from 600 px, optional groups and a
3904 See all link. The network's front door listed its 47 sites as feature cards, 8,459 px on a phone; as a launcher it is
3905 about a fifth of that.
3906
3907## 4.37.0 — 2026-10-29
3908
3909- **Kit 2 has its own files: `kit-data.css` and `kit-data-web.js`.** Its look and behaviour moved out of `kit.css` and
3910 `kit-web.js`, which weigh what they did before Kit 2 again (kit.css 44 → 25.6 KB, kit-web.js 20 → 12.4 KB). A page that
3911 draws steps, a table, a chart, a ranking, a meter, a drop zone or a receipt adds `kit-data.css` after `kit.css`, and
3912 `kit-data-web.js` for chart readouts and drop zones. Why: Media's front page needed only cards and rails, and the
3913 44 KB kit.css took its stylesheets from 28 KB to 63 KB; every site in the pin round would have paid the same. The
3914 builders (`kit.js`) are unchanged. Breaking only for a page that used Kit 2 on 4.36.x without the new files (Report,
3915 Vision, Sources and Codes, updated alongside).
3916
3917## 4.36.2 — 2026-10-29
3918
3919- **kit-web.js on boosted sites:** a page reached through boost.js (a swap of `#main`, no page load) now gets the kit's
3920 behaviour too — copy buttons, menus, sheets, rails, drop zones, chart readouts. kit-web listens for `ov:boost:load`;
3921 enhance() was already idempotent.
3922- **Tables:** a long value (a DNS name, a model id, a URL) wraps inside its cell instead of widening the table, on wide
3923 screens and in the stacked phone cards.
3924
3925## 4.36.1 — 2026-10-29
3926
3927- **Kit 2 charts:** a scatter label right of centre could run past the chart and widen a phone page (a 390 px
3928 phone laid the page out at 601 px). The chart clips to its own box, and kit-web.js moves a label that would cross the
3929 right edge to the left of its dot. The real cause on Report was the screen-reader copy of the numbers: a table
3930 cannot shrink below its content, so even visually hidden it set the page's width; it now sits in a hidden box.
3931- **Kit headings hold their look on every site:** a site rule such as `.rp h2` had the same specificity as the kit's
3932 `.ovk-section > h2` and loads later, so it restyled kit headings (Report showed a section title at display size).
3933 The kit's heading and paragraph rules now double their class (`.ovk-section.ovk-section > h2`, `.ovk-card .ovk-card-title`),
3934 which outranks any one-class site rule. No markup changed.
3935
3936## 4.36.0 — 2026-10-29
3937
3938- **Kit 2:** the pieces data and tool pages need, in `kit.js`/`kit.css` (`kit-web.js` adds their behaviour):
3939 `steps` (a flow of a few steps), `table` (a real table that turns into labelled cards under 600 px), `chart`
3940 (`line`, `area`, `bar`, `scatter` with a frontier; an SVG stretched to the box with every label as real text,
3941 focusable points and bars, a screen-reader table, a hover and arrow-key readout), `rank` (a leaderboard),
3942 `meter`, `drop` (a file input with drag, paste and the phone camera), `receipt` (a result and what it took),
3943 `page` (the app page's whole shape) and `num` (money, compact, percent, ms). `kit.VERSION` is 2. Additive: no
3944 existing class or builder changed. See docs/kit.md, "Kit 2".
3945- **SEO:** `seo.jsonLd` gains `event`, `jobPosting`, `recipe` and `dataset` builders (the shapes Events, Today, Work,
3946 Food and Bench shipped by hand on 2026-10-29), so the next site to need one uses the shared, tested shape.
3947
3948## 4.35.0 — 2026-10-29
3949
3950- **Showcase:** the page root clips the horizontal axis (`html { overflow-x: clip }`), so a narrow page whose
3951 hero aura is drawn past the right edge never scrolls sideways on a phone. Vertical scrolling and sticky
3952 positioning are untouched — unlike `overflow-x: hidden`, `clip` does not create a scroll container. Four
3953 sites had patched this in their own stylesheets on 2026-10-29; the kit now owns it for every site that
3954 includes `showcase.css`.
3955
3956## 4.34.0 — 2026-10-29
3957
3958- **Agent discovery:** the server-only `agent-layer` module serves `/.well-known/agent.json`
3959 from a service descriptor and Markdown twins from the same public renderer as HTML.
3960 Frame discovery links, Link headers, and optional Markdown links in `llms.txt` accompany
3961 the three-line mount; see [the adoption guide](docs/agent-layer.md). Browser assets are unchanged.
3962
3963## 4.33.0 — 2026-10-28
3964
3965- **Navbar icons:** 15 more Font Awesome solid glyphs are drawn inline (`nav-icons.js`, loaded after first paint):
3966 `fa-gauge`, `fa-pen`, `fa-earth-americas`, `fa-hashtag`, `fa-inbox`, `fa-folder`, `fa-puzzle-piece`, `fa-shield`,
3967 `fa-display`, `fa-microphone`, `fa-infinity`, `fa-list`, `fa-flag-checkered`, `fa-tv` and `fa-compass`.
3968 - Sites already named them in their links and phone app bars, so they used to show **empty** wherever the page
3969 loads no icon font (Actor, Blog, Chat, Codes, Coupons, Deals, Live, News, Services, Trade, Surf).
3970
3971## 4.32.0 — 2026-10-28
3972
3973- **The session view** (`agent-session.js`, plan 2026-10-28 §6 decision 4, CODES-3):
3974 - its look moves to **`agent-session.css`** (served at /shared/, in `files.js`), so OpenVibe.Design can theme it. The
3975 script links it from beside itself unless the page links it in its `<head>` (do that to paint styled from the
3976 first line).
3977 - a **Files** tab: every file the agents edited or wrote, with its +/− and how many changes; a tap opens its step.
3978 `view.files()` answers the same list.
3979 - each step keeps its **raw input**, folded under Input, with Copy.
3980 - **cost per agent** in the head (from each agent's done line; `summary().costByAgent`) and **per step** when a
3981 `tool_result` carries `cost_usd` (and `duration_ms`).
3982 - an optional sticky **action bar**: `opts.actions` / `view.actions([{ id, label, kind }], note)` with
3983 `opts.onAction(id)` (Approve, Continue, Stop…); the buttons stay disabled until the page's promise settles.
3984 - a done line never moves the head's "who is working".
3985
3986## 4.31.0 — 2026-10-28
3987
3988- **The Action Center** (Notifications 2.0, plan 2026-10-28 §3.10): the ✦ beside the bell opens every agent task in one
3989 place, on every site.
3990 - Tabs: Running (re-read every 15 s while open), Waiting for you (Approve and Decline right there), Scheduled, Done
3991 (cost, steps and the page) and Failed (why).
3992 - A service that did not answer is named.
3993 - A 420 px popover on desktop; on phones the bell's Agents tab opens it as a full-height sheet.
3994 - It reads Network's `GET /api/v1/me/actions` (Network#396, contracts 0.229.0).
3995 - `action-center.js` loads on the first click (8 KB brotli budget); `notification-ui.js` adds only the button.
3996 - `init({ actions: false })` leaves the ✦ out; `openActions(anchor)` opens it from a site's own control.
3997
3998## 4.30.0 — 2026-10-28
3999
4000- **The bell center** (Notifications 2.0, plan 2026-10-28 §3.10):
4001 - a 420 px popover on desktop, and a full-height sheet on phones with a drag handle, a × and a scrim;
4002 - tabs: All · Mentions · Live · Deals & alerts · System · Agents, with unread counts from Network's
4003 `/api/notifications/unread-by-tab` (Network#392; none without it), and an Unread filter for any tab;
4004 - each tab says what would be there when it is empty;
4005 - rows carry a thumbnail from `rich_content` (image, thumbnail_url or thumbnail, never the avatar twice), an unread
4006 dot on the left, and swipe right to read or left to clear on touch screens.
4007- **Quiet hours:** in the person's window (Network `GET /api/notifications/quiet-hours`, Network#390) toasts and sounds
4008 wait; the bell still fills, and critical notices still show.
4009- **Readable tones:** severity chips, ×N counts and glyphs mix their tone toward the text colour (the orange WARNING
4010 chip was about 2:1 on Daylight).
4011- `notification-ui.js` loads only after sign-in; its size budget is 24 KB brotli (measured 22.6 KB).
4012
4013## 4.29.2 — 2026-10-28
4014
4015- **`sc.room()`'s look moves to `room.css`** too: 4.28.0 had put it in `showcase.css`, which every product home loads,
4016 and that is what failed 22 sites' CSS budgets (cssRawKB 39.8 > 37 on Help). `showcase.css` is back to its size
4017 before the room kit; `room.css` holds the rooms' accents, cards, rails and front door. A room's pages load
4018 `kit.css`, `room.css` and `showcase.css`.
4019
4020## 4.29.1 — 2026-10-28
4021
4022- **`room.css`**: the room kit's cards, rails and room accents leave `kit.css` for a stylesheet of their own.
4023 - `kit.css` stays the controls only, so sites that use the kit's controls are not charged for components they
4024 never draw. 4.28.0 had made 18 sites' CSS budgets fail on the pin round.
4025 - Pages with `kit.card`, `kit.rail` or `sc.room()` load `room.css` after `kit.css`.
4026- **`motion.js`, `rooms.js` and `room.css` are served at `/shared/`** (they were missing from `files.js`'s lists in
4027 4.28.0, so `/shared/motion.js` answered 404).
4028- **The new rooms have ring icons:** each room id resolves to the nearest existing glyph until it gets its own, so the
4029 menu never draws the fallback mark. The mapping:
4030 - domains: globe;
4031 - design: palette;
4032 - today: clock;
4033 - report: gauge;
4034 - surf and casino: games;
4035 - fun: quest;
4036 - meme: image;
4037 - exchange: trade;
4038 - cam: video;
4039 - tours: map;
4040 - vision: AI;
4041 - shoes: deals.
4042
4043## 4.29.0 — 2026-10-28
4044
4045- **Every result, in the OpenVibe menu** (the Search Fabric, plan 2026-10-28 §4.5; Contracts 0.225.0
4046 `search.fabric.query`). Pressing Enter in the menu's search box now shows every result in the panel itself.
4047 - **Exceptions:** a row picked with the arrows opens that row, and a place's exact name (`live`, `tools`) still
4048 goes to the place. "All results for …" and a recent search open the results too.
4049 - **Tabs:** All · OpenVibe · Web · Video · News · Products, and Yours for a signed-in person.
4050 - **OpenVibe's results** come at once: this site's first ("On OpenVibe.Live"), then across OpenVibe. They are
4051 fetched with Network's `GET /api/frame/query` (scopes network and, signed in, mine; depth quick), through the same
4052 connect-src as suggestions.
4053 - **The web's results** arrive on their own request, with skeleton rows meanwhile. Each says its host and who
4054 found it ("en.wikipedia.org · via Wikipedia").
4055 - **Snippets** run two lines.
4056 - **A closing line** says what was searched and how long it took ("Searched OpenVibe in 40 ms · the web through
4057 DeepSeek and Wikipedia in 1.4 s").
4058 - **Your own results and paid providers:** the person's token goes along, so their own results and the paid web
4059 providers are theirs.
4060 - **Safety:** titles are text and script links are dropped, as everywhere in the menu.
4061 - Typing again returns to suggestions.
4062- The menu's browser test waits for the lazily loaded box instead of a fixed 400 ms (a macOS runner was slower).
4063
4064## 4.28.0 — 2026-10-28
4065
4066- **The room kit** (plan 2026-10-28 §3.3–§3.5, §3.11): what every new room's pages are built from.
4067 - **`rooms.js`** (`openvibe-shared/rooms`, `window.OpenVibeRooms`): the fourteen rooms (Fun, Shoes, Tours, Today,
4068 Meme, Domains, Casino, Exchange, Cam, Surf, Report, Website, Vision, Design) with:
4069 - their neighbourhood, host, hero line (eight words at most), icon and motif;
4070 - an accent every 25° of OKLCH hue: a dark-theme value that passes AA on `#0B0D12`, and a light-theme value
4071 (text and filled buttons) that passes AA on white.
4072
4073 `rooms.style(id)` on a wrapper with the class `ov-room` gives `--room-accent`, `--room-strong`, `--room-fill` and
4074 `--room-on`, in the right one for the theme's mode (`:root[data-theme-mode]`). An unknown room keeps the
4075 theme's accent.
4076 - **`kit.card()`**: one card for every kind of thing.
4077 - Media in the type's shape (16:9 streams and runs, 1:1 memes, shoes and deals, 4:3 reports and themes,
4078 2:1 domains) with a badge (LIVE pulses) and a corner (a length or a price).
4079 - A peek line on hover, and a muted preview loop that loads only while the card is hovered (computers only,
4080 never under reduced motion).
4081 - The title in two lines at most, then who, one count and when. A count of 0 or none is left out.
4082 - A ⋯ menu: Save and Remind when given, and Share always (the system share sheet, else the link copied).
4083 - The whole card is one link.
4084 - **`kit.rail()`**: a row that snaps.
4085 - About 1.2 cards show on a phone, bleeding to the screen's edge.
4086 - Fixed widths on wider screens (`s` for square things, `m`, `l`).
4087 - ‹ › buttons on devices with a pointer, each hidden at its end.
4088 - A card's menu inside a rail is placed on the screen, so the scroller never clips it.
4089 - **`sc.room()`**: a room's front door, in the order every room shares: the hero, the rails, the scenes, then
4090 explore, for developers, questions and the rest of OpenVibe.
4091 - The hero holds up to eight words, one box (with typed hints) or one Start, five chips at most, and the
4092 live proof.
4093 - Every live-proof number is real and is either live or dated; a 0 is left out.
4094 - The room's motif sits beside the hero, and faint in the corner on phones. The default is the room's icon in
4095 an aura of its accent; a room can bring its own.
4096 - Feature scenes alternate sides and reveal as they scroll in.
4097 - **`motion.js`**: reveal on scroll, a number ticker and a typed placeholder.
4098 - The server always writes the end state, so a page is complete without the script, under reduced motion
4099 (the OS setting or the account's) and for crawlers.
4100 - The kit's icons gain `chart`, `swap`, `wave`, `shoe` and `palette`.
4101
4102## 4.27.0 — 2026-10-28
4103
4104- **Notifications 2.0** (`notification-ui.js`). The owner's screenshot on 10-28: three red "Alert:
4105 OpenVibeBrowserCheckFailed" toasts drawn over the open menu, translucent and overlapping, a siren emoji, raw
4106 "run: sudo ovhost …" text, a badge of 67.
4107 - **Toasts:**
4108 - an opaque, themed card with a line glyph for the kind of notice (never an emoji), or the sender's avatar with
4109 the glyph as a badge;
4110 - a one-line title, up to two lines of text, chips, up to two actions and a timeout bar;
4111 - phones show one at a time under the bar, with "+N more" inside the card and swipe to dismiss;
4112 - desktops show up to three at the bottom right.
4113 - **Layering:**
4114 - a toast **never draws over an open menu, sheet, panel or dialog**: it waits, timers paused, and shows when the
4115 layer closes;
4116 - open layers are read from the page itself (the menu's `is-open`, any bar toggle's `aria-expanded`, a drawn
4117 `aria-modal`, `dialog[open]`), so a lazily loaded bell still sees a menu opened before it loaded.
4118 - **Grouping and priority:**
4119 - the same notice within 15 minutes joins the card that is showing ("Browser check failed ×3" with the sites as
4120 chips);
4121 - one notification id is one card, however often a poll or a push brings it;
4122 - critical first: a full stack lets a lower-priority card go, never a higher one.
4123 - **Operator alerts** read as System cards: "Browser check failed · Release 80b7955 · 1 failing check", the
4124 severity, the sites, "Open status", and the fix command behind **Copy fix command** in the bell, never in the
4125 body.
4126 - **The bell:**
4127 - day headers, category chips, unread dots and grouped repeats, plus search;
4128 - the row's read and dismiss buttons sit on its time line, never over its text, and are always shown on touch
4129 screens;
4130 - polling toasts only what is unread and newer than the last thing seen.
4131- **Budget:** `notification-ui.js` 13 → 20 KB brotli. It loads only after sign-in, when the bell mounts.
4132
4133## 4.26.1 — 2026-10-28
4134
4135- **No site name twice:**
4136 - **The problem:** 14 homes opened with "OpenVibe.X · …" under a navbar that already says the name.
4137 - **The fix:** an eyebrow led by the site's name ("OpenVibe.Food · What should we eat?") now keeps only what follows it ("What should we eat?"), starting with a capital.
4138 - **Unchanged:** a name later in the phrase stays. An eyebrow that is only the name was already left out (4.23.0).
4139
4140## 4.26.0 — 2026-10-28
4141
4142- **Every showcase hero gets a visual** (`showcase.js` `hero`; the owner, 2026-10-28: the heroes were "ugly af,
4143 boring… not easy to understand… nice animated visuals"). The 32 sites whose home starts with `sc.hero` get it on this
4144 pin, with no change of their own.
4145 - **The aura:** with no aside of its own, a hero shows the site's mark in three slowly turning rings with accent
4146 dots and a breathing glow, beside the words on a computer and faintly in the top corner on a phone, where it takes
4147 no space.
4148 - **The orbit:** `hero({ …, orbit: { center: { title, sub, href }, inner: [{ icon, label, href }], outer: [...] } })`.
4149 The site's main places turn on the inner ring with their names, and more on the outer ring, every icon upright and
4150 linked. On a phone it sits small above the words. Network's front door has the same orbit.
4151 - **Opting out and overriding:** `visual: false` keeps a hero plain, and an `aside` of the page's own still wins.
4152 - **Motion:** transforms only, still under either reduced-motion setting.
4153
4154## 4.25.0 — 2026-10-28
4155
4156The brand bar, from the owner's phone on 2026-10-28.
4157
4158- **A new mark: the open loop** (`ov-mark.js`, `app-icon.js`): one stroke, an O opened at the top by the V, with the
4159 site's dot in the opening. It replaces the ring, comet, white V and orbiting dot.
4160 - **Colour:** from the accent towards violet.
4161 - **Motion:** it draws itself in, and a short light runs along it.
4162 - **Light themes:** the dot follows the text colour, so it shows there.
4163 - **Icons and cards:** app icons (`icons/`), favicons and share cards (`social/`) are redrawn with it.
4164 - The owner: "the open vibe logo isn't even that great… new ideas… more modern… better effects".
4165- **The OpenVibe menu's orb, beside the brand** (`navbar.js`). The magnifier on the right is gone (the owner: "the
4166 little diamond thing… cool animations… next to the logo").
4167 - **The orb:** nine dots as a diamond. Hovered, a ripple runs from the centre; it waves while the menu loads, and it is
4168 an × while the menu is open (a second click closes it). Nothing moves at rest.
4169 - **The menu** (`search.js`) drops from the orb under the bar: a panel on a computer, a sheet as tall as its contents
4170 on a phone. "On this site" chips carry the place's own icon.
4171 - **Events:** `search.js` sends `openvibe-menu` events and calls `onClose`.
4172- **Both balances on phones.** The folded pill shows OpenCoins and Vibes at every width (it showed the first one up
4173 to 440 px). The bar's padding, gaps, picture and buttons tighten under 400 px; under 350 px the site's word gives
4174 way to the mark.
4175- **Vibes beside OpenCoins in the account menu.**
4176 - **One request:** the navbar asks OpenVibe.Network once, `GET /api/coins/me?with=vibes`, Network#373. Every site's
4177 connect-src allows the Network and none allowed Billing, so Vibes never showed. An older Network falls back to
4178 Billing.
4179 - **Labels:** each balance names itself.
4180- **The account menu on a phone** is a sheet the width of the screen.
4181 - **Tiles:** they never leave one alone on a row (five share a row, six make two of three).
4182 - **Anonymous:** the pill reads whole.
4183 - **Footer:** its buttons share the row evenly.
4184- **On a phone the bar steps away** while you read down and comes back on the first scroll up. It never does with a
4185 menu open or focus inside it. `autoHide: false` keeps it.
4186- **Messages beside the bell** (`messages: { onOpen, href }`, `setMessages(n)` for the unread count), for a site
4187 that would otherwise float a bubble over the page (the owner: "this DMs button is like buggy as hell on mobile").
4188- **Routers get paths.** `onNavigate` (the bar, the account menu and the app bar) receives `/@name?tab=x`, never a
4189 full address. On Live, "My channel" (`https://openvibe.live/@name`) had opened `/https://openvibe.live/@name`.
4190
4191## 4.24.4 — 2026-10-28
4192
4193- **`agent-session.js`: no made-up run time.** A finished run mounted without `startedAt` (a replay) showed "0 s"; it
4194 now shows no time until its done line says how long it took. A live run, or one given `startedAt`, still counts
4195 from then. `summary().durationMs` is 0 when the time is not known.
4196
4197## 4.24.3 — 2026-10-28
4198
4199- **`agent-session.js`: web steps read as lookups.** A tool whose name speaks of the web (`web_search`, `web_fetch`) is
4200 a web step before a file search, and a query given as its description reads "Looked up …". An address reads
4201 "Opened …". OpenVibe.Actor's web searches showed as "Listed …".
4202
4203## 4.24.2 — 2026-10-28
4204
4205- **`agent-session.js` takes `link(url)`**, which decides which https addresses in an agent's text become links; the
4206 rest stay text. OpenVibe.Actor's agents browse untrusted pages, so its task page links only the person's own desk, as
4207 its server-drawn timeline always did. Without `link`, every https address links, as before.
4208
4209## 4.24.1 — 2026-10-28
4210
4211- **`agent-session.js` reads OpenVibe.Actor's task events** (`actor.task-event@1`), so Actor's agents show in the same
4212 view as every coding agent. The owner asked that it "handle that between all agents… in a smart way".
4213 - **Mapping:** plan is a quiet line, text what it said (an error a note), a hand-off a divider, a check a note (a passed
4214 check is good news, not a problem), and `tool_call`/`tool_result` are steps. A result without an id finds its step
4215 by tool.
4216 - **Agents:** any line may name its `agent`, and the head follows who is working. A hand-off without a `from` is the
4217 agent that was working.
4218 - **States:** `{ t: 'state' }` lines (Actor's task states) move the head.
4219 - **No duplicates:** an agent's start after its hand-off no longer lists it twice.
4220
4221## 4.24.0 — 2026-10-28
4222
4223- **`agent-session.js`: a coding agent's session, as people read it.** Asked for by the owner, 2026-10-28, showing
4224 the Claude app's session view whose running command said "Output unavailable": "OpenVibe.Codes should… actually show
4225 the output from the agent… more user friendly… handle that between all agents… in a smart way".
4226 - **Steps:** steps in a row fold into one group ("1 command · 1 edit · 2 reads", with +/− and a running, done or
4227 failed mark).
4228 - **Inside a step:** its command and diff, and its output, live while it runs; a failed step opens by itself.
4229 - **Helpers:** a helper's steps nest under it.
4230 - **Hand-offs:** they read "Claude Code → Codex", with the reason.
4231 - **The head** stays in view: state, agents, time, steps, files changed, cost, and the plan (TodoWrite) as one line
4232 that opens.
4233 - **Filters:** Everything, What it said, Steps, Problems; Follow keeps the newest step in view.
4234 - **Formats:** it reads Claude Code's stream-json (every OpenVibe.Codes harness adapter's shape) and Codes' job lines
4235 (protocol 1.1).
4236 - **Safety:** the agent's text is never markup, and only https addresses link.
4237 - **For servers:** `normalize`, `kindOf` and `diffLines` work in Node.
4238 - Docs: `docs/agent-session.md`. Tests: `agent-session` (node) and `agent-session-chrome`.
4239
4240## 4.23.0 — 2026-10-28
4241
4242The owner, 2026-10-28, on the phone:
4243- the launcher and the magnifier "kind of accomplish the same exact thing… combine… a better hierarchy… use this user's
4244 history… autocomplete… maybe use tabs";
4245- the account menu: "a lot of this is just not even really needed… a plus button… better use of icons… compactness…
4246 data-driven";
4247- the balances "look a little weird… polish… use the space".
4248
4249- **One OpenVibe menu** (`search.js`). The magnifier, `/` or Ctrl+K opens search, every site and every tool together.
4250 - **Before typing**, three tabs:
4251 - *For you*: jump back in from the person's Network history, this site's pages, recent searches, and their sites
4252 (or the most used across OpenVibe);
4253 - *Sites*: every site in the Frame's groups;
4254 - *Tools*: your tools, the most used tools from the Frame's analytics, and the families.
4255 - **Typing** makes one ranked list: go to (name matches; tools you used first), this site's results and pages, your
4256 history, the rest of OpenVibe (the sites you use first), tools for it, your searches, then search everywhere and
4257 Ask Vibe.
4258 - **Completion:** the box completes from your searches, history, the sites and this site's pages; Tab or → takes it.
4259 - Results carry their site's icon.
4260 - `OpenVibeNavbar.openMenu(tab)` opens it on a tab.
4261 - The drawer's "All sites" opens the Sites tab.
4262- **The launcher button is gone.** Its panel, catalog and site lists moved into `search.js`, which loads on first use,
4263 so `navbar.js` is 15 KB smaller (161 → 146 KB raw, 37.4 → 33.8 KB brotli). Guests' display controls moved to the
4264 foot of the phone drawer.
4265- **A compact account menu.**
4266 - **Header:** + (add an account), the gear and sign out sit beside the name; other accounts are small pills.
4267 - **Your places:** a grid of icons, four across, the site's own first, eight shown and the rest under More. The order
4268 follows what this person opens: counted on this device only, a place opened twice moves up.
4269 - **Foot:** one row for light, dark, auto, text size, motion and themes.
4270 - **Gone from it:** Notifications (the bell has them; the row stays where there is no bell), "Recently used" (the
4271 menu's Jump back in) and the long list.
4272 - Items take `short` for the grid's label.
4273- **Calm balances.**
4274 - **The pill:** one pill on the bar's own surface; each balance a coloured icon and a plain number, a hairline between
4275 them.
4276 - **On phones:** the pill shows the first balance up to 440 px, so the site's name always reads whole, with Apple's
4277 wider system font too; a tap lists both. Wider screens show both. The pill is compact at every phone width.
4278 - **Computers:** one chip per balance in the same style.
4279 - **Room:** on phones the bar drops the empty spacer's gap.
4280- **The showcase hero on a phone** (the owner on openvibe.quest: "why is there empty space at the top… why is
4281 openvibe.quest twice… better visuals… icon usage… reduce wasted space… the same for all the other sites"). Every site
4282 built on `showcase` gets:
4283 - **No repeated name:** an eyebrow that is only the site's name ("OpenVibe.Quest") is left out, since the navbar shows
4284 it; any other eyebrow stays, and `{ icon, text }` gives it an icon.
4285 - **Less space and text:** the hero starts 14 px under the bar (was 36), and the title and lede tighten.
4286 - **Buttons:** they sit side by side.
4287 - **Numbers:** they go three across, and `stats` items take an `icon`.
4288- The mark's seasonal leaves fall under the menus, not over them.
4289- `docs/kit.md`: the OpenVibe menu, the account menu, balances.
4290- Tests: `navbar-launcher` covers the Sites tab and "All sites"; `search-loading-chrome` covers the tabs and the
4291 completion; `navbar-touch-hover` checks the menu's hover-only rows; `navbar-phone-fit-chrome` checks the bar
4292 from 320 to 430 px; `navbar-brand` covers the new header and grid.
4293
4294## 4.22.2 — 2026-10-28
4295
4296- **In-page links scroll instead of reloading** (`boost.js`). A link to a part of the page (`href="#the-quests"`) made
4297 Chrome fire `popstate` with no state. boost answered every state-less `popstate` with `location.reload()`, so the page
4298 loaded again and landed at the top (openvibe.quest's "See the quests", on every boosted site).
4299 - boost now knows which page it is showing. A `popstate` on that same page (a part link, or Back and Forward between
4300 parts) is left to the browser: the part scrolls into view, and Back returns to where the person was.
4301 - `test/boost-chrome.test.js` covers both.
4302
4303## 4.22.1 — 2026-10-28
4304
4305- **Search suggestions reach every site.** `search.js` asked `search.openvibe.network` from the page, and the
4306 `connect-src` of 33 sites refused it (Live among them): the magnifier opened but suggested nothing. Suggestions now
4307 come through OpenVibe.Network's `GET /api/frame/suggest`. That route asks Search, keeps answers 30 s and keeps
4308 Search's limit per person; every site already allows `openvibe.network` for the navbar.
4309 `OpenVibeSearch.suggest(q, { url })` takes either address.
4310- **The navbar fits a phone, signed in.** On openvibe.games the site's name was drawn under the launcher and the
4311 magnifier: the right-hand group (magnifier, balances, bell, picture, menu) left the brand about 19 px, and the name
4312 never clipped. The changes:
4313 - **The name:** it gives way with an ellipsis, and only the site's word shows up to 480 px (was 420).
4314 - **Balances:** up to 440 px the folded balances show the first one; a tap lists all.
4315 - **The launcher:** a signed-in phone (and every phone under 375 px) keeps it in the menu's "All sites".
4316 - **Spacing:** the right-hand buttons sit 2 px apart under 400 px.
4317
4318 New `test/navbar-phone-fit-chrome.test.js` covers 320–430 px, signed in and out.
4319
4320## 4.22.0 — 2026-10-28
4321
4322- **A tidier navbar** (the owner, 2026-10-28: "reanalyze all the nav bars across all the sites… redundant feeling
4323 options… awkward… not front-facing of what the important stuff is"). A sweep of the 38 sites' bars found three
4324 problems:
4325 - **Other sites repeated** after every site's own links ("Live · Tools · Space · Community"), twice on Codes and
4326 News.
4327 - The bar no longer repeats them: the launcher lists every site, and the phone drawer keeps "Across OpenVibe".
4328 - `networkLinks: <n>` (or `true` for four) brings the strip back for a site that wants it.
4329 - **Long bars**, up to twelve entries. Links marked `more: true` now fold into one "More" dropdown, and the drawer
4330 lists them like the others. Clicking More opens it; it never follows a link.
4331 - **"What shipped" and "Search" as main places.** docs/kit.md ("The navbar's links") sets the rules: four or five
4332 places with icons, `more` for the rest, Search as the magnifier, Updates in the footer, the account menu for the
4333 person's things.
4334- **Who OpenVibe is, in every site's structured data** (`seo.jsonLd.organization()`, the publisher every page names).
4335 - The Organization gains a description, `alternateName` (OpenVibe Network, OpenVibers), a
4336 `disambiguatingDescription` ("…not the Openvibe mobile social-media aggregator app"), and fifteen sites in `sameAs`.
4337 - Why: Google's AI Overview for "whois openvibe" described an unrelated "Openvibe" app (the owner, 2026-10-28).
4338 - Network's home page and llms.txt say the same (Network #368).
4339- `test/navbar-more.test.js`; `test/seo-org.test.js`; `perf-budget-chrome` gives a loaded runner three tries to report
4340 the late block's shift (it failed macOS CI twice on 2026-10-28).
4341
4342## 4.21.0 — 2026-10-28
4343
4344- **Search on every site** (`search.js`; navbar `search`, `openSearch()`).
4345 - The owner, 2026-10-28: "a magnifying glass button that's more compact, shared across the sites… intelligent… lazy
4346 loading and autocomplete… reused in the SDK and API".
4347 - Every navbar has a magnifier; `/` and Ctrl+K open it too. `search.js` loads on first use.
4348 - It shows this site's pages and your recent searches, then OpenVibe.Search's suggestions as you type (the older
4349 request cancelled). This site's results come first, then the rest of OpenVibe.
4350 - It always offers the site's own search page, "Search all of OpenVibe" and "Ask Vibe".
4351 - Keyboard and screen-reader friendly; full screen on phones. `search: false` hides it.
4352 - `OpenVibeSearch.suggest()` serves a page's own box.
4353 - The navbar's budget is 37.5 KB (37.05 with the button).
4354- **Loading, redesigned and shared** (`loading.js`, `OpenVibeLoading`).
4355 - The owner, 2026-10-28, of Live's stream loader: "old and outdated… consider the latest frames of the stream…
4356 minimalistic… reusable and shared across the sites".
4357 - An edge-to-edge overlay with no card: the thing's own picture blurred and drifting, a thin ring, one status line,
4358 and a two-pixel progress line.
4359 - `update` / `slow` (a hint and actions) / `fail` (Retry) / `done` (fades out); skeleton rows for lists.
4360- `shipped.js` labels OpenRestream "Restream" among the short names (the launcher did the same in Network #367).
4361- `test/search-loading-chrome.test.js`:
4362 - Search: lazy, scoped, cancelled, text-only titles, script links dropped, keyboard.
4363 - Loading: steps, slow, fail, done, skeleton.
4364 - No sideways scroll at 390 px.
4365
4366## 4.20.1 — 2026-10-28
4367
4368- **The kit's behaviour is its own small file, `kit-web.js`** (about 7 KB, 2 KB compressed): copy buttons, menus that
4369 close, Enter to send, confirms, sliders and sheets.
4370 - A server-rendered site loads only `kit-web.js`. Its markup comes from `kit.js`'s builders on the server.
4371 - A page that draws itself loads `kit.js` too; `OpenVibeKit.enhance()` now calls `OpenVibeKitWeb.enhance()`.
4372 - Loading all of `kit.js` (33 KB) on every page pushed a site's home over its JavaScript budget (Coupons: 328 KB raw
4373 against 292).
4374
4375## 4.20.0 — 2026-10-28
4376
4377- **The OpenVibe Kit** (`kit.js`, `kit.css`; `require('openvibe-shared/kit')`, `window.OpenVibeKit`). One set of app
4378 pieces for every site and anyone's, phone first.
4379 - The owner, 2026-10-28: "the pages are so dang long… use tabs or collapsible areas… on a phone it should act more
4380 like an app… one app ecosystem across all our systems… part of the SDK and the API… explained in Markdown and on
4381 the wiki… reusable for other people's projects."
4382 - **The pieces:** a sticky head (back, title, one line, icon actions) with link tabs (`tabs`, `tabOf`), rows and
4383 lists, sections, folds and a "more" menu; switch, range, select, segmented and chip rows with a save bar; a find
4384 row and a dock (the composer pinned at the bottom); a numbers strip; empty states, notices, copyable snippets,
4385 bottom sheets; buttons, icon buttons, one-button forms with a confirm; 78 line icons; `when()` and `mark()`.
4386 - **How it runs:** the same builders on the server and in the page, every text escaped and unsafe URLs dropped.
4387 `enhance()` adds copy buttons, menus that close, Enter to send, confirms, sliders that speak and sheets, and runs
4388 by itself in the page.
4389 - **The look:** `kit.css` on the site's theme tokens (`--ovk-*` from `--bg-*`, `--text-*`, `--accent-strong`…).
4390 44 px touch targets, a sticky head and dock, layouts for phones (< 600 px), tablets and computers (≥ 1024 px,
4391 `.ovk-split`).
4392 - **Docs and gallery:** docs/kit.md (the why, with the 38-site phone sweep; the shape of a page; every builder;
4393 adoption) and `scripts/kit-gallery.js` (one page with every piece, screenshots at 390/768/1280 and a sideways-scroll
4394 check).
4395- **The phone app bar** (`appbar.js`; navbar `appBar`, `setAppBar()`).
4396 - A site's main places as a bottom tab bar under 760 px: `appBar: true` takes the links that carry an icon (four and
4397 More, which opens the drawer), or a list of five.
4398 - The open page is marked, a link's dot shows on its tab, and clicks go through `onClick` and `onNavigate`.
4399 - It steps aside for the keyboard and open panels, and keeps the page's last line above it (`html.ov-has-appbar`,
4400 `--ovnav-appbar-h`).
4401 - `navbar.js` loads it only for a site that sets `appBar`, so the navbar's own size is unchanged.
4402
4403## 4.19.0 — 2026-10-28
4404
4405- **Footer `variant: 'auto'`** (`footer.js`). The owner, 2026-10-28: "the full-size footer on the chat pages might be a
4406 little much … if we want to have that app-like design … make it a little more sleeker across sites".
4407 - It shows the full footer on the site's front page (and the paths in `fullOn`, default `['/']`), and the one quiet
4408 line (`compact`, with "More from OpenVibe" to open the rest) everywhere else.
4409 - On the server, `ssr({ variant: 'auto', path })` picks from the request's path; with no path it is compact.
4410 - In the browser it reads `location.pathname`, follows back and forward on a single-page site, and
4411 `setVariant('auto')` is a variant like the others.
4412 - A claim still wins over it (Live's channel while live).
4413 - `autoFor(path, fullOn)` is exported.
4414
4415## 4.18.0 — 2026-10-28
4416
4417- **The account menu, reviewed** (`navbar.js`). The owner, 2026-10-28: "duplicated things … could just be organized
4418 better … so many icons sizes that aren't consistent … needs a full review". The changes:
4419 - **Nothing twice.**
4420 - The balances are left out of the menu's header when the bar shows them (a site's `coins`/`vibes` chips, or
4421 `wallet: true`).
4422 - "My Profile" is gone: the name and picture are the profile link.
4423 - "Seasonal themes" is gone: its switch is on openvibe.network/themes, beside the themes.
4424 - "Linked Services" is gone (it is in the account page), and so is "Across OpenVibe": the other sites are the
4425 launcher's and the drawer's.
4426 - **One order on every site.**
4427 - First the site's own rows (`before`, its `sections`, then `after`, which now sits with them).
4428 - Then **You**: Account & settings, Notifications, My channel, History, Ask Vibe, Work together, and Admin for an
4429 admin or the owner.
4430 - Then **Display**: Light / Dark / Auto, text size, animations, and Themes ›.
4431 - Then Sign out.
4432 - **One icon box.** Every row's glyph keeps its height and sits centred in the same 20 px box, whatever its width
4433 (an id card is wider than a bell).
4434 - Rows are 13 px text in 32 px rows, and a link row no longer overflows the menu by its padding.
4435
4436- **The browser harness on macOS** (`browser-harness.js`): Chrome starts with `--disable-backgrounding-occluded-windows`,
4437 `--disable-renderer-backgrounding` and `--disable-background-timer-throttling`, so every page draws and runs its timers
4438 as if in front. On macOS a new page counted as an occluded window and painted nothing (the Core Web Vitals test saw no
4439 paint and no shift). CI now runs the suite on Windows and macOS too.
4440- **`perf-budget` vitals wait for the first paint before the click** (`measure({ browser })`): Chrome stops recording
4441 LCP at the first input, and a slower machine clicked before anything was painted (no LCP at all on the macOS runner).
4442
4443## 4.17.0 — 2026-10-27
4444
4445- **`wallet: true`: OpenCoins and Vibes in the bar on any site** (`navbar.js`; owner, 2026-10-27: "make that as a
4446 feature in the shared nav bar … maybe we'll put it on games too").
4447 - The navbar asks the Network (`/api/coins/me`) and Billing (`/api/me/vibes`) itself, at most once a minute, and again
4448 when the tab comes back.
4449 - The last balances are kept per person (`localStorage`), so the next page paints the chips at once: no empty chips,
4450 no jump.
4451 - Vibes hides while Billing answers 409 (not the ledger yet). A site's own chips with the ids `coins`/`vibes` (Live)
4452 win; signed out, nothing is asked.
4453 - On a phone they fold into one chip (4.16.0).
4454
4455## 4.16.0 — 2026-10-27
4456
4457- **Balance chips fold into one on a phone** (`navbar.js`; owner, 2026-10-27: "the currencies are taking up all the
4458 space … compact the two currencies into a single one when on smaller mobile resolutions … so you can still swap
4459 between sites on mobile").
4460 - With two or more chips at 560 px and narrower, the bar shows one chip with each icon and a short value ("161k · 400").
4461 - A tap lists each in full; a row does what its chip does (a chip may now carry a `label`).
4462 - `setChip` keeps both up to date. A site can show the site switcher next to its logo again (Live hid it to make room).
4463- **"Across OpenVibe" in the menu is a section of its own:**
4464 - a tinted block with a header (the OV mark) and **All sites** (it opens the launcher);
4465 - the other sites two to a row, each with its mark (up to eight).
4466- **Icons no longer pop in.**
4467 - `ov-icons.js` loads once when the page settles (`requestIdleCallback`), and at once on intent (a pointer over the
4468 launcher, a touch, focus), instead of the first time the launcher, menu or drawer opened.
4469 - Every icon slot there has its size and a faint ring until it is drawn, so nothing moves when the glyphs arrive.
4470- **An open tab stays on the latest version** (`release-watch.js`; owner, 2026-10-27: "make sure people are on the
4471 latest version … if the client needs to update or something, it should just be self-aware or intelligently do that").
4472 - A release that only prompted ("Update ready · Reload") used to wait for the button forever. Now it applies itself
4473 while nobody is looking: after the tab has been hidden for 30 s, unless something is protected (typing, a dirty form,
4474 playing media, a live camera or mic, `[data-ov-protected]`, `OVProtected()`).
4475 - Any reload comes back at the same scroll position, for the same address, for ten minutes.
4476 - A site can opt out with `OVReleaseConfig.autoApply = false`. Style and content releases still apply in place, and
4477 required ones reload when safe, as before.
4478
4479## 4.15.2 — 2026-10-27
4480
4481- **Showcase sections no longer fade in** (`showcase.js`, `showcase.css`). The scroll reveal animated opacity from
4482 nothing, so a section below the fold was half transparent when it was measured. Text in it then failed contrast checks:
4483 the post-release browser check (axe) reported color contrast on ten product homes (help, host, inventory, media, rent,
4484 run, services, space, vip, zone). VIP's muted text measured #4d5464, its colour at about half opacity. The sections
4485 still rise 18 px into place for people who want motion. `test/showcase.test.js` fails if the reveal animates opacity again.
4486
4487## 4.15.1 — 2026-10-27
4488
4489- **The compact footer is one line again** (`footer.js`). 4.15.0 put "More from OpenVibe" on a row of its own under the
4490 compact line, so every page with the compact footer lost about 25 px: 78 px instead of 53 px at 1400 px wide. That
4491 includes players, a live channel and the Games editor, whose canvas shrank and moved its snap control under a click
4492 its tests make, so Games#157 failed. Closed, "More from OpenVibe" now ends the line as a link like the others (the
4493 compact footer is a wrapping row); opened, it takes a full-width row below with its pill, as before. Measured in
4494 Chrome: 46 px closed at 1400 px, as in 4.14.0; 135 px on a 390 px phone (152 in 4.15.0); opened, unchanged.
4495
4496## 4.15.0 — 2026-10-27
4497
4498- **The footer adapts to what the page is doing, opens up from compact, and shows the network right now** (`footer.js`;
4499 the owner, 2026-10-27: "you can use compact footer on the live streamer channel when they are live only … but it
4500 should be full mode when not live. make sure thats something the shared footer system can handle … in a modular way
4501 … more alive and has some stats … allow user to expand to full even on pages that use compact … ensure maximized SEO").
4502 - `OpenVibeFooter.claim('compact' | 'full', key)` → `release()`: a part of the page asks for a variant while it needs
4503 it (Live's channel while its stream plays). Compact wins over full, any claim over the page's own; releasing brings
4504 back what is left. `setVariant` stays the page's own (an SPA route's); `OpenVibeFooter.variant` is what shows.
4505 - The compact footer carries the whole footer in a "More from OpenVibe" disclosure (a `<details>`): opening it needs
4506 no script, and its links are in the page, server-rendered too, so a player or channel page links the network the
4507 way a home page does (crawlers follow them; nothing is hidden by CSS).
4508 - The pulse line, at the top of the full footer (and the opened compact one): streams live now (a pulsing dot),
4509 people watching, the open sites and last week's page views, from OpenVibe.Network's `/api/frame/pulse`
4510 (OpenVibe.Network#345). It is read when the footer scrolls into view and every minute while it stays there;
4511 `pulse: false` leaves it out. Nobody live: "Nobody live right now · go live".
4512 - The network column lists eight sites, not six.
4513 `test/footer-alive.test.js`.
4514
4515## 4.14.0 — 2026-10-26
4516
4517- **The navbar's brand is big enough for a finger** (`navbar.js`). On touch screens the wordmark's links were 22 px tall
4518 and the mark 30 px. They now reach about 44 px (the navbar's height, and a clear ring around the mark) under
4519 `(pointer: coarse)`, without moving anything.
4520- **Volume on phones, and a video with no sound says so** (`player.js`, `player.css`; the owner, 2026-10-26, on
4521 openvibe.video: "Volume seems broken … on mobile can't hear this and also can't easily adjust volume"). Touch screens
4522 only had the mute button. Now the volume slider shows there too, finger-sized (84 px, an 18 px thumb), wherever the
4523 page can set the volume (`.can-volume`: Android). An iPhone leaves volume to its own buttons, so it keeps the mute
4524 button alone. A video with no sound track (that VOD, 5986, was recorded without its microphone) showed a working
4525 speaker that did nothing. Now, once the browser can tell, it shows "No sound in this video" once, with a dimmed speaker
4526 that says it again when tapped, no slider and no "Tap for sound". Firefox reports this directly (`mozHasAudio`).
4527 Chrome and Safari count it: no audio decoded after 2 s of decoded video, which they count muted or not. A new source
4528 asks again. `test/player.test.js`.
4529
4530## 4.13.0 — 2026-10-26
4531
4532- **Switching accounts works on every site** (`navbar.js`; the owner, 2026-10-26: "the thing that lets you switch …
4533 between different accounts doesn't work"). On a product site the account menu's other accounts dispatched an event only
4534 openvibe.network's account switcher listens to, so a click did nothing, and "Add another account" went to the Network
4535 and back to the page without signing the site in. Each other account is now a link through OpenVibe.Network's chooser
4536 (`/login?switch_to=<id>&login_hint=<username>`) that comes back through the site's own sign-in (its `loginUrl`, made
4537 absolute), so the site ends up signed in as the chosen account whatever its session; "Add another account"
4538 (`add_account=1`) does the same for a new one. The list is the accounts this browser used on the site (the account
4539 switcher's plus the ones the navbar saw signed in, `openvibe_seen_accounts`: names and pictures, at most ten, never a
4540 token). openvibe.network's own pages still switch in place; "Switch to Anonymous" shows only there. Account names in
4541 the menu are escaped as text. Needs OpenVibe.Network's chooser of the same night (switch_to, add_account).
4542 `test/navbar-switch-account.test.js`.
4543- **A person's stuff on any site** (`profile-panel.js`, `profile-panel.css`, new). One tag (`data-ov-profile="name"`)
4544 draws someone's worn items, their counts of items, badges, achievements and games, their latest unlock per game and
4545 links to their profile and inventory, from OpenVibe.Network's public profile (which gathers Inventory, Quest and
4546 Play since Network#333 and Play#38). Steam-style, so Games, Quest and any site can show it (the owner, 2026-10-25).
4547 `data-ov-profile="me"` is whoever is signed in on that site (its own `/auth/me`), so a page cached for everyone can
4548 show "your stuff" (a `hidden` element appears once drawn). `test/profile-panel.test.js`.
4549- **One lit row in the menus on phones** (`navbar.js`). A tap left `:hover` on the row it touched, so the "all sites"
4550 launcher showed two highlighted rows: the site you are on and the last one your finger crossed (the owner, 2026-10-25,
4551 on openre.stream). The launcher's and the phone drawer's hover backgrounds now apply only under `@media (hover:
4552 hover)`; the site you are on and keyboard focus stay lit everywhere. On phones a launcher row's description is one line
4553 (OpenVibe.Network sends short menu labels with the regrouped sites), so the list is shorter and easier to scan.
4554 `test/navbar-touch-hover.test.js`.
4555- **The phone menu always opens on screen** (`navbar.js`). The burger's drawer is anchored to the bar (`position:
4556 absolute`, right under it) instead of the viewport. On a page wider than the phone (openvibe.chat/messages with a long
4557 pasted link was 939 px on a 412 px phone), the fixed drawer opened off-screen and the menu looked missing (the owner,
4558 2026-10-25). `test/navbar-drawer-overflow.test.js`. The navbar's brotli budget is 33.5 KB (was 33; 33.0 KB now).
4559- **Ask Vibe from every site** (`navbar.js`). The account menu's "Ask Actor" is now **Ask Vibe**: it opens a new chat
4560 with Vibe, OpenVibe's assistant, at `openvibe.chat/vibe`, with the text selected on the page and the page's address
4561 filled in (never sent by itself; OpenVibe.Chat#165). `_ask.askVibeHref` replaces `askActorHref`;
4562 `test/navbar-ask-vibe.test.js` replaces `navbar-ask-actor.test.js`. Beside it, **Work together** starts a workspace
4563 room on OpenVibe.Chat (`openvibe.chat/work`): a team and Vibe with a work queue, alerts from other systems and a
4564 shared screen (OpenVibe.Chat#163–#168).
4565- **Richer home pages for every site** (`showcase.js`, `showcase.css`, `docs/home-pages.md`). The owner, 2026-10-25:
4566 "more rich content and different types… shared code so you can make nice home pages across all our different sites…
4567 and other people can reuse too". About 30 sites build their homes from showcase.
4568 - New sections:
4569 - `stats`: a strip of real numbers.
4570 - `cards`: rich content in a grid or a swipeable rail, with lazy images that hold their ratio, a LIVE badge and
4571 "See all".
4572 - `spotlight`: features shown with a picture or a short muted loop, alternating sides.
4573 - `faq`: an accordion that works without JavaScript, plus `faqJsonLd()`.
4574 - `network`: "Part of OpenVibe", up to four related products from the curated `RELATED` map, with ring icons
4575 and the `ov_from` tag, so the network links together in a readable, crawlable way.
4576 - `jsonLd()` writes structured data that cannot close its script early.
4577 - The hero gets a soft glow from the theme's accent. Sections rise in on scroll (CSS scroll-driven animations, only
4578 without reduced motion).
4579 - The same safe-link and escaping rules as the other sections.
4580- **One OpenVibe for search engines and AI crawlers** (`seo.js`):
4581 - `headTags` adds OpenVibe's Organization node beside a WebSite that names it as publisher but did not include it.
4582 Chat, Video, OpenRestream, Actor and Work had the reference without the node.
4583 - `robotsTxt` says yes to search, AI answers and AI training in so many words (`Content-Signal: search=yes,
4584 ai-input=yes, ai-train=yes`) unless a site passes `allowAI: false`. The owner, 2026-10-25: "fully AI allowed and
4585 indexable".
4586
4587- **The player sounds, loads and looks better** (`player.js`, `player.css`). The owner, 2026-10-25: "the volume up and
4588 down button on mobile will actually unmute… those little color sample type s*** so that it loads that then the
4589 thumbnail… then the actual video… that background effect… the mute button looks a little weird".
4590 - **Sound:**
4591 - A video playing muted (autoplay) shows "Tap for sound", and the first tap on it unmutes.
4592 - The media volume keys unmute and set the level wherever a browser passes them to the page (`AudioVolumeUp`
4593 and the rest; phones keep their hardware buttons to themselves).
4594 - The viewer's own mute is remembered; a page's autoplay mute is not.
4595 - The Media Session API gives lock screens and headsets play, pause and seek.
4596 - **Loading in layers:**
4597 - `data-ov-color` paints the average colour at once, and `data-ov-thumb` (a tiny inline image) shows blurred.
4598 - The poster sharpens over them, then the first frame fades in.
4599 - Without `data-ov-color`, the poster's average colour is used when the browser allows it.
4600 - **Ambient glow:** `data-ov-ambient` adds a glow of the picture around the player, as in YouTube's ambient mode: a
4601 32×18 sample every 0.7 s while the player plays and is on screen, blurred and crossfaded. It is off for reduced
4602 motion and data saver, and softer in light themes.
4603 - **Controls:**
4604 - One rounded, filled icon set (Material Icons Round). The mute icon was a crude cross.
4605 - Buttons press in; the big play button has a soft ring.
4606 - A short sign shows a seek (±10 s, on its side) or a volume level.
4607 - Touch screens drop the volume slider and keep the mute button; phones get 40 px targets.
4608
4609## 4.12.0 — 2026-10-25
4610
4611- **Avatars show the picture the account has now.** The owner, 2026-10-25: their picture showed on Live but the
4612 navbar on openvibe.network drew their initial. A user record can be older than its picture (a site's session, or a
4613 remembered account, from before the picture was set).
4614 - The navbar (`navbar.js`) and the footer's account row (`footer.js`) ask the Network's `/avatar/<username>` when a
4615 record has no openvibe.media picture. That route redirects to the current picture, or answers an initial.
4616 - A picture that fails to load asks `/avatar/<username>` once (`data-retry-src`) before falling back to the initial.
4617 That also covers an image that failed before the handler was attached.
4618 - On openvibe.network itself the request is same-origin.
4619- **The launcher fits a phone.** Its panel was `min(760px, 100vw - 24px)` wide plus its padding and border, so on a page
4620 without a global `box-sizing: border-box` it ran 10 px past the screen and the page scrolled sideways. It is now
4621 `border-box`.
4622
4623- **Every theme in light and dark.** The owner, 2026-10-25: "each theme should have a light counterpart instead of … a
4624 mixed bag", with an easy light and dark switch in the account menu.
4625 - **Pairs:** `builtin-themes.js` `PAIRS` gives every theme its other half, and each theme carries `pair`. That makes 25
4626 pairs and 50 themes. Thirteen are new light ones: Dawn, Tokyo Day, Alucard, Monokai Light, Shallows, Ash, Vapor
4627 Day, Greenbar, Nord Light, Bright, High Contrast Light, Hayride and Snowfall. Cloud is now named "Vibe Light", the
4628 default's light half.
4629 - **October** (the owner's picks): Midnight Pumpkin (`halloween`), pumpkin orange on OpenVibe's navy, replaces the
4630 violet Pumpkin Night, and **Hayride** is its light half. December gets **Snowfall** beside Frost. `SEASONS` entries
4631 carry `light`. A season stands in for the default theme in either mode (Vibe or Vibe Light).
4632 - **Light / Dark / Auto** (`theme-loader.js`, `OpenVibeThemeLoader.scheme.get()/set()`; the account menu and the
4633 launcher's Display box):
4634 - Light or Dark saves the picked theme's other half (the default's half when a community theme has none). The
4635 season stays as it was.
4636 - Auto (the display preference `scheme: auto`, synced like the others) paints the half matching the device, and
4637 follows it when it changes.
4638 - `pairOf(id)` and `modeOf(id)` are exported too.
4639 - **Smaller loader:** the theme map is now rows of values against one list of token names, expanded at load into the
4640 same full sets. `theme-loader.js` went from 59.5 KB to 50.7 KB with thirteen more themes.
4641
4642- **The OpenVibe mark wears the season; the pumpkin button is gone.** The owner, 2026-10-25: the badge beside the brand
4643 "takes up a lot of space … especially on mobile"; integrate it into the logo instead.
4644 - **The mark's look** (`ov-mark.js`): `<html data-ov-season-mark="halloween|winter">` (set by `theme-loader.js` for
4645 anyone who has not turned seasonal themes off, whatever their theme) dresses every mark.
4646 - October: a pumpkin's stem, leaf and faint ribs, with the V as its grin.
4647 - December: a cap of snow on the ring.
4648 - **Moments:**
4649 - The leaf unfurls when the mark plays its intro.
4650 - Every 4 to 8 minutes a mark on screen flickers like a candle for a second.
4651 - On the first visit of the day, a few leaves (snowflakes in December) drift down from the first mark seen.
4652 - None of this runs under either reduced-motion setting.
4653 - **Navbar:** no `.ovnav-season` badge. The switch stays in the account menu ("Seasonal themes") and the display box.
4654- **Theme changes follow you between sites without a jolt** (`theme-loader.js`). The owner, 2026-10-25: a seasonal
4655 toggle on Live, then Themes in the menu, showed openvibe.network in the old theme until a refresh.
4656 - **Saves outlive the page:** both PUTs (`/api/themes/me`, `/api/themes/me/display`) are `keepalive`, so a choice made
4657 just before following a link still lands.
4658 - **The account is asked again:**
4659 - when the page comes back into view (at most every 10 s);
4660 - once more 2.5 s after arriving from another site, since that site's save may land after this page first asked.
4661 - **Corrections fade:** a theme that changes under someone (the account's answer, another tab) crossfades for 0.4 s
4662 instead of jumping. An answer that changes nothing repaints nothing. Calm motion never fades.
4663- **Your name in the account menu opens your profile.** The avatar and name at the top of the menu link to
4664 `openvibe.network/@username` (an anonymous visitor's to `/my`). They were plain text, and pressing them did nothing.
4665
4666## 4.11.0 — 2026-10-25
4667
4668- **Seasonal themes.** The owner, 2026-10-24: a Halloween theme and style on every site, as the default during
4669 October, part of a seasonal system that turns on by date, with an opt-out for anyone who wants their normal
4670 default.
4671 - **Two seasonal themes** (`builtin-themes.js`):
4672 - Pumpkin Night (`halloween`): pumpkin orange on a violet night, October 1–31;
4673 - Frost (`frost`): frosted teal on a midnight sky, December 1–31.
4674 - **How they turn on.** A theme with `season: { id, name, from, to }` (MM-DD, both days included, the visitor's own
4675 calendar) is also listed in `SEASONS`, and `seasonOn(date)` finds the season for a date. A season may run over New
4676 Year.
4677 - **What the loader paints** (`theme-loader.js`, Seasons):
4678 - Anyone on the default theme sees the season's theme in its place while it runs. A theme someone picked, or a
4679 customised default, stays theirs.
4680 - `<html>` carries `data-ov-season="<id>"` and `data-theme` names the theme on screen. The saved choice
4681 (`getCurrent()`) stays the person's.
4682 - `window` gets `ov:season` with `{ id, name, theme }` or `null`.
4683 - API: `OpenVibeThemeLoader.seasons.{ SEASONS, now(), showing() }`.
4684 - **The opt-out** is a display preference, `seasonal: on|off` (on by default). It repaints at once and in other tabs,
4685 and is saved to the account with text size and motion (`PUT /api/themes/me/display`). Network stores it once it
4686 accepts the key; until then it stays in the browser. Picking the default theme during a season turns seasonal
4687 themes off: "my normal theme".
4688 - **The navbar** shows the season's emblem beside the brand while a season runs: a pumpkin, or a snowflake in
4689 December. It is pressed while the season's theme shows and faded when seasonal themes are off, and a click flips
4690 it. The account menu's Display section and the launcher's display row get a "Seasonal themes" switch.
4691
4692## 4.10.0 — 2026-10-24
4693
4694- **items.css draws `name-fx-twofat`, "2fat Bass Drop"**, the one-of-a-kind name effect the owner asked for 2fat on
4695 2026-10-23: the name thumps on the beat in club lights, with an equalizer before it and a speaker after it. Its
4696 particles (`px-twofat`) are notes, a CD and a tape in club pink. Like Josh Vegas, the Calm setting
4697 (`data-ov-motion=reduced`) and the system's reduced motion stop it, so it shows the same on openvibe.chat, Network's
4698 pages and every site drawing items as on Live (Live#318, OpenVibe.Inventory#71).
4699- **Shared's own status text uses the 4.9.0 twins**: the showcase's "yes" cells and the island's action buttons
4700 (`--ovis-t`: `--success-text`/`--danger-text` by state, the accent otherwise; the status colour stays their fill and
4701 tint). Shared now writes no status colour as text without its twin (H4's last uses; the 23 sites moved on 10-22/23).
4702- **"Report a problem" in every site's footer** (the Legal column, and the compact line). It opens a ticket on
4703 openvibe.help with the site and the page filled in (OpenVibe.Help#81):
4704 `https://openvibe.help/tickets/new?site=<host without www.>&page=<origin + path>`.
4705 - The page leaves out the query and the fragment, where a sign-in code or a token can ride.
4706 - A server-rendered footer links the plain form, and a click fills in the page at that moment, so a single-page
4707 app's current route is the one reported.
4708- **`analytics/pg` keeps bot rows shorter when asked**: `pruneRawEventsPg(db, { days, botDays })` and the tracker's
4709 `retention: { days, botDays }` delete `is_bot` rows after `botDays` (default `days`, so nothing changes until a
4710 service sets it). Bot rows are in the hourly and daily rollups once their day closes, and were half or more of the raw
4711 table: Live 1.49M of 2.47M rows (it prunes them at 7 days itself since 10-22), Network 1.22M of 2.46M (991 MB).
4712- **`analytics/pg` `getStats` reads the raw range 9 times instead of 17** (plan J3). On Live's production data
4713 (1.3M rows), 30 days took 8 to 12 s and now 2.8 s; 7 days 3.1 s → 1.5 s. Related reads share one grouped scan,
4714 split in JS: status codes and bot types; device, browser, OS and country; the busiest API calls and the slowest paths;
4715 and requests, the signed-in split and sessions. Each distinct count per group (visitors per page, per hour of the day,
4716 per day signed in or not) groups by session first instead of sorting for `COUNT(DISTINCT)`. The numbers and response
4717 shapes are unchanged: the old and new reads matched on production inside one snapshot, and
4718 `test/analytics-pg.test.js` checks each list on known rows.
4719
4720## 4.9.0 — 2026-10-23
4721
4722- **Status colours that read as text** (plan 2026-10-22, track H4). After 4.8.0, axe in the light themes still failed
4723 where sites write a status colour as text: `--success` on white reads at 2.3:1 (Games' achievement counts, Work's
4724 badges, Food's recall classes). The status colours themselves stay exactly as each theme set them, because they are
4725 backgrounds too (a live badge under white text, an amber strip under dark text) and darkening them would spoil those.
4726 Instead each gets a `-text` twin held to 4.5:1 on every surface: `--success-text`, `--warning-text`,
4727 `--danger-text`, `--error-text`, `--info-text` and `--live-red-text`. A light theme that sets no status colours
4728 carries the default theme's (theme-loader merges them first), so its twins are derived from those on its own
4729 surfaces: snow's text green is `#167e3c`. Write `color: var(--success-text, var(--success))`; the navbar, user card,
4730 notifications and account switcher do so for their red "danger" words. `STATUS_TOKENS` is exported.
4731- **`name-fx-vegas` in items.css**: the one-of-a-kind "Josh Vegas" name effect (OpenVibe.Inventory `fx_vegas`, issued by
4732 Live), drawn as Live draws it: casino-gold text under marquee lights between two slot reels that spin and land on 7,
4733 and the name pops like a jackpot as they do. The reels are pseudo-elements, so the Calm setting and reduced motion
4734 stop them explicitly. Its set's particles (`px-vegas`: suits, dice and coins) are casino gold.
4735
4736## 4.8.0 — 2026-10-22
4737
4738- **Text reads at 4.5:1 in every light theme** (WCAG AA; plan 2026-10-22, track H4). axe failed 50 to 155 contrast
4739 checks on every site's home in each of the 12 light themes, through three tokens:
4740 - `--accent-light` was a pale tint used as link and eyebrow text (snow's `#38bdf8` on white is 2.1:1);
4741 - `--text-muted` sat at 4.1 to 4.4 on the secondary surfaces;
4742 - `--accent` sat just under the floor in a few light themes.
4743
4744 `builtin-themes.js` `deriveTokens` now moves a text token below 4.5:1 on any of the theme's surfaces (`--bg-primary`,
4745 `-secondary`, `-tertiary`, `-card`, `-input`, `-hover`) toward black, just far enough, measured on the rounded hex.
4746 - Light themes: `--text-secondary`, `--text-muted`, `--accent` and `--accent-light`.
4747 - Dark themes: the two text tokens only; their accents are the theme's brand colour and already read, so Vibe's
4748 `#3b82f6` is unchanged.
4749 - A token that already reads is left exactly as set.
4750
4751 `theme-loader.js` is regenerated. `readableText`, `TEXT_TOKENS` and `SURFACES` are exported. Measured on production
4752 pages with the new values applied: snow on openvibe.network went from 50 failures to 0 apart from site-specific
4753 colours, Food from 14 to 0.
4754- **"Recently shipped"** (`shipped.js`): the site chip's label uses `--text-primary` on its accent tint (the tint cost
4755 `--accent-light` about 0.4 of contrast).
4756
4757## 4.7.0 — 2026-10-20
4758
4759- **The video player** (`player.js`, `player.css`, served at `/shared/`): one look for every `<video>` across the
4760 network (the owner asked to "override the default style of video players across all our sites and vods … for a
4761 more unified look"). `video[data-ov-player]` gets the network's controls in place of the browser's: a scrubber with
4762 the buffered range and the time under the pointer, play, volume, time, captions, speed, picture-in-picture and
4763 fullscreen, hidden while playing and idle, with YouTube's keys and touch gestures. Progressive (no script, native
4764 controls) and media-element only, so a page's HLS, captions and chapters keep working.
4765
4766## 4.6.0 — 2026-10-20
4767
4768- **Page cards.** A site's big pages get a share card of their own, `social/<service>-<page>.jpg`, drawn like the site
4769 card with the page's name as the headline, the site's brand beside the mark and the page's address in the pill:
4770 `live-rundown` (The Rundown), `live-content`, `live-moments` (AI Moments) and `live-chat`. They are listed in
4771 `scripts/build-social-cards.js` (`PAGES`); `social.url('live-rundown')` and `/shared/social/live-rundown.jpg` work
4772 like a site card. `--only live` renders a site with its page cards.
4773- **Live's card re-rendered** from Contracts 0.205.4: "Go live from your browser", with no OBS, no downloads and no
4774 follower minimum underneath (it said "Streams, clips and chat").
4775
4776## 4.5.0 — 2026-10-20
4777
4778- **Video sitemaps.** `seo.sitemapXml` takes `videos` on a URL: `{ thumbnail_loc, title, description, content_loc
4779 and/or player_loc, duration?, view_count?, publication_date?, family_friendly?, uploader?, uploader_info?, live? }`,
4780 written as Google's `<video:video>` (sitemap-video/1.1). The `xmlns:video` namespace is declared only when a video
4781 is listed, so every existing sitemap is byte-for-byte the same. An entry missing a thumbnail, title, description or
4782 a file/player is left out (its page stays). Titles are clipped to 100 characters, descriptions to 2,048, and a
4783 duration outside 1–28,800 seconds is omitted. First user: openvibe.video, whose recordings and clips had no video
4784 sitemap.
4785
4786## 4.4.0 — 2026-10-17
4787
4788- **Every site in the launcher, in groups.** The menu beside the brand showed the first 12 of the network's sites (the
4789 owner: "missing tons of sites … needs organization"). It now lists every site the Frame sends (up to 60), under the
4790 Frame's `groups` in order (Watch & stream, Create & play, Talk & learn, Shop & everyday, Build & automate, Your
4791 OpenVibe), most used first within each, as compact tiles: four across on a computer, two on a phone, with the
4792 tagline under each name and the page's own site marked (`aria-current`). The search box stays on top while the menu
4793 scrolls. A site in a group the navbar does not know goes in the last group; a Frame without groups (older Network)
4794 shows one "Sites" section. Products on their own domain (Pics, Download, Homes) come with Network's frame.
4795- The Frame cache key is `ov_frame_v2` (navbar.js and footer.js alike), so a cached 12-site answer is not reused.
4796- navbar.js: 31.4 KB brotli (budget 32 KB).
4797
4798## 4.3.0 — 2026-10-17
4799
4800- **Vibes in the account menu on every site.** Next to the OpenCoins chip, the menu header shows the person's Vibes
4801 from OpenVibe.Billing (`GET https://billing.openvibe.network/api/me/vibes`, their token, no cookies; the owner: "make
4802 vibes cross site too like opencoins"). Billing answers 409 until it is the money ledger, and then no chip shows,
4803 never a shadow number. The chip links to openvibe.live, where Vibes are bought and spent. `billingBase` points
4804 elsewhere for a test cell. Both balances load in parallel, once per page, and a site with its own `headerChips`
4805 (Live) still asks nothing.
4806
4807## 4.2.2 — 2026-10-16
4808
4809- **IndexNow sends 100 URLs a request.** `ping()` (and the debounced `pingSoon()` flush) splits a list into requests of
4810 100 URLs, a second apart, and returns the URLs sent and the first refusal's status. api.indexnow.org answered 403 to
4811 a 939-URL list from a host whose key it accepted for one URL; 100 at a time were all 200. `createIndexNow` takes
4812 `chunkGapMs` (tests pass 0); `CHUNK` is exported.
4813
4814## 4.2.1 — 2026-10-15
4815
4816- **A showcase table that scrolls is a focusable region.** `compare()` and `limits()` wrap their table in
4817 `.sc-table-wrap`, which scrolls sideways on a phone. The wrapper is now `tabindex="0" role="region"`, labelled by the
4818 section's heading. An axe scan (WCAG 2.1 AA) of every OpenVibe home at 390 px flagged it on ai.openvibe.services
4819 (`scrollable-region-focusable`, serious). Code samples already were.
4820
4821## 4.2.0 — 2026-10-15
4822
4823- **"Ask Actor" in the account menu.** Signed in, the menu's You section has Ask Actor after Notifications. It opens
4824 openvibe.actor's console with the text the person had selected on the page (up to 1000 characters, spaces folded)
4825 as the ask, and the page's https address (without its hash, up to 400 characters) as where it came from
4826 (`/console?ask=&from=`). With nothing selected it opens the plain console. The selection is read when the avatar is
4827 pressed, since the press itself can clear it. Nothing runs until the person presses Run it in Actor. Signed out,
4828 the link is not shown. Hidden with the rest of the defaults by `menu: { defaults: false }`.
4829- The navbar's brotli budget is 31 KB (was 30): it measures 30.6 KB with Ask Actor and its built-in glyph.
4830
4831## 4.1.9 — 2026-10-15
4832
4833- **Avatars are asked for in the size they are shown.** When a person's picture is an openvibe.media object
4834 (`/o/med_…`), the navbar asks for `?w=<size>&fmt=webp` (64 for the chip, 72 for the menu, 48 for the account
4835 switcher, already twice the shown size). The footer's 24 px avatar asks for 64. Media answers a resized WebP (Fabric
4836 F4, Media's `/o/:id?w=&fmt=`): a sampled 18.6 KB avatar is under 1 KB at 96 px. Any other picture address is used as
4837 it is.
4838
4839## 4.1.8 — 2026-10-15
4840
4841- **Four showcase steps sit two by two below four columns.** In a section narrower than four steps across (under
4842 1192 px), `sc-steps` with exactly four items uses two columns instead of `auto-fit`'s three and one, which left
4843 step 4 alone on openvibe.zone and openvibe.download. Three, five and more steps are unchanged.
4844
4845## 4.1.7 — 2026-10-15
4846
4847- **`shell.page()` removes the browser's body margin.** A site with no stylesheet of its own (openvibe.events,
4848 openvibe.bot) kept the default 8 px, so the Frame's footer stopped short of the window's edges. The head's reserved
4849 style now starts with `body{margin:0}`; sites that set it themselves are unchanged.
4850
4851## 4.1.6 — 2026-10-15
4852
4853- **Play and Inventory show their own "Recently shipped".** `shipped.js` knew no `play` or `inventory` id, so the
4854 widget fell back to the page's host and play.openvibe.network listed Network's changes. Both ids are in the registry
4855 now, and the test checks that every site in `icons/sites.json` resolves to itself.
4856- **Share cards and install manifests for Play and Zone.** `social/play.jpg` and `social/zone.jpg` are drawn from their
4857 Contracts site blocks (build-social-cards.js), and `icons/sites.json` names both (build-icons.js; 37 services), so their
4858 pages get an `og:image` and an installable manifest like every other site.
4859
4860## 4.1.5 — 2026-10-15
4861
4862- **Steps line up.** In a row of steps, a step whose text is one line had a gap under its title: the row stretched it to
4863 its taller neighbour's height, and its own grid spread the extra space between title and text. `.sc-steps li` now
4864 keeps its rows at the top (`align-content:start`).
4865- **Four cards never fall three and one.** A section is now a size container (`container-type:inline-size`), and a grid
4866 of exactly four cards in a section narrower than four columns (520–1035 px) sits two by two. Wider sections keep four
4867 across; phones keep one column.
4868
4869## 4.1.4 — 2026-10-14
4870
4871- **A `play` glyph for OpenVibe.Play.** Play (achievements and leaderboards for every OpenVibe game) gets its own icon:
4872 a podium under a star in the stroke set (`OpenVibeIcons.svg('play')`), and Font Awesome's ranking star in the solid
4873 set. Before it, Play's site could only borrow Games' controller, and the navbar keys a site's entry by its icon, so
4874 the two collided.
4875
4876## 4.1.3 — 2026-10-12
4877
4878- **`shell.page()` holds the navbar's place.** The navbar script is deferred and mounts into the page's `#navbar-mount`;
4879 on a slow first paint the 52 px bar arrived after it and pushed the page down (CLS 0.058 on many homes in a phone
4880 sweep). The head now reserves `#navbar-mount{min-height:52px}`, and a `<noscript>` style removes the empty mount
4881 without JavaScript.
4882- **The updates log no longer jumps while it loads.** `shipped.js` log showed "Loading…" inside the days, then the site
4883 chips and the patch notes arrived above it and pushed it down (CLS 0.17 on openvibe.live/updates on a phone). The
4884 first load now shows one standalone "Loading…" line, a site's own chips are drawn before the data, and the chips,
4885 patch notes and days land together.
4886
4887## 4.1.2 — 2026-10-12
4888
4889- **"Recently shipped" tells visitors what changed, not which library was bumped.** The one-liner and the short
4890 list (`shipped.js` latest and list) pass over library pin bumps ("openvibe-shared v4.1.0 (was v4.0.0)", opened by
4891 pin-bump.js on every repo at once): they read three times as many entries and show the newest real changes; when
4892 only pin bumps shipped, those are shown rather than an empty block. The full updates log keeps every deployed commit.
4893 `isHousekeeping(entry)` is exported.
4894
4895## 4.1.1 — 2026-10-12
4896
4897- **`release.js` reads the commit time on release-layout services.** A release directory belongs to the deploying
4898 user while the service runs as its own, so git refused it ("detected dubious ownership") and `released_at` fell back
4899 to the boot time — on openvibe.live and openre.stream, for example. The read-only git calls now pass
4900 `-c safe.directory=<the release's real path>`, so `released_at` is the commit time again (the sitemaps' static
4901 `lastmod` and open tabs' release checks use it).
4902- **Showcase grids balance five, six or nine cards** in three columns on a wide screen (≥ 900 px), instead of four
4903 and a lonely last row (the Wiki and Events homes showed 4 + 2).
4904
4905## 4.1.0 — 2026-10-12
4906
4907- **Every `shell.page()` site is an installable web app, with no per-site code.** A new `icons/` directory ships
4908 four pre-rendered app icons (192 and 512, any and maskable) per app-icon tint variant — 8 sets, 32 palette PNGs,
4909 about 630 KB, since most sites share the network's tint — drawn by `scripts/build-icons.js` from
4910 `app-icon.svg({ site: variant, maskable })`; `icons/sites.json` gives each of the 35 services with a share card its
4911 name, tagline and variant (`social.iconSite`). The new `openvibe-shared/app-icons` module names them (`has`, `url`,
4912 `site`, `isIcon`, `path`), and `serve.js` answers them at `/shared/icons/<variant>[-maskable]-<size>.png` beside the
4913 browser files and share cards.
4914- `serve.js` now also answers `GET /shared/manifest/<service>.webmanifest` as `application/manifest+json`, built
4915 from the service's Contracts `site` block (name and tagline, committed in `icons/sites.json`) with the same
4916 hashed icon URLs, `start_url`/`scope` `/`, `display: standalone`, the app-icon background and the navbar's
4917 `#0d131d` theme colour. Unknown services 404.
4918- `shell.page()` adds `<link rel="manifest">` and `<link rel="apple-touch-icon">` for the page's service when it has
4919 icons, using the same service resolution as the share-card default (`service`, else `navbar.service`, else
4920 `footer.service`). `manifest: false` opts out, and a page whose `head` already carries a manifest link keeps it.
4921
4922## 4.0.3 — 2026-10-11
4923
4924- Share cards: `run` (openvibe.run has a site block since openvibe-contracts 0.141.0) and a redrawn `host` (its tagline
4925 is now "Hosting, compared honestly").
4926
4927## 4.0.2 — 2026-10-11
4928
4929- `shell.page()` finds the site's share card from `navbar.service` or `footer.service` when the page itself names no
4930 `service` (most sites set it only there), so their pages get `og:image` too.
4931- The site template pins openvibe-shared v4.0.0 (it still named v3.0.0).
4932
4933## 4.0.1 — 2026-10-11
4934
4935- Tagged in error on the 4.0.0 commit: identical to 4.0.0. Use 4.0.2.
4936
4937## 4.0.0 — 2026-10-11
4938
4939- **Share cards for every site** (`openvibe-shared/social`, new): a 1200×630 card per service in
4940 `social/<service>.jpg` (about 60 KB each), drawn by `scripts/build-social-cards.js` from the Contracts service
4941 manifests' `site` blocks (name, tagline, what, first domain, accent) with the app-icon mark and embedded Inter.
4942 `serve.js` answers them at `/shared/social/<service>.jpg?v=<hash>` beside the browser files, and `shell.page()` uses
4943 the site's card for `og:image`/`twitter:image` (with `summary_large_image`) when the page names no image and has a
4944 canonical; `image: null` opts out. `social.url(service)` gives the URL to pages that build their own head.
4945- **Breaking (for a caller relying on it):** `middleware.js` `extractToken` no longer reads Live's legacy `token`
4946 cookie: only the bearer, `ov_token` and the WebSocket query parameter. Live stopped setting that cookie and Chat
4947 stopped reading it (compatibility register C-05). No OpenVibers service imports this module today.
4948
4949## 3.0.1 — 2026-10-10
4950
4951- `notification-live.js` (the bell's realtime feed) resumes from Events' opaque cursor: `last_event_id` is the last
4952 SSE id Events sent, handed back as it came, never a number it read from the message. A replay after a reconnect is
4953 dropped by its `event_id` (the last 256 heard), not by position. `state()` reports `lastId` in place of `lastSeq`.
4954- `release-watch.js` drops a replayed release message by its `event_id` alone (it already resumed from the cursor).
4955- With these, nothing in Shared reads Events' numeric seq as a position, so Events can stop accepting one (plan T7,
4956 ADR-042 decision 7). A bell on an older pin keeps working after that; a reconnect then starts at the head, and the
4957 count poll fills in.
4958
4959## 3.0.0 — 2026-10-10
4960
4961- Removed the SQLite configuration store. `createConfigStore` now requires an openvibe-sdk/db handle and returns a promise of the ready PostgreSQL store. `fromRows`, `adminRoutes`, `configSchema`, and other non-SQLite exports remain.
4962- Removed SQLite analytics: `AnalyticsTracker`, `analytics/tracker`, `analytics/schema`, `analytics/prune-cli`, the SQLite retention and scrub functions, and the SQLite `ANALYTICS_SCHEMA` and `ensureSchema` exports. Use `analytics/pg` (`AnalyticsTrackerPg`, `analyticsSchema`, `pruneRawEventsPg`). `analytics/retention` still exports `MAX_DAYS`.
4963- Consumers already passing an openvibe-sdk/db handle to `createConfigStore` and using `analytics/pg` need no code changes. Live's `test/shared-pins.test.js` still lists `openvibe-shared/analytics/prune-cli` in `RESOLVE_ONLY`; remove that entry when updating its pin.
4964- `better-sqlite3` is gone from devDependencies (and the lockfile).
4965
4966## 2.21.2 — 2026-10-10
4967
4968- `release-watch.js` reopens its release-notification stream with `last_event_id` set to the last SSE id Events sent
4969 (its opaque cursor, ADR-042), not the numeric seq it computed from the message. It still drops a replayed message
4970 by the seq the data carries. This is one of the last consumers of Events' numeric positions (plan T7 retires them).
4971
4972## 2.21.1 — 2026-10-10
4973
4974- **The last fallback "V"s go.** A scan of every site's home page found ring icons drawing the "V" for names with no
4975 glyph: Quest (page), Rent (db, deploy, gauge, page), Food (location, page) and openvibe.download (db, link, shield).
4976 - New glyphs, each a Font Awesome Free solid twin plus a stroke fallback: `gauge`, `link`, `shield` and `plug`.
4977 - New aliases onto existing glyphs: `page` → `docs`, `db`/`database` → `zone`, `location` → `ip`, `gamepad` →
4978 `games`, `robot` → `bot`, `deploy` → `upload`, `copy` → `paste` and `activity` → `ping`.
4979 - The navbar set is unchanged.
4980
4981## 2.21.0 — 2026-10-10
4982
4983- **Every product has its own ring icon.** Ten sites on the openvibe.network home drew the fallback "V": Actor, Help,
4984 Inventory, MediaHub, Quest, Rent, Services, Space, Watch and Work. They now get Font Awesome Free solid glyphs like
4985 the rest: brain, question mark, open box, hard drive, trophy, key, cubes, rocket, eye and briefcase. Glyphs are also
4986 added for the products opening next: `events` (bolt), `ai` (chip), `run` (terminal), `billing` (wallet), `feeds`
4987 (RSS), `pics` (photos), `homes` (house), `website` (window), `zone` (database) and `fund` (hand with coin). `bot`
4988 gets the solid robot. New aliases: `media-hub`/`mediahub` → `drive`, `sources` → `feeds`, `agent` → `actor`,
4989 `jobs` → `work`. Each new name also has a stroke glyph.
4990- Centring re-measured (`scripts/measure-icons.js`): the solid twins had inherited offsets measured for their old
4991 stroke glyphs. Games sat 1.5 units high and Tools 1.3 units right; every glyph is now centred on its measured
4992 bounds.
4993- `scripts/build-nav-icons.py` draws a ring-only glyph without adding it to `nav-icons.js`. navbar.js's width table and
4994 size budget are unchanged.
4995
4996## 2.20.4 — 2026-10-10
4997
4998- Ring icons (`ov-icons.js`, `showcase.css`) no longer put a CSS transform on any element inside the SVG. On a phone,
4999 scrolling left some icons unpainted: a glyph missing inside its ring, or a whole chip icon blank. The icons that
5000 failed were the ones under the scrolling thumb, plus the cards whose comet spins. Both used CSS transforms on SVG
5001 children (`transform-box: view-box` with `transform-origin` on the glyph, a rotated comet circle, a rotated progress
5002 circle and a scaled pulse).
5003 - The comet is now a dash moving round the ring (`stroke-dashoffset`), and the progress arc is a path that starts at
5004 twelve o'clock.
5005 - Pulse animates opacity only, and the hover lift scales the whole `<svg>` element.
5006 - Hover effects apply only under `(hover: hover)`, so a thumb scrolling over a link starts nothing.
5007 - At rest the icons render the same as 2.20.3, comet position included.
5008
5009## 2.20.3 — 2026-10-10
5010
5011- Filled controls use the theme's readable pair, `--accent-strong` with `--on-accent-strong`, instead of `--accent` with
5012 white. White on the default accent (#3b82f6) is 3.68:1, under WCAG AA's 4.5:1 for text this size; the strong pair is
5013 4.62:1 in the default theme, and every theme defines it (dark text on the light accents). This covers the primary
5014 `ui.js` button, the toast's primary action, the account switcher's add button, the footer's avatar initial (white on
5015 #60a5fa was 2.5:1) and the navbar launcher's pressed segment. The release UX check's axe run flagged the same pairing
5016 on product sites.
5017
5018## 2.20.2 — 2026-10-09
5019
5020- On a phone (≤ 420 px) the compact navbar keeps a joined brand whole: openre.stream shows "OpenRestream", not "stream"
5021 (other sites still shrink "OpenVibe.Live" to "Live").
5022
5023## 2.20.1 — 2026-10-09
5024
5025- The OpenRestream brand on openre.stream reads as one word: "OpenRe" and "stream" touch, with no padding between the
5026 two links (`.openvibe-navbar-brand.joined`). 2.20.0 left about 2 px between them, which read as two words.
5027
5028## 2.20.0 — 2026-10-09
5029
5030- The product at openre.stream is **OpenRestream** (owner, 2026-10-09), not "OpenRe.Stream". The navbar brand there
5031 reads as one word: "OpenRe" + "stream" with no dot between, in the brand's two colours, both linking to
5032 openre.stream (`brand().joined`, `brand().site`; ingest.openre.stream is "Ingest.OpenRestream"). The footer,
5033 `shipped.js` and the showcase name it OpenRestream too.
5034
5035## 2.19.0 — 2026-10-09
5036
5037- `items.js` draws **community badges** (`network.badge.image@1`, OpenVibe.Inventory's Workshop): the badge's
5038 reviewed image, from OpenVibe.Media, in an `img.ov-badge` just before the name. The image comes straight from the
5039 equipped read's `media_id`, with no extra request, and only ids of the `med_…` shape become a URL. items.css sizes it
5040 to the text.
5041
5042## 2.18.0 — 2026-10-09
5043
5044- `items.js` draws two more renderers. **Hats** (`live.hat.glyph@1`): the emoji just before the name, with Live's
5045 float/pulse/warp motion. **Particles** (`live.particle.css@1`): a short burst of the item's characters when you point
5046 at or focus the name, never on its own. Hats and particles take their emoji, motion and characters from Live's
5047 definitions: one public read per page, only when someone on it wears one. Elements are made from the name's own
5048 document. items.css carries Live's hat motions and particle colours. Reduced motion and the Calm setting stop the
5049 hats and hide the bursts.
5050
5051## 2.17.0 — 2026-10-09
5052
5053- `items.js` + `items.css`: what people wear in OpenVibe.Inventory, on any site (plan T21 "equip everywhere"). A name
5054 marked `data-ov-subject="usr_…"` gets its owner's name effect (`live.name_effect.css@1`: `ov-fx` + the token) from
5055 Inventory's public batch read, 100 people per request with a 60 s cache. items.css holds Live's 13 name effects
5056 unchanged, so a name looks the same in Live's chat and everywhere else; reduced motion stops the animation.
5057
5058## 2.16.0 — 2026-10-09
5059
5060- The account menu links your public profile: **My Profile** → `https://openvibe.network/@<username>` (plan T21: the
5061 picture, bio, what you wear and the items you own in OpenVibe.Inventory), next to My Account and My Channel. Signed-in
5062 accounts only; an anonymous identity has no profile.
5063
5064## 2.15.0 — 2026-10-08
5065
5066- The navbar fades into the page at the very top (no background or border) and brings its frosted bar back with a
5067 smooth transition as you scroll; it keeps that state itself (`.ovnav-top`) on every render and scroll, so a page no
5068 longer has to. A page whose hero runs under the bar sets `body.ov-nav-overlay`: the bar then sits over the hero with a
5069 soft scrim for its text (a fixed bar, like Live's, needs no pull-up; `--ovnav-real-h` is 0 for it).
5070- The navbar sticks again on sites that mount it in `#navbar-mount`: the wrapper was exactly the bar's height, so the bar
5071 had no room to stick and scrolled away. The wrapper is sticky now.
5072- navbar.js brotli budget 29 → 30 KB (29.4 KB with the fade).
5073
5074## 2.14.1 — 2026-10-08
5075
5076- shipped.js names every site (Space, Services, Actor, Run, Watch, Download and the rest): the update log showed
5077 "space", "services" and "actor" in lowercase next to "Host" and "Community". A site missing from the list still reads
5078 as a name (its id capitalised), and `siteName` is exported.
5079
5080## 2.14.0 — 2026-10-08
5081
5082- A boost page move no longer keeps the old navbar. 2.13.3 removed the render's document listeners, but panels.js
5083 still held every panel ever registered (the drawer on each render; signed in, the account menu too) in its list,
5084 with a MutationObserver, so each re-render kept the whole removed bar and its listeners alive.
5085 `OpenVibePanels.register()` now takes `signal` (an AbortSignal: the registration ends when it aborts, its observer
5086 is disconnected) and returns its `unregister()`; `OpenVibePanels.unregister(el)` does the same by element. The
5087 navbar registers its panels with the render's signal, gives the display rows' `ov:display` window listeners that
5088 signal too, and drops a gone render's queued registrations.
5089- browser-harness: the navigation probe forgets a window or document listener when its AbortSignal aborts, as the
5090 browser does. It kept every one, and with it whatever the listener closed over, so the growth check also measured
5091 its own probe: with 2.13.3's navbar alone it still saw the old bars.
5092- Measured with ovhost browser-check's method (probe on, forced GC, lap 2 → 5), the patched files served into the
5093 live pages: openvibe.codes +780 nodes, +60 listeners → 0, 0; openvibe.services +852, +60 → 0, 0; openvibe.space
5094 +744, +72 → 0, 0; openvibe.community +3324, +72 → 0, 0. Both halves are needed: either one alone still grows.
5095- New test boost-navbar-chrome.test.js (real Chrome: navbar, panels and boost over six laps, measured like
5096 browser-check; no node or listener growth, no detached bar); panels.test.js and navbar-brand.test.js cover the
5097 signal lifecycle.
5098- The navbar's notes on `history`, `silentLogin`, `fedcm` and `fedcmLogin` moved to the README (navbar.js 28.8 KB
5099 brotli, budget 29 KB).
5100
5101## 2.13.3 — 2026-10-08
5102
5103- The navbar removes the document listeners of its previous render (the outside-click closers of the launcher, the
5104 dropdowns, the drawer and the account menu) with an AbortController per render. Every re-render — each
5105 `openvibe-shared/boost` page move re-renders the bar — had left them on document, keeping the removed bar and its
5106 listeners alive: ovhost browser-check measured +25–40 listeners and +250–1100 nodes per navigation lap on
5107 openvibe.space and openvibe.community (2026-10-08).
5108
5109## 2.13.2 — 2026-10-08
5110
5111- The navbar loads `ov-mark.js` only when no `<script src=".../ov-mark.js">` is on the page yet: a page carrying its own async
5112 tag that had not run when the navbar rendered got a second copy (ovhost browser-check "script requested twice" on
5113 openvibe.coupons, .reviews, .services, .vip and openre.stream, 2026-10-08).
5114
5115## 2.13.1 — 2026-10-08
5116
5117- `showcase` code samples are a focus stop with a name (`<pre tabindex="0" aria-label="…">`): a sample that scrolls
5118 sideways could not be reached from the keyboard (axe `scrollable-region-focusable`, serious; ovhost browser-check
5119 on ai.openvibe.services and openvibe.events, 2026-10-08).
5120
5121- `scripts/pin-bump.js` (plan T1, the pin bot): after a release of openvibe-contracts, -shared, -sdk or -publishing,
5122 `node scripts/pin-bump.js <library> <tag> [--repos A,B] [--apply]` opens one PR per OpenVibers repository that pins
5123 the library below the tag: every pin moved, the npm or pnpm lockfile refreshed, STATUS.json and the README versions
5124 block rewritten by `scripts/docs-status.js`. A dry run by default; CI tests each PR (full suite and pin drift).
5125
5126## 2.13.0 — 2026-10-07
5127
5128- `release-watch` now connects to `https://openvibe.events` by default; the notification bell follows the stream URL in Network's ticket. A site pinning this release must allow `connect-src https://openvibe.events` in its CSP.
5129
5130## 2.12.0 — 2026-10-07
5131
5132- `showcase`: the sections (`.sc-hero`, `.sc-sec`, `.sc-cta`) set their own font stack (`--ov-font`, else Inter and
5133 the system sans-serif fonts). `shell.page` adds no base stylesheet, so a home built from the shell and the
5134 showcase rendered in the browser's default serif face (OpenVibe.Events, Reviews, Deals, Trade, Host).
5135- `footer`: the Frame footer sets the same font stack on `.ovf`, as the navbar already does, so it no longer
5136 inherits a serif face on pages without a body font.
5137- `app-icon`: `space` (indigo, OpenVibe.Space) and `ai` (cyan, OpenVibe.AI) tints.
5138- `navbar`: the brand spells `openvibe.space`, `openvibe.services`, `openvibe.events` and `openvibe.bot` with their
5139 names (Space, Services, Events, Bot), and the `space` service falls back to the Space brand off the network.
5140
5141## 2.11.0 — 2026-10-07
5142
5143- `ov-icons`: a `bot` glyph (a robot head with its antenna, eyes and side lights, in the family's 24-unit stroke
5144 style), centred by hand (`OFFSETS.bot` `[0, 0.75]`: its bounds run from y 3.5 to 19) so no other icon's
5145 measured offset moves. OpenVibe.Bot's site block (openvibe-contracts 0.106.0) names the icon `bot`; until a
5146 site pins this release its navigation shows the OV mark for Bot.
5147
5148## 2.10.1 — 2026-10-05
5149
5150- `openvibe-shared/shipped`: OpenVibe.Bot is a known site (`bot` → "Bot"), so its changelog lines and filter chip
5151 read "Bot" rather than the raw id, and `?site=bot` / `openvibe.bot` resolve to it.
5152
5153## 2.10.0 — 2026-10-05
5154
5155- `openvibe-shared/showcase`: `nextSteps({ id, title, lede, items })` renders up to four next steps on other OpenVibe
5156 products (Shared ADR 0002, plan D94, `docs/adr/0002-traffic-between-products.md`). An item is
5157 `{ to, what, text?, href, from?, embed? }`: `to` picks the ring icon and the "on OpenVibe.X" label, `from` appends
5158 the referral tag `ov_from=<from>` (nothing about the person), `embed` adds an "Embed it" link. Only `https://` URLs
5159 or site paths are rendered; an unusable item is left out and no items render ''. Styled in `showcase.css`.
5160- `docs/adr/0002-traffic-between-products.md` (proposed): next-step cards, shareable result pages, embeds and badges
5161 (live-rendered SVG + a proof page that re-checks the fact), and quests.
5162## 2.9.0 — 2026-10-04
5163
5164- `openvibe-shared/showcase`: a feature card's ring icon (`icon: 'ov:…'`) now fills the 40 px icon slot
5165 (`--ovi-size: 40px`, class `sc-ic-ov`, no second tinted tile behind it). Before, the ring drew at 22 px
5166 inside the tile, so its glyph read at about 10 px. Font Awesome icons keep the tinted tile.
5167- `scripts/docs-status.js` (new; plan D41 docs currency): reads a repository's openvibe-contracts/sdk/
5168 shared/publishing codeload pins and keeps STATUS.json's `contracts`/`sdk`/`shared`/`updated` fields
5169 and the README `<!-- versions:start -->`/`<!-- versions:end -->` block in step with them, preserving
5170 every other STATUS key (and any prose after a version). `--check` reports drift and exits 1; a
5171 repository that pins nothing omits the block. In write mode `updated` is rewritten only when one of
5172 those fields really changed, so a rerun with no pin change touches nothing.
5173- The reusable test workflow's "docs currency" step is **off by default** (`docs-status: false`):
5174 consumer repositories do not have the README versions block yet, so a default-on check would fail
5175 their CI. A repository turns it on with `docs-status: true` once its README carries the
5176 `<!-- versions:start -->` block and its STATUS.json `contracts`/`sdk`/`shared` fields match its pins.
5177
5178## 2.8.0 — 2026-10-04
5179
5180- `openvibe-shared/test-runner`: `parallel: 'auto'` resolves to `max(1, min(8, cpus - 1))` (before, any
5181 non-number made the runner start nothing); a numeric pin works as before and no option keeps the
5182 `min(4, cpus - 1)` default. `run()` also returns the resolved `parallel`, and `resolveParallel()` is
5183 exported. Each test file runs in its own process and owns its database (`createTestDb`: its own PGlite,
5184 or under `test:pg` its own PostgreSQL schema, roles and port), so files parallelize safely and a repo
5185 no longer needs `parallel: 1`.
5186
5187## 2.7.0 — 2026-10-04
5188
5189- `openvibe-shared/notifications`: a new `CONFIRMATION_REQUESTED` type (category `system`, priority
5190 `high`, icon 🔑, title "Approval needed"). OpenVibe.Network sends it to an agent's owner when a
5191 confirmation for that agent's sensitive capability use is created and waits for them.
5192
5193## 2.6.0 — 2026-10-03
5194
5195- `openvibe-shared/shell` (new): `page(o)` composes a whole server-rendered document — the doctype
5196 and `<head>` from `seo.headTags` (plus `seo.pageSummary` when `summary` is given), the theme-loader
5197 before the first paint, `web-runtime.js`/`navbar.js`/`footer.js` deferred through `serve.url`, the
5198 no-JavaScript nav from `frame.noscriptNav`, the body and `frame.footer`. `scripts(o)` returns just
5199 the head scripts and the inline boot: the runtime options as JSON with `<` escaped, so a value can
5200 never close the `<script>`. Every interpolated value (title, description, `lang`, body attributes)
5201 is escaped. `templates/site/server/app.js` now calls `shell.page` instead of hand-writing the
5202 document. Additive: a new subpath export, nothing removed.
5203
5204## 2.5.0 — 2026-10-02
5205
5206- `seo.feedXml(feed, { format })` takes items as `{ title, link, guid?, description?, content?, author?,
5207 published, updated? }` and the feed's `language` and `selfUrl`. RSS adds `<language>`, an
5208 `atom:link rel="self"` and a `lastBuildDate` that is always present. Atom adds
5209 `link rel="alternate"`/`rel="self"`, `xml:lang`, `<content type="html">` and an `<updated>` that is
5210 always present. Without `updated`, both use the newest item date, or now when there are no items.
5211 Text is escaped with `esc`, so `'` becomes `&#39;`. The 2.4.0 `url`, `id` and `summary` still work.
5212- `seo.llmsFull` takes `base` and sections of `pages: [{ title, url, text | html }]`: each page
5213 renders as `### title`, `URL: …` and its full text (HTML stripped), in input order, and
5214 `maxBytes` ends the file with a `(truncated: N more pages at <base>/llms.txt)` line. The
5215 2.4.0 `{ url, body }` sections render unchanged. `seo.pageSummary` now converts to an HTML
5216 string — `<meta name="ai-summary">`, WebPage JSON-LD and, with string `facts`, a
5217 `<noscript><section data-ai-summary>` block — and has `.head`/`.body`; its `meta`, `jsonLd` and
5218 `html` are unchanged. `templates/site` serves `/llms-full.txt` and puts the summary in its head.
5219- `perf-budget` measures Core Web Vitals (plan T11). `measure({ browser })` opens the page in
5220 Chrome through `browser-harness` after the byte pass, clicks once, and adds
5221 `cwv: { lcpMs, inpMs, cls }`. `check()` accepts `lcpMs`, `inpMs` and `cls` budgets and throws
5222 when they are used without `cwv`; `format()` prints a `cwv:` line. Without `browser`,
5223 measurement is unchanged and needs no Chrome.
5224
5225- The asset pipeline adds `hashAsset(bytes)`, generated AVIF and WebP srcsets from
5226 `picture({ src, widths, ... })`, and `openvibe-build-assets` to copy files to
5227 hashed names with a `manifest.json` lookup. The builder does not transcode images.
5228
5229- `openvibe-shared/assets` (plan T11): content-addressed asset URLs and responsive `<picture>` markup for
5230 a product page. `hash(bytes)` is the estate's sha256 truncated to 12 hex; `url(src, hash)` appends
5231 `?v=`, replaces an old one and leaves the path unchanged when there is no hash; `srcset(entries)` builds
5232 a srcset; `picture({ src, hash?, alt, width, height, sizes, sources })` emits AVIF before WebP before
5233 the fallback `<img>` (any other type ranks after WebP), renders a plain `<img>` when there is nothing to
5234 choose between, and renders nothing without a `src`. Formats come from the caller: this package does not
5235 transcode images, so it only emits the AVIF/WebP variants a site's own build produced.
5236
5237## 2.4.0 — 2026-10-02
5238
5239- The SEO kit adds `llmsFull` for full-text AI maps, `pageSummary` for escaped page
5240 summaries and WebPage data, `feedXml` for RSS 2.0 and Atom 1.0, and `feedLinkTags`.
5241 Product, review, rating, image gallery and VideoObject JSON-LD builders are available
5242 through `jsonLd`. The package root now exposes the kit as `seo`.
5243
5244## 2.3.1 — 2026-09-29
5245
5246- `showcase.css` is served at `/shared/showcase.css` (files.js `STYLES`, text/css): the showcase sections and the ring
5247 icons' CSS as one cacheable stylesheet, so a product home links it instead of inlining ~10 KB of CSS in every page
5248 (and boost loads it styles-first on a swap). Generated by `scripts/build-styles.js`; the test fails when it is stale.
5249 `serve.js` answers each file with its own content type.
5250
5251## 2.3.0 — 2026-09-29
5252
5253- `openvibe-shared/showcase` (plan T11, D92): the sections a product's home page is built from — `hero`, `features`,
5254 `steps`, `code`, `demo`, `compare`, `pricing`, `limits` (pricing that equals the API's limits, citing its source),
5255 `stories`, `cta` — rendered on the server as plain HTML with inline `CSS` on the theme tokens. Every string is
5256 escaped (only a field named `html` passes markup through), a section with no items renders nothing, and heading ids
5257 come from titles so a page renders the same bytes every time.
5258
5259## 2.2.0 — 2026-09-29
5260
5261Boost on real sites (plan T11):
5262- `boost.js` starts itself from its script tag: `<script src="/shared/boost.js" data-main="#main" defer>` (optional
5263 `data-prefix`, `data-exclude="/a/,/b/"`); the controller is `OVBoost.controller`. `OVBoost.start()` still works.
5264- A swap also carries the new page's `<body>` class (page-type classes).
5265- `navbar.js` follows boost: on `ov:boost:load` the bar re-renders (active links, a sign-in that returns to the new
5266 page) and records the new page in the signed-in person's history.
5267
5268## 2.1.0 — 2026-09-29
5269
5270Three new modules for plan T11, plus `serve.js` now taking its cache headers from the shared policy (its own headers are
5271unchanged):
5272- `openvibe-shared/cache-policy` — `isHashed(path)`, `assetHeaders(path, { hashed })` (immutable for a content-addressed
5273 asset, otherwise `public, max-age=300, stale-while-revalidate=86400`), `htmlHeaders({ private, maxAge, swr })` and an
5274 `applyHtml()` middleware, so "hashed asset = immutable, HTML = stale-while-revalidate" is one rule instead of one
5275 per site.
5276- `openvibe-shared/indexnow` — `createIndexNow({ host, key, keyLocation?, endpoint?, fetch, log })` → `keyFile`,
5277 `ping(urls)` (one POST, deduped, https-only, own host, ≤10,000 URLs, never throws) and `pingSoon(urls)` (one
5278 debounced batch in 30 s). No key means no request.
5279- `boost.js` (`OVBoost.start({ main })`, served at `/shared/boost.js`) — smooth same-site page moves for a server-rendered
5280 site: a click fetches the next page (prefetched on hover, focus or touch), loads its new stylesheets first, swaps
5281 `<main>` in place with the shared transition, updates the title and the head tags that describe the page, runs the new
5282 main's inline scripts once, keeps history and scroll, and moves focus to the content. Any doubt (another release
5283 marker `<meta name="ov-boost">`, other external scripts, a non-HTML answer, a redirect off the site, an error) is a
5284 normal page load. Pages listen for `ov:boost:load` to wire their widgets.
5285
5286## 2.0.0 — 2026-09-29
5287
5288**X-Internal-Key is gone from the shared package** (plan T2: every internal call in the estate uses a service token).
5289Breaking, and nothing in the estate used any of it:
5290- `internalApiAuth` (the `middleware` export and the index re-export) is removed.
5291- `OpenVibeAuthClient#verifyTokenInternal` (Network's `/internal/verify-token`, deleted) and `#getUserProfile` (a route
5292 Network never had) are removed; the rest of the client is unchanged.
5293- The deprecated `openvibe-shared/chrome-ssr` alias is removed: use `openvibe-shared/frame`.
5294
5295## 1.30.1 — 2026-09-28
5296
5297Page moves, measured on Live with a 400 ms network: the bar runs from the click, "Loading Content…" appears at 150 ms and follows the phase ("starting up"), the styled page fades in, and nothing shows unstyled in between. Fixes to 1.30.0: the first move after a server-rendered page is animated (the "first page" is the first `enter()` before any route change, route generation ≤ 1, not simply the first call); `route-transition.js` is fetched a few seconds after load, so it is ready for the first click; the loading line sits over the page's visible area but outside the section (a page script that re-renders its section while loading can no longer take it away); the file is requested by path, so the site's versions map hashes it.
5298
5299## 1.30.0 — 2026-09-28
5300
5301**Pages never show unstyled, and moving between them looks finished.** `web-runtime.js`: a feature's stylesheets (its dependencies' too, `rt.stylesOf(name)`) start downloading together with its markup fragment, and the fragment is inserted only once they have loaded (or timed out), so a route no longer flashes unstyled markup for a moment. New `rt.enter(section, work, { label })`: the page's content stays hidden while its route loads, keeping its space (no layout jump), and is shown when the work settles, failure included. On the first in-site move the runtime fetches the new **`route-transition.js`** from next to itself (a browser file in `files.js`, export `openvibe-shared/route-transition`): a progress bar across the top, after 150 ms a centred "Loading Content…" line with the current phase under it (`<prefix>:phase` events: "applying styles", "starting up", or the site's own `rt.status(text)`), then an opacity-only fade-in (a transform would make the page the containing block of its fixed children). Theme tokens colour it; reduced motion gets none of the animation. The first page of a visit is never animated (it arrived server-rendered). The runtime's usage notes moved to the README ("The web runtime"), keeping it at 6.2 KB brotli (budget 6.5); the transition layer is 1.6 KB and loads only when someone moves.
5302
5303## 1.29.2 — 2026-09-28
5304
5305**Navbar dropdowns open again.** A top link's dropdown (Live's "Go Live" → Dashboard) opened but was cut off: it hangs below the links row, which scrolls sideways (`overflow-x: auto`) and so clips anything below it. The menu is now fixed-positioned under its link whenever it can show (hover, keyboard focus, a tap), measured from the box a filtered or transformed bar makes for fixed children. The usage notes at the top of `navbar.js` moved to the README ("The navbar"), which keeps the file inside its 29 KB brotli budget (28.7 KB).
5306
5307## 1.29.1 — 2026-09-28
5308
5309**Themes repaint the whole page and keep button text readable.** `applyVars` sets the canvas through the variables (`var(--bg-primary, value)`), so a page with its own theme switcher that sets `--bg-primary` later repaints the background too, instead of leaving the first theme's colour behind; `save()` applies the theme it saves, so every token, the canvas and the browser UI follow at once. A theme without its own `--on-accent` (custom colours, older server themes) gets one derived by WCAG relative luminance, near-black on bright accents (a lime green) and white on dark ones; `OpenVibeThemeLoader.onColor(hex)` is exported for pages that paint their own accent surfaces. **Navbar brand, compact:** the mark and the name sit 6px apart with 2px padding, the ".Live" part hugs the name, and the bar's side padding follows the viewport (`clamp(8px, 1.6vw, 16px)`), so narrow screens keep more room for links.
5310
5311## 1.29.0 — 2026-09-28
5312
5313**`openvibe-shared/analytics/pg`: request analytics on PostgreSQL** (ADR-035; ADR-021's privacy bounds unchanged). `AnalyticsTrackerPg` is the tracker on an openvibe-sdk/db handle, with the same tables and `getStats`, `getOverview` and `getBotAnalysis` shapes; `analyticsSchema()` is the DDL for the service's migrations; `pruneRawEventsPg` is the 30-day retention in batches. The request path never waits for the database: rows buffer in memory and `flush()` writes them in one transaction every 5 s (or at 100). Unique visitors stay right across processes: the day's salt is shared through `analytics_day_salts`, so the visitor hash is computed at flush, while (address, user agent) wait in memory until then, as the rate counters always did; no address reaches the database. Session ids stay per process, as they were. Every SUM is cast to bigint (numeric comes back as text). `test/analytics-pg.test.js` on PGlite: two trackers count one visitor once, DNT records nothing, rollups, dashboards and retention. Needed by Network's and Live's PostgreSQL passes.
5314
5315## 1.28.1 — 2026-09-28
5316
5317Fixes the owner reported from phones.
5318- **Account panel:** the panel scrolls as a whole and its sections keep their height. The panel is a flex column, which let its sections shrink, so on a height-limited phone screen the accounts block ("Switch to Anonymous", "Add another account") collapsed into a sliver with its own scrollbar. The account rows now have the menu rows' size, weight and icon width.
5319- **OpenVibe.Codes mark:** a mark at rest (every page after the first of a visit) paused the codes variant's looping V animation at its first frame, where the V is not drawn yet, so the logo showed only the ring and the dot. At rest the V is now fully drawn.
5320- **Footer:** phones no longer reserve 84–96 px of empty space under the footer. A page with a fixed bottom bar sets `--ov-footer-clearance` to its height; the default is none.
5321
5322## 1.28.0 — 2026-09-28
5323
5324**`openvibe-shared/config` on PostgreSQL** (ADR-035: Media, Network and Live keep their configuration here). Given an openvibe-sdk/db handle, `createConfigStore` uses the new `config-pg.js`, which is the same store on the async handle. Revisions are immutable, activation is atomic, the last-known-good revision is restored when `onActivate` fails, secret values are redacted with a per-namespace fingerprint key, legacy values are imported, and so on. On this path `createConfigStore` returns a promise of the store: it has read, restored or seeded its active revision when the promise resolves. `get()` and `revision()` stay in memory. The other methods are async. The tables come from the service's migrations: `configSchema()` is their DDL, the SQLite schema converted. `adminRoutes` awaits either kind of store. The better-sqlite3 path is unchanged. `test/config-pg.test.js` runs every scenario of `test/config.test.js` on PGlite (openvibe-sdk v0.20.4, which returns `bytea` as a Buffer, is a dev dependency).
5325
5326## 1.27.0 — 2026-09-28
5327
5328**`openvibe-shared/express-async`: an async handler's rejection no longer ends the process.** Express 4 ignores what a handler returns, so a promise that an `async (req, res) => …` handler rejects (a database error, a thrown problem) became an unhandled rejection, and Node 22 exits on one. The move to PostgreSQL (ADR-035) made most handlers async; nine services had between 5 and 41 such routes. `routeAsyncErrors(app)` patches the app's own Express 4 `Layer` once, so the rejection goes to `next(err)` and the service's error handler answers, exactly as a synchronous throw always did. Error handlers get the same treatment. On Express 5, which handles promises itself, it does nothing. **`instrument()` calls it**, and every service calls `instrument()`, so a pin bump is the whole change. `test/express-async.test.js`.
5329
5330## 1.26.0 — 2026-09-28
5331
5332**Async gauges** (ADR-035: services on PostgreSQL read their counts asynchronously). A gauge's `collect()` may return a promise. The `/metrics` handler (`metricsHandler`, and the `instrument()` middleware that uses it) now calls the new `registry.metricsAsync({ timeoutMs = 2000 })`. It awaits every async collect together. A collect that rejects or runs past its budget is left out of that scrape and is never reported stale, just as a throwing one already was. The synchronous `registry.metrics()` is unchanged, and it leaves an async gauge out. The handler is now an async function; Express and plain `http` callers need no change. `test/metrics.test.js` covers async, rejecting and slow collects. This replaces the "one scrape behind" workaround the first PostgreSQL services used.
5333
5334## 1.25.0 — 2026-09-28
5335
5336**`openvibe-shared/test-runner`: no fake green in `npm test`** (roadmap WS-Q task 7). Every service carried its own copy of `test/run.js`, and each counted a test file that exited 0 as passed, including one that printed `…: skipped (no ffmpeg)` and checked nothing. The runner is now one module: `require('openvibe-shared/test-runner').main({ dir: __dirname, timeoutMs, pad, hide, env })` (`run()` returns the results for a caller that wants them).
5337- Same behaviour as the copies: every `*.test.js` in `dir` in its own process, a few at a time, `NODE_ENV=test`, a timeout per file, name filters from the arguments, and the last 40 lines of a failed file (minus `hide` lines).
5338- **Skips.** A test prints `<label>: skipped (<why>)` for each thing it could not run (the convention OpenVibe.Host's acceptance runner already reads). Such a file is listed with ○ and its reasons, and is not counted as passed: the summary reads `148/150 test files passed, 2 skipped (…)`, and only a run with nothing skipped says `N/N test files passed`. Skips alone exit 0; `--strict` (or `OV_TEST_STRICT=1`) makes them fail the run.
5339- This package's `test/run.js` uses it; `test/test-runner.test.js` covers pass, skip, partial skip, failure, hidden lines, filters and strict.
5340
5341## 1.24.0 — 2026-09-28
5342
5343**No fake green in readiness** (roadmap WS-Q task 7: "skipped counts as yellow; nothing may be marked green by being synthesised from skipped parts"). `ready.js` had only `ok` and `fail`, so a check whose feature was not configured had to claim `ok` (Network's Discord bot, Media's events outbox and News's source pull did) or fail and turn the service yellow for a setting.
5344- **`skip(reason, detail?)`**, exported by `openvibe-shared/ready`, or a check returning `{ skipped: reason }`: the check reports status `skipped` with `reason` (and `detail`), never `ok`, and is listed in the new `skipped` array of the body.
5345- A skipped **optional** check leaves a service whose other checks passed `ready`. A skipped **required** check is listed in `degraded` too, and the service is `degraded` (still HTTP 200: nothing failed).
5346- A service is `ready` only when at least one check really passed: one with no checks, or with every check skipped, is `degraded`. This changes what a service with no checks reports (before: `ready`).
5347
5348## 1.23.1 — 2026-09-27
5349
5350**Two release-watch fixes found by the release-lifecycle acceptance scenarios** (D46, roadmap WS-P task 16; OpenVibe.Host `scripts/release-acceptance.js`). release-watch.js stays within its 5 KB brotli budget (4.99 KB; comments shortened).
5351- **An account switch during an update** (scenario 9). An in-place update fetches its regions with the session (`credentials: 'same-origin'`), so a region can be rendered for whoever is signed in. On sites that switch accounts in place (Live: the `openvibe-auth-changed` window event), a region fetched for the previous account and still waiting for focus to leave it, or still in flight when the account changed, was committed as fetched: the new account (or a guest, after signing out) saw what the server had rendered for the previous one. Now `openvibe-auth-changed` drops such regions and fetches them again with the new session, and an answer that arrives after a switch is never committed (counted as `deferred: account`, a new reason `/release-metrics` accepts). The switch itself still reloads nothing, prompts nothing and opens no second stream.
5352- **Resuming an offline tab** (scenario 14). Back online, the release stream reconnected at once only when it had given up (6 failures); a tab that came back while still backing off waited out the backoff (up to 8 minutes) before hearing release notifications again. `online` now reconnects at once from the cursor in both cases, like `notification-live.js` does.
5353
5354New tests: `test/release-account-switch.test.js` and `test/release-offline-resume.test.js` (offline: no prompt, no reload, nothing counted as failed; back online: one `/release.json` read straight to the newest release, in place; a connection lost mid-update leaves the page whole and prompts once; past the window, never reloaded under a focused field; a hidden tab resumes its stream when shown). And `test/release-reading.test.js` (scenario 4, a feed update while reading: a region above the viewport that grows or shrinks moves the page by exactly that, the region keeps its own scroll, a region in view is not compensated; nothing to fix). All three print `[metric] name=value` lines the acceptance runner reads.
5355
5356## 1.23.0 — 2026-09-27
5357
5358**Web runtime** (roadmap WS-P task 6). New browser file `web-runtime.js` (`OVWebRuntime.create(options)`), in `files.js` and exported as `openvibe-shared/web-runtime`: OpenVibe.Live's feature loader (`public/js/ov-loader.js`), made shareable and extended. 6.1 KB brotli, budget 6.5 KB.
5359- **Carried over from Live.** Features from a registry (fragment, deps, css, js, `after` hook, `stubs`, `idle`) and routes; scripts and stylesheets once each, the server's own tags included; versioned URLs; route generations; route scopes (timers, listeners, observers, AbortSignal); intent and idle prefetch; stubs for inline handlers; a retry box for a route that failed.
5360- **Transactional asset groups.** A feature commits (hook, event, loaded) only when every script loaded. A failure withdraws the stylesheets that attempt added (unless another feature wants them) and the failed tag; a retry fetches only what is missing, and nothing runs twice.
5361- **Scopes refuse late registrations.** An interval, timeout, listener, observer, fetch or child scope registered after its route ended is refused and counted, not left running. `scope.fetch()` uses the route's signal; `scope.child()` scopes one component; `scope.held()` counts what a scope holds. Fired timeouts are released.
5362- **A prefetch budget**: a count, and bytes where sizes are known.
5363- **Diagnostics**: `diagnostics()` and `leaks()` (duplicate tags, failed and rolled-back assets, late registrations, what the current route holds).
5364- **Other changes.** `showRouteError` builds its box from DOM nodes. Assets from another origin are keyed by origin and path, so a CDN file never collides with a local one. The idle prefetch also starts when the page has already loaded.
5365
5366`test/web-runtime.test.js` covers it in a linkedom page where tags load or fail on cue. `test/web-runtime-chrome.test.js` covers it in headless Chrome against a local site: a slow script still runs in order, a real 404 rolls the group back and the retry completes it, and a route's interval stops and its fetch aborts.
5367
5368## 1.22.0 — 2026-09-26
5369
5370**The notification bell in realtime** (roadmap WS-E task 3, WS-F task 1; openvibe-contracts 0.61.0, ADR-005 amendment 2). It is off by default. A site turns it on with `OpenVibeNavbar.init({ notificationsRealtime: true })`, or with `OpenVibeNotifications.init({ realtime: true })`.
5371- **New browser file `notification-live.js`** (`OVNotificationLive.create({ ticketUrl, token, credentials, onNotification, onGap, onState })`), in `files.js`, 2.4 KB brotli (budget 3 KB). For every (re)connect it asks Network for a realtime ticket (`POST /api/v1/realtime/ticket`), then opens Events' `/realtime/stream?topics=network.notification.*&ticket=…` without credentials, resuming with `last_event_id`.
5372 - Only events whose subject is the ticket's person count; another person's, other types and replays are ignored.
5373 - `event: gap` is reported.
5374 - Errors close the stream (a ticket opens one stream) and back off from 2 s, with jitter. After ten failures in a row it stops, until the browser is `online` again.
5375 - Hidden 5 minutes, it closes; shown, it resumes.
5376 - A Content-Security-Policy refusal, 401/403 or 503 (`REALTIME_TICKETS=off` on Network) stop it.
5377- **`notification-ui.js`** loads it from its own directory when realtime is on. An event re-reads the count and toasts new items, once per burst, and again after a poll already in flight. A gap also reloads the open lists. The 15 s poll runs only every 2 min while the stream is open. `setToken()` restarts the feed for the new person, and `realtimeState()` reports it. notification-ui.js gets a 13 KB brotli budget (12.7 KB).
5378- **navbar.js** passes `notificationsRealtime` through.
5379
5380`test/notification-live.test.js` covers the above in a linkedom page: subscribe, resume from the cursor with a fresh ticket, a gap re-reading the count, CSP-blocked falling back to 15 s polling, a guessed `user:<other>` topic yielding nothing, and the ticket never being kept.
5381
5382`test/notification-live-chrome.test.js` runs it in headless Chrome, with a real EventSource, against a stand-in for the ticket route and Events' stream. A dropped connection resumes from the cursor with a fresh ticket, with nothing lost or repeated; a retention gap is reported; another person's event is ignored. It is skipped without Chrome. `browser-harness` now exports `openPage` for it.
5383
5384## 1.21.0 — 2026-09-26
5385
5386**Browser harness: an unreachable host** (ADR-024, roadmap WS-E task 1).
5387- `openPage(…, { block })` makes every request matching a pattern fail, as if the host were down.
5388- `checkUnreachable(url, { block, widths, minText })` loads a page that way (by default with `openvibe.network` unreachable, where the theme, the frame and sign-in live). It reports whether the page still paints: status 200, settled, real text, a `--accent` theme token and a background (the body's, or the html's when the body has none). The blocked requests themselves are not counted as errors.
5389- Tested in headless Chrome: a page whose theme stylesheet is blocked keeps its inline default.
5390
5391Host's `browser-check.js --network-down` runs it against every site.
5392
5393## 1.20.0 — 2026-09-26
5394
5395**Release manifest 1.2.0** (roadmap WS-P task 7; openvibe-contracts 0.60.0, ADR-016 amendment 2).
5396- `createRelease({ clientGeneration, minClientGeneration, shell })` serves `client_generation`, `min_client_generation` (also env `MIN_CLIENT_GENERATION`) and `shell: { version, components }`, where the version is the first 12 hex of SHA-256 over the named components' ids and versions.
5397- The meta tag carries `data-generation`.
5398- A tab below the minimum generation reloads when safe (`required`). A changed shell is never applied in place; it prompts.
5399- release-watch stays under its 5 KB brotli budget (4.94 KB) with a tighter header comment.
5400
5401The Contracts devDependency moves to 0.60.0.
5402
5403## 1.19.0 — 2026-09-26
5404
5405**The update matrix, complete** (roadmap WS-P task 8). `release-update.js` sends `ov:content-dispose` (bubbling, `{ component, release }`) on a region just before it is replaced, so a stateless widget inside can drop its listeners and timers; `ov:content-updated` follows as before, to mount it again. Tests now cover every row of the matrix: stylesheet swap and `ov:styles-updated`, revision-aware content with scroll kept, widget disposal and remount, deferral of busy regions, the prompt, and the security minimum. The README "Releases" section lists the rows.
5406
5407## 1.18.0 — 2026-09-26
5408
5409**Update telemetry** (roadmap WS-P task 14).
5410- **Prompts are counted.** `release-watch.js` records `prompted` when it shows the update prompt, with reason `optional`, or `required`, `window` or `contract` when a reload must follow. `release.js` accepts the new outcome into `release_client_updates_total`.
5411- **Sessions by generation.** Each tab sends a beat to the metrics URL once its manifest is read and then every 5 minutes, carrying a random 16-hex id kept in memory only and the release it runs. On `pagehide` it sends `ended`. The collector keeps the tabs heard from in the last 12 minutes, at most 20,000 per registry, and exposes the gauge `release_client_sessions{generation="current"|"older"}`. `current` is the release the process serves: `mount` and `collect` pass it, or pass `collect(registry, { currentRelease })` yourself. Sec-GPC/DNT requests are still not counted.
5412- **Size.** release-watch stays within its 5 KB brotli budget (4.96 KB); comments were shortened to make room.
5413
5414## 1.17.1 — 2026-09-26
5415
5416Fix: `release-watch.js` acts on a `host.release.published` event only when its `origin` (when present) is the page's own. A service id alone is not unique across the network: Sites' placeholder pages reuse product ids (`ai`, `coupons`, …), so a placeholder's release made the real product's tabs check for nothing.
5417
5418## 1.17.0 — 2026-09-26
5419
5420**Release notifications in `release-watch.js`** (roadmap WS-P task 9, ADR-016 amendment 1). When a release goes live, OpenVibe.Host publishes `host.release.published` to OpenVibe.Events (contract `host.release.published@1`, openvibe-contracts 0.58.0; `ovhost deploy` and `ovhost announce`). The event is public, has subject `release` and carries `service`, `release`, `commit` and `origin`. Tabs now hear about a release within seconds instead of at their next poll:
5421- **One stream per tab.** release-watch opens one EventSource, without credentials, on `https://events.openvibe.network/realtime/stream?topics=host.release.published`, once the page's release and service are known.
5422- **Which events count.** Only those for the page's service: `OVReleaseConfig.service`, the meta tag's `data-service`, else `/release.json`'s `service`.
5423- **Generations.** The tab ignores the release it runs, the one it already knows from `/release.json` and one already queued (a hex prefix of the other counts as the same release). It also ignores a repeated event id and a seq at or below the last one seen.
5424- **Coalescing.** A new release runs the usual `check(true)` after a random 0–20 s delay, so tabs do not all fetch at once. Everything arriving while that check is pending collapses into it, and after a check there are 30 s of quiet, after which whatever arrived meanwhile gets one more check. The update plan, the prompt and the safe-reload rules are unchanged.
5425- **Gaps.** An `event: gap` (events missed while away) also runs a coalesced check.
5426- **Account switches.** The events are public, so `openvibe-auth-changed` changes nothing, and a second load of the script still returns early, so there is never a second EventSource.
5427- **Hidden tabs.** A tab hidden for 5 minutes closes its stream: the poll and the check on becoming visible cover it, and it reopens on return with `last_event_id`.
5428- **Failures.** An error closes the stream at once, rather than letting the browser retry every 3 s, and it reopens after 30 s, doubling to 15 minutes, with jitter. After 6 failures in a row only polling is left until the browser comes back online.
5429- **Polling.** Unchanged throughout (focus, visibility, `online`, every 10 minutes). Without EventSource, or when Events cannot be reached, nothing else changes.
5430- **Configuration.** The default URL applies on https pages. `OVReleaseConfig.eventsUrl` or the meta tag's `data-events` sets another URL, and `false` or `"off"` turns it off. Events answers `https://*.openvibe.*` origins and those in its `REALTIME_CORS_ORIGINS`; elsewhere the stream fails and backs off.
5431- **CSP.** A site's Content-Security-Policy must allow the Events origin in `connect-src`, as Live and Community already do. Otherwise the browser refuses the stream and logs that refusal once. release-watch sees the `securitypolicyviolation` for its URL, stops (state `blocked`) and stays on polling. Add `https://events.openvibe.network` to `connect-src`, or set `data-events="off"`, when you move a site to this release.
5432- **State.** `OVRelease.state().realtime` reports `{ state, service, url, events, ignored, checks, failures, lastSeq }`.
5433
5434`release-watch.js` is 4.7 KB brotli (was 3.2 KB). Its budget in `test/release-update.test.js` moves from 3.5 KB to 5 KB for this.
5435
5436`release-compat` `openPage()` also takes `globals` (extra window properties, such as a fake `EventSource` or a `Math` with a fixed `random`). It adds `timeouts()` (the pending delays), `fireTimeouts(filter)`, `dispatch(type)` and `timeoutsSet`.
5437
5438Tests are in `test/release-watch-realtime.test.js`. Additive.
5439
5440## 1.16.1 — 2026-09-26
5441
5442Fix: the **OV mark** (`ov-mark.js`) no longer animates forever. Its SVG animations restyle and re-lay-out on the main thread every frame, which kept every idle page with the navbar 12–29% busy (the browser check counted 8–19 endless animations per page). A mark now moves for the intro (the first 8 s of a visit in a tab, from the first time a mark is on screen; the next page of a multi-page site finishes that intro rather than starting another), while it or its link is hovered or focused (plus 1.2 s), and while it shows activity (`data-state` busy/ok from `island.js`). Otherwise its CSS animations are paused where they are and its SMIL clock is stopped, so it keeps its look. It never moves while the page is hidden, while it is off-screen (IntersectionObserver), with `data-static`, under `prefers-reduced-motion` or under the network's own reduced-motion setting (`html[data-ov-motion=reduced]`); the V's light sweep, a SMIL animation, used to keep running in all of those. Measured in headless Chrome after five page loads: 12 running animations and 16.5% CPU before, none and 0% after. `test/ov-mark.test.js`. Sites pick it up with their `openvibe-shared` pin (the file is `/shared/ov-mark.js`); pages that load it from openvibe.network get it when Network's pin moves.
5443
5444## 1.16.0 — 2026-09-26
5445
5446**`openvibe-shared/browser-harness`** (roadmap WS-Q task 3, WS-T tasks 3 and 4): Live's browser smoke suite, generalised for any site. It drives headless Chrome over the DevTools protocol with Node's own WebSocket (Node 22+, no dependency), one browser context per route. `run({ base, routes, widths, checks, navigation, … })` checks every route at 390, 768 and 1280 px: the HTTP status (a route can expect 404), console errors (exceptions, `console.error`, failed loads, CSP), horizontal overflow (naming the elements) and scripts requested twice. With JavaScript off it checks the visible text (200 characters), exactly one absolute canonical link (a noindex page may have none), and that JSON-LD parses and its headline or name is in the visible text. For an ItemList, at least 80% of the item names must be visible. axe-core runs at the widest width: serious and critical violations fail, moderate is reported. Once per site, A → B → A navigation runs five times, clicking a link when there is one so a single-page app navigates in place. After a forced GC it samples heap, DOM nodes, listeners and documents (`Performance.getMetrics`), plus live intervals, pending timeouts and open WebSockets (a probe installed before page scripts). A measure fails when it grows past its budget from lap 2 to the last lap and is still growing (`BUDGETS`). CPU, requests and bytes over 5 idle seconds are reported, not failed. `ignoreErrors` rules are counted per label, never dropped silently. `format()` gives text or Markdown, and `summarize()` gives per-check counts. axe-core is not vendored: an installed `axe-core` is used, else 4.13.0 comes from jsDelivr, checked against a pinned sha384 (the same bytes as the npm tarball) and cached. `test/browser-harness-chrome.test.js` runs it in real Chrome against a fixture and skips without Chrome. Additive.
5447
5448Fix: the selected filter chip of the shared update log (`shipped.js`, every site's `/updates`) was white on `--accent` (3.67:1). It now uses `--accent-strong` / `--on-accent-strong` (4.5:1 or better in every theme), like the navbar's Sign In button. The harness found it.
5449
5450## 1.15.0 — 2026-09-26
5451
5452**`openvibe-shared/perf-budget`** (roadmap WS-T task 1): size budgets for a page's first load, measured from the running server with no browser. `measure({ base, path })` fetches the page and every same-origin script and stylesheet it names. It reports the files and their raw and brotli KB; cross-origin assets are listed as `external` and not weighed. `check(m, budgets)` lists the budgets exceeded, and `format()` is the failure message.
5453
5454## 1.14.0 — 2026-09-26
5455
5456**`openvibe-shared/config`, the configuration model** (roadmap WS-C task 7): `createConfigStore({ db, service, namespace, schema, validate, classify, defaults, legacy, onActivate })` keeps one namespace of a service's configuration as immutable revisions (`common.config-snapshot@1`) in the service's own better-sqlite3 database (`config_snapshots`; several namespaces per database). `get()` serves the active values from memory; `propose`/`activate`/`apply` validate (a built-in JSON Schema subset and/or `validate()`), classify every key (public, internal by default, secret) and activate in one transaction, and a throwing `onActivate` puts the last-known-good back in memory and in the database, rejects the new revision with the error and throws it. `rollback()` activates a copy of the previous good revision (or `to`) as a new revision; `history()`, `lastKnownGood()` and `summary()` are redacted: a secret is `{ redacted: true, fingerprint }`, an HMAC-SHA256 under a random 32-byte key made per namespace on first use (`config_keys`; never served, never logged), a snapshot's checksum covers the values as shown, and secret values never leave or reach the log. A marker sent back keeps the secret it stands for only when its fingerprint matches. Revision 1 comes from `defaults` or a legacy source (`legacy`, `import()`, `fromRows()` for typed key-value rows). `adminRoutes(stores, { requireAdmin })` serves `GET /api/admin/config`, `GET /api/admin/config/:namespace/history`, `POST /api/admin/config/:namespace` and `POST /api/admin/config/:namespace/rollback` with problem+json errors, on Express or plain http, without depending on either. No new dependency. Additive.
5457
5458## 1.13.0 — 2026-09-25
5459
5460Accessibility (roadmap WS-T task 3), from an axe survey of every site. The navbar's **Sign In** button read at 3.67:1 (white on #3b82f6) on nine sites: every theme now derives **`--accent-strong` / `--on-accent-strong`**, the accent deepened (under white) or lightened (under dark ink) just enough for 4.5:1, and the button uses them (`test/login-contrast.test.js` checks all 35 themes). The two tokens are in `theme-sync` and the loader's map. The **footer** reads `{ label, href }` items as `{ name, url }`, so a site passing the navbar's shape no longer renders empty links (Media and Games did). Additive.
5461
5462## 1.12.0 — 2026-09-24
5463
5464- **`openvibe-shared/serve`: a site serves its own pinned copy of the shared browser files.** Mount it with `app.use('/shared', serve.handler())` and reference files with `serve.url('navbar.js')`, which gives `/shared/navbar.js?v=<content hash>`. The current hash is cached immutably, anything else for five minutes. ETag is the hash, the files are CORS-open and cross-origin readable, and only the files `openvibe-shared/files` lists are served. A site's pin then decides what its pages run, and the Frame keeps working while openvibe.network is down.
5465- The navbar loads its lazy siblings from wherever it was itself loaded: `nav-icons.js`, `release-watch.js`, `ov-icons.js`, `panels.js`, `notification-ui.js`, `ov-mark.js`, `sso-client.js` and `history.js`. Before, several were always fetched from openvibe.network (or the `apiBase`); the network's copy is now only the fallback. The footer already loaded `shipped.js` this way.
5466
5467## 1.11.1 — 2026-09-24
5468
5469Fix: the footer's "shipped X ago" line uses the footer's own `service` (`live`, `wiki`, …). Before, it resolved the page's hostname, so a local or unmapped host showed the whole network. A site id that is not a registry id (a Tools satellite's `dev`) falls back to the hostname rather than to the whole network.
5470
5471## 1.11.0 — 2026-09-24
5472
5473**The OpenVibe Frame.** The shared navbar, footer and "shipped" views every OpenVibe site sits in were called the "chrome", which was borrowed jargon easily mistaken for the browser. They are now the Frame:
5474
5475- `openvibe-shared/frame` is the server module (formerly `openvibe-shared/chrome-ssr`, which stays as a deprecated alias until 2.0.0).
5476- The navbar and footer read `https://openvibe.network/api/frame` (Network keeps `/api/chrome` as an alias for older copies) and send their page-view beacon to `/api/frame/hit`.
5477- They share `window.OpenVibeFrame`; `OpenVibeChrome` points at the same object.
5478- The browser cache key is `ov_frame_v1`.
5479
5480No behaviour change.
5481
5482## 1.10.0 — 2026-09-24
5483
5484`openvibe-shared/chrome-ssr` gains the server-side pieces of the shared update system, so every site renders the same markup instead of its own copy:
5485
5486- `shipped({ service, updates, title, limit })`: the home page block (the pill and the recent changes), with a plain link without JavaScript.
5487- `updatesBody({ service, siteName })`: the body of a site's `/updates` page. Without JavaScript it falls back to openvibe.network's server-rendered log.
5488- `shippedScript()`: the script tag.
5489
5490`shipped.js` styles both. Additive.
5491
5492## 1.9.0 — 2026-09-24
5493
5494- **One "shipped" and update-log system for every site** (`shipped.js`). There are three views: `latest` (the "🚀 shipped 5m ago: …" pill), `list` (the recent changes plus the latest Patch notes link and an "All updates" link) and `log` (a full `/updates` page: changes grouped by day, "Load more" by cursor, this site or the whole network with a filter per site, and the Patch notes posts). Views mount from markup, `data-ov-shipped="latest|list|log"` with `data-service`, `data-limit`, `data-more` and `data-href`, when the script loads, or from code with `latest()`, `list()` (`mount()`), `log()` and `scan()`. The feed is now `https://openvibe.network/api/v1/changelog`, a cached proxy of OpenVibe.Blog's changelog that every site's CSP already allows. One request is shared per page; the "ago" text stays current; `serviceFor(host)` maps hostnames to registry ids.
5495- **Footer.** Every footer carries the "shipped X ago" line and an **Updates** link. The link goes to the site's own log (`updates: '/updates'`) or, by default, `openvibe.network/updates?site=<host>`. The footer loads `shipped.js` lazily from its own origin; turn it off with `shipped: false`. `detectService()` now knows the TLD sites (`openvibe.wiki` → `wiki`, `openre.stream` → `openre`).
5496- **Fix: an empty bordered bar in the footer.** The signed-in row's `display:flex` beat its `hidden` attribute on sites without a global `[hidden]` rule.
5497- **Fix: "Sign In" shown to signed-in people on sites with their own session** (openvibe.blog and the other publishing sites). When a stored token is stale or refused, the navbar now drops it and asks the site's `sessionUrl` instead of giving up. The navbar also takes a new `logoutUrl` (for example `/auth/logout?next={path}`), so Sign out ends the site's server-side session too. `loginUrl` and `logoutUrl` accept `{url}` (the full return URL) and `{path}` (its local path).
5498
5499Everything is additive.
5500
5501## 1.8.0 — 2026-09-24
5502
5503`shipped.js` (browser file, also `openvibe-shared/shipped`): "Recently shipped" for any OpenVibe site.
5504`OpenVibeShipped.mount(el, { service, limit, title })` reads the network changelog OpenVibe.Blog keeps
5505(`GET https://openvibe.blog/api/v1/changelog`): what each site deployed, newest first, each line
5506linked to its commit, and a link to the latest "Patch notes" post that gathers a batch of changes.
5507Without `service` it shows the whole network with site labels. DOM nodes only (commit text is never
5508parsed as HTML), https links only, hidden when there is nothing or the fetch fails; styles follow
5509the theme variables. Additive.
5510
5511## 1.7.0 — 2026-09-24
5512
5513`openvibe-shared/trace` (Track O): one W3C trace across the services a request touches.
5514`trace.install(app)` (right after openvibe-contracts' `http.middleware()`) runs each request inside
5515an AsyncLocalStorage holding its trace and request ids, and wraps `fetch()` once so a call to a
5516loopback address or an OpenVibe host carries `traceparent` (the request's trace id, a new span) and
5517`X-OpenVibe-Request-Id`, unless it sets its own. Calls to anyone else (payment and AI providers,
5518user-chosen URLs) are untouched, so trace ids never leave the network; outside a request nothing is
5519added. Also `trace.current()`, `trace.outboundHeaders()`, `trace.run(ctx, fn)`. Additive.
5520
5521## 1.6.0 — 2026-09-24
5522
5523`openvibe-shared/egress`: the one rule for connecting to an address someone else chose. Live
5524(`server/net/egress.js`), Events (`server/egress.js`) and Tools (`apps/_shared/egress.js`) each had a
5525copy and they had drifted: Live allowed the 3fff::/20 documentation range and 5f00::/16, and only knew
5526the /96 NAT64 prefix. This module is the strictest union: `isPublicAddress`, `embeddedV4`, `expandV6`,
5527`normalizeHost`, `isInternalName`, a connect-time `safeLookup` (every DNS answer must be public, so there
5528is no rebinding window), `createSafeLookup` for tests, `assertPublicUrl` and `EgressDenied`. Node only;
5529the three services keep their own fetch, delivery and throttle wrappers on top of it.
5530
5531Fixed: `release.collect()` counted client beacons per socket address, which behind the proxy is
5532127.0.0.1 for everyone, so one 30-a-minute bucket refused the whole site. It now keys by `req.ip`
5533(the app's trust-proxy view), or `keyOf(req)` when given.
5534
5535## 1.5.1 — 2026-09-23
5536
5537Touch and keyboard feedback in the shared chrome (owner feedback: phones flashed a theme-blind blue
5538box over anything tapped). The navbar, its dropdowns, the launcher, the drawer and the footer turn
5539off `-webkit-tap-highlight-color`, show a slight press (`scale`, which composes with existing
5540transforms) and give keyboard focus one ring in the theme accent. Rules are wrapped in `:where()`
5541so they carry zero specificity: the chrome's own `:focus-visible` rules and host pages still win.
5542With reduced motion the press is a brief dim instead of a shrink.
5543
5544## 1.5.0 — 2026-09-23
5545
5546Roadmap Track R (D42–D46, ADR-016): release manifests with components, open tabs updated in place,
5547update metrics, and a mixed-version test harness. Additive; a service that changes nothing keeps
5548serving exactly the manifest it served on 1.4.0.
5549
5550- **`/release.json` carries the registry.release-manifest 1.1.0 fields** (openvibe-contracts
5551 v0.31.0): `components` (`{ id: { kind: style|content|script|server, version } }`), `assets` (logical
5552 path → content-addressed URL, component, sha384 integrity), `schema_generation`,
5553 `schema_compatible_from`, `contract_ranges` (`{ id: { version, accepts: ">=a.b.c <x.y.z", role? } }`)
5554 and `metrics_url`. `createRelease()` takes `components` (`assets` under `publicDir`, `files` under
5555 `root`; versions are content hashes), `contracts` (`^`, `~`, `x`, `>=` ranges are normalised),
5556 `schemaGeneration`/`schemaCompatibleFrom` (numbers or functions), `assetUrl`, `recheckMs` and
5557 `metricsPath`. Without a declared `shell` component its version is the release, so every release
5558 still reloads. The package versions are part of the shell's hash, so a Shared bump never applies
5559 in place.
5560- **The contract filter.** The served manifest keeps only the fields the service's installed
5561 openvibe-contracts release-manifest schema declares. Under openvibe-contracts ≤ 0.30.x that is
5562 the 1.0.0 set, so `/release.json` keeps validating. `full()` is the unfiltered manifest,
5563 `fields()` what is served, `validate()` checks the served one with the service's contracts, and
5564 `refresh()` re-reads git, env and files.
5565- **`release.mount(app, { registry })`** mounts GET `/release.json` and POST `/release-metrics`.
5566 Tabs' reports go into `release_client_updates_total{outcome,reason}` in the service's
5567 `openvibe-shared/metrics` registry, and so into `/metrics`. Outcomes are `applied` (by the kinds
5568 applied), `reloaded` (`user`, `required`, `window`, `contract`), `deferred` (`typing`, `dirty`,
5569 `protected`, `media`, `capture`, `active`) and `failed` (`style`, `style-timeout`, `content`,
5570 `origin`, `script`). An unknown reason counts as `other`. A report is at most 4 KB, counts at most
5571 50 per reason, and one address sends at most 30 a minute. Sec-GPC/DNT reports are dropped.
5572 `collect(registry)` is the POST handler on its own.
5573- **release-watch.js** keeps its rules: it prompts once, and reloads by itself only when it must
5574 and only when that is safe. Now it also reads the page's own manifest at load, even with the
5575 meta tag, and hands a release with components or contract ranges to **release-update.js** (new
5576 browser file, loaded on demand from the same directory). The update is applied in place when
5577 only style, content or server components changed and the contract ranges still hold. New
5578 `<link>`s are matched by path or `data-ov-asset` and load beside the old ones, which are removed
5579 only after every new one has loaded. `[data-ov-content="<component>"]` regions are re-fetched
5580 (`data-ov-src`, default the page URL) with no scripts, frames or inline handlers. Each region is
5581 replaced only when nothing inside it has focus, is protected or plays; otherwise it waits, and an
5582 unchanged `data-ov-rev` is skipped. Any failure keeps the old page and falls back to the prompt.
5583 Contracts outside the server's range force a reload, but still only when safe. It counts outcomes
5584 and beacons them (on hide, after an update, before a reload) to `OVReleaseConfig.metricsUrl`, the
5585 meta tag's `data-metrics` or the manifest's `metrics_url`, same origin only. It emits
5586 `ov:release-applied`, `ov:styles-updated` and `ov:content-updated`. The new
5587 `OVReleaseConfig.inPlace: false` turns in-place updates off.
5588- **navbar.js**: `init({ releaseWatch: { … } })` is passed to release-watch as `OVReleaseConfig`.
5589- **`openvibe-shared/release-compat`** (new, Node): `runMixedVersion` and `assertMixedVersion` run
5590 the page × server matrix from the manifests' contract ranges. Where the manifests say
5591 compatible, they call the real server with the other release's client. Where they say
5592 incompatible, they require the tab's plan to be a reload. Adjacent releases must be compatible.
5593 Also `replay(calls)`, `manifestFor()`, `consumerCompatible()` (service to service),
5594 `rollbackSafe()` (schema generations), `plan()`/`compatible()` (the tab's own logic) and
5595 `openPage()`, which runs release-watch in a linkedom page for in-place tests.
5596- Sizes (brotli q11): release-watch.js 1.7 → 3.1 KB (+1.4 KB; it loads after first paint at low
5597 priority). release-update.js is 2.9 KB and loads only when a release lists components. navbar.js
5598 is +21 bytes (27.9 KB). Both release files have a 3.5 KB budget test.
5599- Dev only: `linkedom` and `openvibe-contracts` (v0.30.2) are devDependencies.
5600 `test/fixtures/release-manifest.v1.1.0.json` is Contracts' 1.1.0 schema, used until that
5601 devDependency moves to v0.31.0.
5602
5603## 1.4.0 — 2026-09-23
5604
5605`openvibe-shared/analytics` is now the ADR-021 module (roadmap register item 26 and C-80). It
5606replaces three hand-copied versions in Live `server/analytics/`, Tools `apps/_shared/analytics/`
5607and Network `server/analytics/`. The old `analytics.js` was removed. It stored raw IPs, user ids,
5608cities and full user agents, and deleted raw rows only after 90 days. No deployed service still
5609used it.
5610
5611- **Same API, ADR-021 behaviour.** `new AnalyticsTracker(db, service[, opts])`, `middleware()`,
5612 `trackEvent()`, `flush()`, `aggregate()`, `getStats()`, `getOverview()`, `getBotAnalysis()` and
5613 `destroy()` all keep their shapes. A raw row has a route template, a referer origin, a
5614 user-agent class, a rotating session id, a country and a signed-in flag. `ip`, `user_id` and
5615 `city` are always NULL. Unique visitors come from day-salted hashes, which are deleted once the
5616 day's rollup is final. The rate check keeps its counters in memory only.
5617- **Sec-GPC and DNT are honoured on the server.** A request with `Sec-GPC: 1` or `DNT: 1` is not
5618 recorded at all: no raw row, visitor hash, session id or rate counter. It is therefore also
5619 missing from the rollups, because ADR-021 provides for no separate opted-out count.
5620 `trackEvent(name, { headers })` does the same. `privacy.optedOut(headers)` is exported.
5621- **`analytics/event.v1`** is the documented raw event shape:
5622 `docs/schemas/analytics-event.v1.json`, exported as
5623 `openvibe-shared/analytics/event.v1.json`. `openvibe-shared/analytics/event` provides
5624 `toEvent(row)`, `validateEvent(ev)` and `checkRow(row)`. `checkRow` rejects a row that has
5625 ip/user_id/city set. The validator reads the schema file itself. `trackEvent` now stores NULL for
5626 a value outside the schema, and throws a `TypeError` for an event name outside it.
5627- **Retention.** The tracker schedules the 30-day raw-event prune by default. Pass
5628 `retention: false` if the service runs `retention.pruneRawEvents` itself. The new
5629 `retention.schedulePrune(db, opts)` returns `{ run, stop }`. Days above 30 are refused.
5630- **Service specifics are options** (formerly Network's `network.js`). `paramPrefixes` adds
5631 parameter words to the defaults. `pathRules` is a list of `[RegExp, replacement]` rewrites
5632 applied before templating. Both are accepted by the tracker and by
5633 `retention.scrubEvents`/`scrubRollups`/`inspect`, so legacy rows are templated like new
5634 ones.
5635- **`openvibe-shared/analytics/prune-cli`** is the prune and one-time scrub command. Each service
5636 wraps it in its own script and passes `Database`, `defaultDb` or a `targets` hook, extra flags
5637 and path options. It runs a dry run by default. `--apply` needs `--backup` or `--no-backup`. It
5638 backs up one database to a file or several to a directory. Backups are owner-only (0600)
5639 because a pre-scrub backup holds the old IPs. It checks that rollup totals are unchanged and
5640 can VACUUM.
5641- An over-long route template now loses whole segments and ends in `/*`. Before, it was cut at
5642 200 characters, possibly mid-segment.
5643- The root export (`require('openvibe-shared')`) and `openvibe-shared/analytics` keep the names
5644 that 1.x exported: `classifyRequest`, `parseUserAgent`, `ANALYTICS_SCHEMA` and
5645 `BOT_USER_AGENTS` now use the ADR-021 code. `SUSPICIOUS_PATTERNS` is a frozen, deprecated
5646 object that nothing reads, and it will be removed in 2.0.0.
5647- Package shape: [docs/adr/0001-one-package-subpath-exports.md](docs/adr/0001-one-package-subpath-exports.md)
5648 (proposed) keeps one package with explicit subpath exports. `exports` gains the `analytics/*`
5649 subpaths, and `test/package.test.js` now refuses wildcard subpaths and any module file without
5650 an entry.
5651- Dev only: `better-sqlite3` is a devDependency for the analytics tests. The package itself has
5652 no native dependency.
5653
5654## 1.3.0 — 2026-09-23
5655
5656Observability and readiness (roadmap Track O, §15.19). Additive only; both modules are Node-only
5657and have no dependencies.
5658
5659- `openvibe-shared/metrics`: a Prometheus text-format registry (counter, gauge with optional
5660 scrape-time `collect()`, histogram with fixed buckets) that caps every metric's label
5661 combinations (past `maxSeries` they fold into one `_overflow` series, counted by
5662 `metrics_series_overflow_total`). `instrument(app, { service, release })` mounts the golden-signal
5663 middleware — `http_requests_total{method,route,status_class}`,
5664 `http_request_duration_seconds{method,route}`, `http_requests_in_flight` — keyed by the route
5665 TEMPLATE (`req.baseUrl` with id-like segments as `:id` + `req.route.path`, or a caller's
5666 `normalize(req)`; unmatched requests are `unmatched`, never the raw URL), process metrics
5667 (`process_resident_memory_bytes`, `nodejs_heap_*`, `nodejs_eventloop_lag_seconds{stat}`,
5668 `process_cpu_seconds_total`, `process_uptime_seconds`), `release_info{service,release}`, and
5669 `GET /metrics`, which answers only a direct loopback caller (127.0.0.1/::1 with no
5670 `X-Forwarded-For`, `X-Real-IP`, `Forwarded` or `CF-Connecting-IP`) and 404s everyone else.
5671- `openvibe-shared/ready`: `createReadiness({ service, release, checks, details })` → `handler` and
5672 `run()`. Each named check reports `status`, `required`, `latency_ms` and `checked_at` (a cached
5673 check keeps the time it really ran). `ready` is false (HTTP 503) only when a required check fails;
5674 failed optional checks are listed in `degraded` and the service stays ready (`status:
5675 "degraded"`). Failure reasons are one line with URL credentials, query strings and
5676 `token=`/`key=` values removed.
5677
5678## 1.2.1 — 2026-09-23
5679
5680- `release-watch.js` never reloads a tab that is playing `<video>`/`<audio>` or holds a live
5681 camera/microphone stream (viewers look idle while watching; broadcasters must never be cut).
5682
5683## 1.2.0 — 2026-09-23
5684
5685- `navbar.js` loads `release-watch.js` (from the same place it was loaded from) four seconds after
5686 `init()`, so every site on the shared navbar gets release prompts once it serves `/release.json`.
5687 A site without the route costs one 404 per page load; `init({ releaseWatch: false })` opts out.
5688
5689## 1.1.1 — 2026-09-23
5690
5691- `release-watch.js` works without the `<meta name="ov-release">` tag: the release `/release.json`
5692 reports when the script loads becomes the page's baseline, and a site that serves no
5693 `/release.json` is asked once and left alone. Adopting it is a route plus one script tag.
5694
5695## 1.1.0 — 2026-09-23
5696
5697Active-client update safety (ADR-016, contract `registry.release-manifest@1`). Additive only.
5698
5699- `openvibe-shared/release` (server): `createRelease({ service })` → `handler` for
5700 `GET /release.json` (deployed commit, commit time, package versions, `MIN_CLIENT_RELEASE`,
5701 24 h mixed-version window) and `metaTag()` for the page's own release.
5702- `release-watch.js` (browser, served at `/shared/release-watch.js`): on focus, visibility,
5703 reconnect and every 10 minutes, compares the page's release with `/release.json`. A newer
5704 release shows one Reload toast; a required release or one older than the window reloads the tab
5705 only when it is hidden or idle, no text field has focus and nothing is protected
5706 (`form[data-dirty="true"]`, `[data-ov-protected]`, `window.OVProtected()`).
5707
5708## 1.0.0 — 2026-09-22
5709
5710First release from its own repository. Imported history-free from
5711`OpenVibe.Network/packages/openvibe-shared` at Network commit `f337eb6`; every module is
5712byte-identical to that commit except `navbar.js`, which only changed as listed below.
5713Vendored copies in Media, Tools and Community carried a stale `ov-icons.js` (and Community a
5714stale navbar test); canonical was correct everywhere — see `docs/divergence-2026-09-22.md`.
5715
5716### Changed
5717- **navbar.js no longer ships every glyph.** The 87 Font Awesome solid glyph paths moved to a new
5718 browser file, `nav-icons.js`. navbar.js keeps the 13 glyphs it draws itself (account menu,
5719 admin link, wallet chip) plus the widths of the rest, and fetches `nav-icons.js?v=<content hash>`
5720 once, after first paint, the first time a site's link, chip or menu row names another glyph —
5721 from the directory navbar.js was loaded from, else `https://openvibe.network/shared/`. The slot
5722 is an empty `<svg>` of the glyph's exact size until the path arrives, so nothing moves; the
5723 finished DOM and pixels are identical to the Network copy (checked in headless Chrome). If
5724 `nav-icons.js` cannot load, those icons fall back to `<i class="fa-solid …">` (the behaviour
5725 before inline glyphs). A page that loads `nav-icons.js` before navbar.js gets every glyph
5726 synchronously. New option: `iconsUrl`.
5727 navbar.js: 151.8 KB raw / 39.0 KB brotli → 118.6 KB / 27.6 KB.
5728- `scripts/build-nav-icons.py` now writes `nav-icons.js`; `scripts/build-navbar-icons.js`
5729 regenerates navbar.js's block from it (`--check` in the tests).
5730
5731### Added
5732- `files.js` (`require('openvibe-shared/files')`): the list of browser files a site may serve at
5733 `/shared/<file>`, the package directory, `isBrowserFile()` and `path()`.
5734- `exports` for every module (`openvibe-shared/navbar`, `/nav-icons`, `/theme-loader`, … and
5735 `/package.json`); every subpath the Network copy exported still resolves to the same file.
5736- Tests: `test/run.js`, a navbar.js size budget (29 KB brotli) and icon availability test,
5737 browser-bundle hygiene (no `require()` or Node globals in served files), package exports.
5738- `scripts/size-report.js`: raw and brotli (q11) size of every browser file.
5739- CI on Node 22.22.1, including an install of a tag-style tarball into an empty project.
5740