openvibers/openvibe.shared CHANGELOG.md
CHANGELOG.md
| 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 |
| 6 | agent system … your actor is just your agent"; "this doesn't work … typing test … nothing happens … not seamless and |
| 7 | sleek"; "why don't the avatars show up properly"; "I don't really like the plus … reanalyze and make it a better one … |
| 8 | way 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 |
| 33 | on the composer … camera, photos, files … our own custom style … a modular system … it can still open this choose an |
| 34 | action … share … each site can choose … highly configurable"; on openvibe.community's feed: "add icons to those [tags] |
| 35 | and make those buttons nicer … make it more like a social media … it should say who posted it … their level … their |
| 36 | username 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 |
| 67 | tasks and codes and chats … we shouldn't even have that. It's just like filters … your actor is just your agent … it |
| 68 | can orchestrate to openvibe.codes, or timer, task, desktop, browser"; "I tried typing test in and just like nothing |
| 69 | happens … not seamless and sleek"; "regenerate this icon … based on what the menu actually looks like and what the |
| 70 | menu 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 |
| 94 | is buggy … it disappears when you scroll … it should just use a floating button that actually moves its position … |
| 95 | it's one button, it's not jumping around, it's transitioning"; "this camera button will flash in and out"; "improve |
| 96 | composer … better responsive support and size on mobile to fit better"; "make the close button look slightly 3D and … |
| 97 | glowing … 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 |
| 120 | phone, 11-13: "redesign the whole entire top to be our agentic search … uses our composer", "move the actor thing so |
| 121 | that it's at the top", "merge … this little online thing … the colors and other settings … shouldn't be taking up any |
| 122 | space", "the close button can't be white", "get rid of every single instance of this right or left arrow", "regenerate |
| 123 | the bell icon like that's ringing", "hide the label of the inactive tabs", "add the badges back", "the icons are so |
| 124 | small", "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 |
| 172 | phone, 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, |
| 197 | 11-13: "what happened to all the tabs … bring all that back", "what happened to the user avatar", "completely redesign |
| 198 | this area", "in edit mode … you can't scroll up", "improve the O so it actually looks like the original", "improve the |
| 199 | send 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 |
| 236 | to it** (the owner's phone, watching a live stream, 11-13: "a small button that lets you pop it in and out", "doesn't |
| 237 | allow 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 |
| 311 | draw their AI and usage numbers with it, and nobody draws a chart of their own. The look is the theme's (tokens only). |
| 312 | Every 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 |
| 329 | other 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 · |
| 403 | Agents 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 |
| 463 | switch: "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 | |
| 506 | The menu keeps its search field and tabs (the owner, 11-12, two phone screenshots: "the gui gets messed up and loses |
| 507 | its 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 | |
| 521 | You redone as tabs, icons that fill their squares everywhere in the menu, names in the brand face, and one composer with |
| 522 | a + for every tool (the owner, 11-12, a run of phone and desktop screenshots: "the notifications area is ultra buggy … |
| 523 | move the notifications into a tab … put the apps more front facing"; "make the icons bigger so they actually take up |
| 524 | more of the square … fix the alignment"; "waste of space … compact"; "fix the mic to the left … the timer should be in |
| 525 | the 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 | |
| 592 | The wallet sheet redesigned for phone and desktop, every phone page back to its true width, and whole site names in the |
| 593 | menu (the owner, 11-12, a phone screenshot of the wallet marked "UGLY" twice; "make everything a little more |
| 594 | user-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 | |
| 616 | Tests: `wallet-chrome` and `purse-catchup-chrome` (the new rows), `wallet-glyphs` (new); the wallet fixture can give a |
| 617 | rich history (`changes: 'many'`). |
| 618 | |
| 619 | ## 4.221.0 — 2026-11-12 |
| 620 | |
| 621 | The icon system, audited end to end (plan L32 I1/I2; the owner, 11-12: "Broken icon. Improve the icons that exist, add |
| 622 | more 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 |
| 642 | screenshot of the Dock crossed out: "there should only be the one unified UI … we're supposed to delete … that has the |
| 643 | old tab system", and the menu marked "the one unified"). And the Vibe place redesigned (the owner: "sleek … nice on |
| 644 | mobile … organized properly and sized well … without all these huge spaces … makes use of the full space … icons for |
| 645 | everything 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 | |
| 689 | Tests: |
| 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 | |
| 699 | Every plain element styled by the theme (the owner, 11-12, with circled screenshots of openvibe.exchange: "make sure all |
| 700 | basic elements are styled … custom with the theme system … update the little dots … reflect and improve all existing |
| 701 | elements … 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 | |
| 716 | Test: `kit-layout-chrome` (the drawn radio and its dot, the filled checkbox, the plain summary's chevron, a section's |
| 717 | padding). |
| 718 | |
| 719 | ## 4.218.0 — 2026-11-12 |
| 720 | |
| 721 | One composer, three verbs: **Post · Ask · Do** (research-2026-11-12/composer-post-ask-do.md; the owner, 11-12: "make it so |
| 722 | that the composer is able to … integrate directly to have the models list from OpenVibe.Exchange … the search the web |
| 723 | feature, the search engine feature … more cohesive, interconnected", and "Actor should be more of an orchestrator … same |
| 724 | features 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 | |
| 748 | Tests: `composer-verbs-chrome` (the switch, the chips, the Model sheet from a fake Frame with prices and Exchange |
| 749 | links, 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 | |
| 754 | The 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 | |
| 772 | One 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 | |
| 810 | Composer v2: one elegant composer that every site mounts (the owner, 11-12: "this composer is completely ugly and |
| 811 | broken … doesn't have the file attachment … it needs to be super elegant, like some kind of social media platform … a |
| 812 | shared 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 | |
| 836 | Test: `test/composer-v2-chrome.test.js` (the four presets at 390 px, a post's chip above and pictures under, onSubmit |
| 837 | and 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 | |
| 841 | A 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 | |
| 893 | A 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 |
| 945 | shared 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 |
| 963 | opens 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 |
| 981 | talk 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 |
| 1000 | place 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.** |
| 1042 | From 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 |
| 1103 | 2026-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 |
| 1154 | and the Dock side by side: "combine and consolidate these two menus into one unified menu … easier to search between", |
| 1155 | and 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 |
| 1175 | shorthand '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 |
| 1191 | the 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 |
| 1225 | 2026-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 |
| 1245 | of 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 |
| 1306 | watch 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 | |
| 1328 | Across sites the record will travel another way (storage is each site's own); gapless moves need pages changed in |
| 1329 | place. 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 |
| 1338 | 2026-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 | |
| 1388 | Built 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 |
| 1394 | from 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 → |
| 1425 | to You tab": "this user drop down is just redundant … move it into the You tab of the menu … add account switching |
| 1426 | and 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 |
| 1443 | improve upon and expand on it. And let's move the user … so that it's to the right of this. And instead of having |
| 1444 | messaging 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 | |
| 1464 | Removed: `site-switcher.js` (and its export), the Messages button and its `messages: { href, onOpen, label }` |
| 1465 | behaviours (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 |
| 1470 | animation… should make the O turn into a bunch of little circles, like how a normal loading spinner looks… a cooler |
| 1471 | animation that actually looks like it's loading or doing something. Also, there should be one dedicated for thinking |
| 1472 | when 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, |
| 1482 | especially the close button, so it feels more seamless… make the animation effect for the menu opening and closing more |
| 1483 | similar 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 |
| 1493 | styled." A `<button>`, a text field, a `<select>` or a `<textarea>` with **no class of its own** gets the theme's face |
| 1494 | from the loader: its colour, border, corner and font, and the accent on a tick box or a slider. Only the face, and at |
| 1495 | no specificity (`:where`), so any rule a site wrote still wins and no layout moves; a control's real shape is the |
| 1496 | kit's (`ovk-btn`, `ovk-input`). The loader grew about 1 KB raw (budget 67,600 characters). |
| 1497 | |
| 1498 | The 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 |
| 1503 | above Jump back in… the thing at the top with your username and online and the settings… a little more compact… most |
| 1504 | used is cool, but maybe we should actually have links to whatever the user uses the most… so it's just easier to jump |
| 1505 | to 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 |
| 1518 | menu button that already exists… it's supposed to be nicer connected to this." Until now the sheet rose from the bottom |
| 1519 | and 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 |
| 1532 | close 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 |
| 1543 | of 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 |
| 1574 | their solid form; 142 of the 278 glyphs had none. 75 of those are made of lines only and thicken when small by design; |
| 1575 | the other 67 are shapes, and all of them are drawn now (frames and cards, marks in a disc, the media keys, things, |
| 1576 | people, 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 |
| 1578 | filled as a shape or not drawn; it carries the stroke inline now, as the sprite already did. Site marks: eleven |
| 1579 | product ids had no mark and drew the bare "V" (computer, mail, lock/locker, git, barcode, party, studio, my, home, |
| 1580 | restream, node), and four things sites name as marks did the same (shoe, heart, calendar, camera). Each has its glyph |
| 1581 | in 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` |
| 1586 | and `dock-actor.js` asked `openvibe.codes` and `openvibe.actor` directly from every site. Every site's `connect-src` |
| 1587 | allows `openvibe.network` and refuses those, so 49 of 49 signed-in sites logged 1-4 console errors at 1280 on |
| 1588 | 2026-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 |
| 1590 | writes; `GET /actor` and its subpaths. An explicit `base`/`agentBase`/`actorBase` (a test, or the extension's own |
| 1591 | panel) still reaches the product itself, and nothing is asked without the person's token. The markup and styles are |
| 1592 | unchanged. `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 |
| 1609 | Community's new-topic form (`/forum/discuss?title=&body=`) with the title and the link filled in; the person picks the |
| 1610 | forum and posts it themselves. OpenVibe's own places lead the grid and wear the accent. Not shown on Community's own |
| 1611 | pages. |
| 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, |
| 1616 | better, 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 `"` 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). |
| 2591 | All notable changes to `openvibe-shared`. Versions follow [semver](https://semver.org/): a |
| 2592 | breaking change to any exported module, browser global or served file name is a new major. |
| 2593 | A 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 | |
| 2843 | From 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 | |
| 3883 | What 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 | |
| 4156 | The 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 | |
| 4242 | The 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 `'`. 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 | |
| 5261 | Boost 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 | |
| 5270 | Three new modules for plan T11, plus `serve.js` now taking its cache headers from the shared policy (its own headers are |
| 5271 | unchanged): |
| 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). |
| 5289 | Breaking, 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 | |
| 5297 | Page 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 | |
| 5317 | Fixes 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 | |
| 5354 | New 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 | |
| 5391 | Host'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 | |
| 5401 | The 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 | |
| 5416 | Fix: `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 | |
| 5438 | Tests are in `test/release-watch-realtime.test.js`. Additive. |
| 5439 | |
| 5440 | ## 1.16.1 — 2026-09-26 |
| 5441 | |
| 5442 | Fix: 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 | |
| 5448 | Fix: 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 | |
| 5460 | Accessibility (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 | |
| 5469 | Fix: 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 | |
| 5480 | No 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 | |
| 5499 | Everything 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 |
| 5506 | linked to its commit, and a link to the latest "Patch notes" post that gathers a batch of changes. |
| 5507 | Without `service` it shows the whole network with site labels. DOM nodes only (commit text is never |
| 5508 | parsed as HTML), https links only, hidden when there is nothing or the fetch fails; styles follow |
| 5509 | the 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 |
| 5515 | an AsyncLocalStorage holding its trace and request ids, and wraps `fetch()` once so a call to a |
| 5516 | loopback 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, |
| 5518 | user-chosen URLs) are untouched, so trace ids never leave the network; outside a request nothing is |
| 5519 | added. 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 |
| 5525 | copy and they had drifted: Live allowed the 3fff::/20 documentation range and 5f00::/16, and only knew |
| 5526 | the /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 |
| 5528 | is no rebinding window), `createSafeLookup` for tests, `assertPublicUrl` and `EgressDenied`. Node only; |
| 5529 | the three services keep their own fetch, delivery and throttle wrappers on top of it. |
| 5530 | |
| 5531 | Fixed: `release.collect()` counted client beacons per socket address, which behind the proxy is |
| 5532 | 127.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 | |
| 5537 | Touch and keyboard feedback in the shared chrome (owner feedback: phones flashed a theme-blind blue |
| 5538 | box over anything tapped). The navbar, its dropdowns, the launcher, the drawer and the footer turn |
| 5539 | off `-webkit-tap-highlight-color`, show a slight press (`scale`, which composes with existing |
| 5540 | transforms) and give keyboard focus one ring in the theme accent. Rules are wrapped in `:where()` |
| 5541 | so they carry zero specificity: the chrome's own `:focus-visible` rules and host pages still win. |
| 5542 | With reduced motion the press is a brief dim instead of a shrink. |
| 5543 | |
| 5544 | ## 1.5.0 — 2026-09-23 |
| 5545 | |
| 5546 | Roadmap Track R (D42–D46, ADR-016): release manifests with components, open tabs updated in place, |
| 5547 | update metrics, and a mixed-version test harness. Additive; a service that changes nothing keeps |
| 5548 | serving 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 |
| 5606 | replaces three hand-copied versions in Live `server/analytics/`, Tools `apps/_shared/analytics/` |
| 5607 | and Network `server/analytics/`. The old `analytics.js` was removed. It stored raw IPs, user ids, |
| 5608 | cities and full user agents, and deleted raw rows only after 90 days. No deployed service still |
| 5609 | used 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 | |
| 5656 | Observability and readiness (roadmap Track O, §15.19). Additive only; both modules are Node-only |
| 5657 | and 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 | |
| 5697 | Active-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 | |
| 5710 | First release from its own repository. Imported history-free from |
| 5711 | `OpenVibe.Network/packages/openvibe-shared` at Network commit `f337eb6`; every module is |
| 5712 | byte-identical to that commit except `navbar.js`, which only changed as listed below. |
| 5713 | Vendored copies in Media, Tools and Community carried a stale `ov-icons.js` (and Community a |
| 5714 | stale 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 |