diff --git a/docs/subsystems/core-client.md b/docs/subsystems/core-client.md index b3048cafb..53f712fd6 100644 --- a/docs/subsystems/core-client.md +++ b/docs/subsystems/core-client.md @@ -96,7 +96,7 @@ The overlay (`flybot-slots.js` `FlyBotApprovals`) is the fallback, not a second - The Session header is the shell's own: FlyBot registers the Bot pill in `conversation.session.header.utilities` rather than claiming the single `conversation.session.header` slot, so the seats that header renders — the lineage title and `conversation.session.header.actions`, where `@deepseek-ai/ui-subagent` puts the subagent catalog — keep rendering (#803). The floating header style FlyBot used to draw over the Session header is gone. - What the pill does to the page is not in the pill and not in a seat of its own (#803). The shell draws `.utilities` — and the pill with it — only while its chrome shows, which a Session without a first message hides, so a side effect parked in the pill stops there: the friend's empty-chat hint, `body[data-bt-friend-chat]`, the `Message {name}` composer label, the visit ledger, `body[data-bt-room]` and the face-room `@` filter. `installHeaderEffects` (`src/client/header-effects.js`) is installed once from `apply` and keeps them on the Session in the main view. It carries no React, and each effect has its own dependency key: the sessions list answers before the roster, so a single "did the Session change" guard would drop a friend's flags on the first pass, while `beginVisit`/`endVisit` keyed on more than the Session would open a second visit for a chat that was only just opened. `test/header-effects.test.js` drives those transitions on jsdom; `test/header-slot.test.js` pins that no such effect runs in `HeaderPill` and that FlyBot claims no other header seat. - `conversation.session.header.corner` is **not** available as that seat: it is `single` and `ui-sidebar-right` already registers its right-sidebar button there with no `priority` (0), and `SlotCore.register` throws on a second registration in the same cell (`single slot "…" already has a registration at priority 0`), which would fail FlyBot's whole client plugin from `apply`. Only `--bt-first-sep` still rides the pill, because it needs a turn and so always has a header to be drawn in. -- `HeaderPill` holds the localized **Message {name}** label on the composer with `holdComposerLabel` (`src/client/composer-label.js`) in a layout effect, so it is in place before the first paint: it sets the placeholder CSS var and writes the label on every `[data-composer-input]`'s `aria-label` and `data-placeholder`; cleanup hands each input the shell's latest own value back when the header unmounts or the session changes (#166). The shell picks its copy by phase before it knows whose chat it is (the new-session hero copy on a first open, its coding copy after a send), so a `MutationObserver` (those two attributes and child changes) rewrites them in its own callback, before the next paint, while the chat is open (#419, #499); `test/composer-label.test.js` drives it on a jsdom composer. In a friend's Bot chat it also sets `body[data-bt-friend-chat]`, and CSS hides the shell's run-stats footer (`[data-composer-stats]`) there: the friend's Bot runs on its owner's home, so local turns/steps/tok/s are relay turns and always 0 (#419). `ContextCell` hides `Tool added` / `Tool removed` rows for the roster's `pluginTools` (set in `indexRoster` via `setPluginTools`, `text.js`) as it does for connector tools (#419). The poll re-reads `pluginTools` from every `state` answer (a plugin reload makes no revision), and `ContextCell` (injected with `face`) selects them from the roster so a row already drawn re-renders when they arrive. +- The localized **Message {name}** label on the composer is one of those effects, not something the pill holds: `installHeaderEffects` starts `holdComposerLabel` (`src/client/composer-label.js`) for the Session in the main view, keyed `sessionId|name`, so the label survives a Session whose header is not drawn. It sets the placeholder CSS var and writes the label on every `[data-composer-input]`'s `aria-label` and `data-placeholder`; cleanup hands each input the shell's latest own value back when the Session changes or FlyBot stops (#166, #803). The shell picks its copy by phase before it knows whose chat it is (the new-session hero copy on a first open, its coding copy after a send), so a `MutationObserver` (those two attributes and child changes) rewrites them in its own callback, before the next paint, while the chat is open (#419, #499); `test/composer-label.test.js` drives it on a jsdom composer. The same installer sets `body[data-bt-friend-chat]` in a friend's Bot chat, and CSS hides the shell's run-stats footer (`[data-composer-stats]`) there: the friend's Bot runs on its owner's home, so local turns/steps/tok/s are relay turns and always 0 (#419). `ContextCell` hides `Tool added` / `Tool removed` rows for the roster's `pluginTools` (set in `indexRoster` via `setPluginTools`, `text.js`) as it does for connector tools (#419). The poll re-reads `pluginTools` from every `state` answer (a plugin reload makes no revision), and `ContextCell` (injected with `face`) selects them from the roster so a row already drawn re-renders when they arrive. ### Face-room held bubble and overview @@ -105,7 +105,7 @@ The overlay (`flybot-slots.js` `FlyBotApprovals`) is the fallback, not a second ### FlyBot product chrome - User-visible chrome says **FlyBot** (header mark + name, About, settings title, tab title, favicon, zh copy). The mark is the existing FlyBot logo (`docs/images/logo.svg`), inlined as `FlyBotMark` from `src/client/brand.js`. Update popups say **FlyBot core {version}**. The npm package remains `ds-bot`; plugin id, localStorage keys, and `console.warn('[ds-bot]')` stay upstream. -- The tab title follows the open chat (`sessionTabLabel` in `brand.js`: a friend's Bot reads `Name (owner)`, #384, a room uses its display name; an ended face-to-face room, closed or left, reads `Face-to-face · closed` unless the user named it, #250). `installTabTitle` re-reads it on session **and** roster changes, so a face room the joiner opens before the roster names it, or a room renamed later, still gets its own title (#173). Sidebar row gear controls are hover/focus-only, and Bot settings call the duplicate/delete card **More**. An empty chat with a friend's Bot shows a one-line hint (`--bt-remote-empty`, set by `HeaderPill`) that the friend approves unless Auto-send is on (#173). The agent entry on **+** reads **Add Bot**. +- The tab title follows the open chat (`sessionTabLabel` in `brand.js`: a friend's Bot reads `Name (owner)`, #384, a room uses its display name; an ended face-to-face room, closed or left, reads `Face-to-face · closed` unless the user named it, #250). `installTabTitle` re-reads it on session **and** roster changes, so a face room the joiner opens before the roster names it, or a room renamed later, still gets its own title (#173). Sidebar row gear controls are hover/focus-only, and Bot settings call the duplicate/delete card **More**. An empty chat with a friend's Bot shows a one-line hint (`--bt-remote-empty`, set by `installHeaderEffects`) that the friend approves unless Auto-send is on (#173). The agent entry on **+** reads **Add Bot**. - In `apply`, any `ctx.effect` that reads `roster` must come after `const roster = createSource(...)`: effects run immediately, and a read before the declaration throws and fails the whole client plugin load (`core/test/client-apply-order.test.js`). ### One sidebar list @@ -168,7 +168,7 @@ The overlay (`flybot-slots.js` `FlyBotApprovals`) is the fallback, not a second - **Group and face-room Bot labels (#526, owner decision 2026-10-20, option a).** The same person-first format: `Reviewer (flybot682b8a43)`, never Bot Social's member label plus the handle (`Reviewer · FlyBot 682b8a43 (flybot682b8a43)`). `remoteBotName` drops a trailing ` · ` from a Bot's name when it is that Bot's owner (the server's `label` reaching a name slot, e.g. a bubble header with no seat yet); `ownerNameOf` passes over an owner name made up from an install id (`isAutoOwnerName`: `FlyBot` + hex id) to the owner's username from the home handle, and keeps it only when no handle is known; `seatOwnerName` reads my own seat's owner the same way (`face-seats.js`, `at-menu.js`). The Bot names in `FacePersonRow` and the remote rows of the group Members lists (`group-settings.js`, `details-drawer.js`) carry the handle as a tooltip, as bubble headers already do. Covered by `core/test/group-bot-label.test.js`. - A face-to-face room's subtitle (`roomSubtitle` → `faceRoomCount` in `group-settings.js`) names what it counts, `1 person · 1 Bot` / `2 people · 4 Bots`: Bots are the live Bot seats, people the distinct owners among them (me once, a friend by owner name); human seats and Bots that left do not count (#306). - The composer `@` in a face-to-face room (`mentionRows` / `atSources` in `at-menu.js`) offers the room's own Bots, each once (a repeated remote seat is folded by handle; Bots that left are out), labelled as the Members panel names them (`Chief (SelfA)`, `Chief (SelfB)`), plus **Everyone** with the room's Bot count (`2 Bots`, #457). Published friends' Bots (`remoteMentionRows`) and plugin rows (the social `Friends' Bots` section) are not offered there, since only the Bots in the room can be reached in it. Only the label changes: the inserted text is still the `@Name` the room routes on (`@SelfA's Chief`), never a handle (#305). -- A face room's `@` menu shows no DSH Sessions rows (#305, owner decision 2026-10-19: filter in FlyBot for now; #422 asks DSH for an API). DSH's built-in `reference` source cannot be dropped for one Session, so `HeaderPill` (`header.js`) starts `actions.filterFaceAtMenu` for a face room, which watches that Session's trigger controller (`inputTriggers.sessionOf`, the same one the composer uses) with `watchFaceMenu` (`face-at-filter.js`): a `