Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions changelog.d/177-tab-title-roster-order.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The web UI no longer stops at "Failed to load plugins — ds-bot": the tab-title effect added in #176 read `roster` before it was declared. A regression test checks the order.
2 changes: 1 addition & 1 deletion core/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -14532,6 +14532,7 @@ function apply(ctx) {
}
ctx.effect(() => installLocale(ctx.get("locale")), "ds-bot: interface copy");
ctx.effect(() => installBrandChrome(), "ds-bot: brand chrome");
const roster = createSource(EMPTY_ROSTER);
ctx.effect(() => {
const sessions = ctx.sessions.list;
const sync = () => {
Expand All @@ -14545,7 +14546,6 @@ function apply(ctx) {
sync();
return sessions.subscribe(sync);
}, "ds-bot: tab title");
const roster = createSource(EMPTY_ROSTER);
migrateStorage();
const UNREAD_KEY = STORAGE.unread;
let savedUnread = {};
Expand Down
2 changes: 1 addition & 1 deletion core/src/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export function apply(ctx) {
} catch {}
ctx.effect(() => installLocale(ctx.get('locale')), 'ds-bot: interface copy')
ctx.effect(() => installBrandChrome(), 'ds-bot: brand chrome')
const roster = createSource(EMPTY_ROSTER)
ctx.effect(() => {
const sessions = ctx.sessions.list
const sync = () => {
Expand All @@ -63,7 +64,6 @@ export function apply(ctx) {
sync()
return sessions.subscribe(sync)
}, 'ds-bot: tab title')
const roster = createSource(EMPTY_ROSTER)
migrateStorage()
const UNREAD_KEY = STORAGE.unread
let savedUnread = {}
Expand Down
17 changes: 17 additions & 0 deletions core/test/client-apply-order.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'

// `ctx.effect` runs its body immediately, so an effect that reads `roster` before
// `const roster` is declared throws a TDZ error and the whole client plugin fails to
// load (blank "Failed to load plugins" page, #176 regression).
test('client apply declares roster before effects that read it', () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the new test to the generated patch payload

Because core/test/client-apply-order.test.js exists only in core/ and not under patches/ds-bot/files/test/, the required node scripts/ds-bot-patches.mjs check reports this file as "committed but not produced by the patches"; consequently npm run verify cannot pass, and rebuilding core/ from the patches drops this regression test. Regenerate the patch payload so the new file is stored verbatim alongside core/.

AGENTS.md reference: AGENTS.md:L69-L69

Useful? React with 👍 / 👎.

const src = readFileSync(new URL('../src/client/index.js', import.meta.url), 'utf8')
const declared = src.indexOf('const roster = createSource(')
assert.ok(declared > 0, 'roster declaration found')
for (const label of ['ds-bot: tab title']) {
const effect = src.indexOf(`'${label}'`)
assert.ok(effect > 0, `${label} effect found`)
assert.ok(declared < effect, `${label} must come after const roster`)
}
})
1 change: 1 addition & 0 deletions docs/subsystems/core-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ The default card (`flybot-slots.js`) shows where a held request came from: `send

- 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`, a room uses its display name). 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**.
- 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
5 changes: 3 additions & 2 deletions patches/ds-bot/0003-flybot-api-client.patch
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ diff --git a/src/client/index.js b/src/client/index.js
import { GROUP_SETTINGS_CSS } from './group-settings.js'
import { FOLDER_CSS } from './folder-menu.js'
import { USAGE_CSS } from './usage-page.js'
@@ -37,7 +37,29 @@ import { noticeApprovals } from './approval-notices.js'
@@ -37,8 +37,30 @@ import { noticeApprovals } from './approval-notices.js'
export const inject = ['sessions', 'uiWorkspace', 'uiConversation', 'slots', 'theme', 'layout']

export function apply(ctx) {
Expand All @@ -486,6 +486,7 @@ diff --git a/src/client/index.js b/src/client/index.js
+ } catch {}
ctx.effect(() => installLocale(ctx.get('locale')), 'ds-bot: interface copy')
+ ctx.effect(() => installBrandChrome(), 'ds-bot: brand chrome')
const roster = createSource(EMPTY_ROSTER)
+ ctx.effect(() => {
+ const sessions = ctx.sessions.list
+ const sync = () => {
Expand All @@ -499,9 +500,9 @@ diff --git a/src/client/index.js b/src/client/index.js
+ sync()
+ return sessions.subscribe(sync)
+ }, 'ds-bot: tab title')
const roster = createSource(EMPTY_ROSTER)
migrateStorage()
const UNREAD_KEY = STORAGE.unread
let savedUnread = {}
@@ -52,7 +52,7 @@ export function apply(ctx) {
try { seenAt = JSON.parse(localStorage.getItem(SEEN_KEY) ?? '{}') ?? {} } catch { seenAt = {} }
// `details` is the panel's subject (a Session id); `view` picks details or the
Expand Down
5 changes: 3 additions & 2 deletions patches/ds-bot/0008-client-bundle.patch
Original file line number Diff line number Diff line change
Expand Up @@ -10123,7 +10123,7 @@ diff --git a/client.js b/client.js
.bt-ubody a{color:inherit}
.bt-umention{display:inline-flex;align-items:center;gap:3px;vertical-align:-3px;max-width:100%;padding:0 1px;border:0;background:none;color:inherit;font:inherit;font-weight:600;line-height:inherit;cursor:pointer;border-radius:6px;white-space:nowrap}
.bt-umention:hover{text-decoration:underline;text-underline-offset:2px}
@@ -13250,7 +13250,29 @@ function noticeApprovals({ work, roster, shownSession, openSession, since: since
@@ -13250,8 +13250,30 @@ function noticeApprovals({ work, roster, shownSession, openSession, since: since
// src/client/index.js
var inject = ["sessions", "uiWorkspace", "uiConversation", "slots", "theme", "layout"];
function apply(ctx) {
Expand All @@ -10137,6 +10137,7 @@ diff --git a/client.js b/client.js
+ }
ctx.effect(() => installLocale(ctx.get("locale")), "ds-bot: interface copy");
+ ctx.effect(() => installBrandChrome(), "ds-bot: brand chrome");
const roster = createSource(EMPTY_ROSTER);
+ ctx.effect(() => {
+ const sessions = ctx.sessions.list;
+ const sync = () => {
Expand All @@ -10150,9 +10151,9 @@ diff --git a/client.js b/client.js
+ sync();
+ return sessions.subscribe(sync);
+ }, "ds-bot: tab title");
const roster = createSource(EMPTY_ROSTER);
migrateStorage();
const UNREAD_KEY = STORAGE.unread;
let savedUnread = {};
@@ -13274,7 +13274,7 @@ function apply(ctx) {
} catch {
seenAt = {};
Expand Down
17 changes: 17 additions & 0 deletions patches/ds-bot/files/test/client-apply-order.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'

// `ctx.effect` runs its body immediately, so an effect that reads `roster` before
// `const roster` is declared throws a TDZ error and the whole client plugin fails to
// load (blank "Failed to load plugins" page, #176 regression).
test('client apply declares roster before effects that read it', () => {
const src = readFileSync(new URL('../src/client/index.js', import.meta.url), 'utf8')
const declared = src.indexOf('const roster = createSource(')
assert.ok(declared > 0, 'roster declaration found')
for (const label of ['ds-bot: tab title']) {
const effect = src.indexOf(`'${label}'`)
assert.ok(effect > 0, `${label} effect found`)
assert.ok(declared < effect, `${label} must come after const roster`)
}
})
Loading