Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/subsystems/core-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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 ` · <owner>` 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 `<style data-bt-face-at>` hides the `reference` rows whose icon is `session` (Sessions and Subagents) and their section title by MenuView's option ids, `controller.hover` moves the highlight past them in the direction of the key (so arrow keys skip them and Enter never picks one), and a menu with nothing else left is `dismiss()`ed, so Enter sends. File rows stay. Elsewhere, **Everyone**'s count is the Bots it reaches (local members and seated friends' Bots), `1 member` / `2 members`.
- 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 `installHeaderEffects` (`header-effects.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 `<style data-bt-face-at>` hides the `reference` rows whose icon is `session` (Sessions and Subagents) and their section title by MenuView's option ids, `controller.hover` moves the highlight past them in the direction of the key (so arrow keys skip them and Enter never picks one), and a menu with nothing else left is `dismiss()`ed, so Enter sends. File rows stay. Elsewhere, **Everyone**'s count is the Bots it reaches (local members and seated friends' Bots), `1 member` / `2 members`.

### Silent creation (#645)

Expand Down
Loading