diff --git a/changelog.d/276-sidebar-gear.fixed.md b/changelog.d/276-sidebar-gear.fixed.md new file mode 100644 index 000000000..3c28a2074 --- /dev/null +++ b/changelog.d/276-sidebar-gear.fixed.md @@ -0,0 +1 @@ +Own Bot rows in the wide sidebar show the same hover/focus gear as friends' Bots, opening Edit Bot ([#276](https://github.com/ThinkFlowLab/FlyBot/issues/276)). diff --git a/core/client.js b/core/client.js index 843a8196d..3e8ebf09c 100644 --- a/core/client.js +++ b/core/client.js @@ -5928,11 +5928,12 @@ function BotRow({ bot, roster, actions, useSessions, useSessionStatus, useSessio ) : null, unreadMark ); - const settings = wide && bot.remote ? (0, import_react9.createElement)("button", { + const settingsLabel = bot.remote ? t("Bot settings") : t("Edit Bot"); + const settings = wide ? (0, import_react9.createElement)("button", { type: "button", className: "bt-row-settings", - "aria-label": t("Bot settings"), - title: t("Bot settings"), + "aria-label": settingsLabel, + title: settingsLabel, onClick: (event) => { event.stopPropagation(); actions.openSettings("bots", id); diff --git a/core/src/client/sidebar.js b/core/src/client/sidebar.js index dfad51284..dedbc1a06 100644 --- a/core/src/client/sidebar.js +++ b/core/src/client/sidebar.js @@ -87,8 +87,10 @@ function BotRow({ bot, roster, actions, useSessions, useSessionStatus, useSessio h('span', { className: 'bt-row-name' }, h('span', { className: 'bt-row-title' }, shown), bot.remote ? h(RemoteMark, { remote: bot.remote }) : null, pinned ? h(PinMark) : null), awayLine ? h('span', { className: 'bt-row-preview' }, awayLine) : preview ? h('span', { className: 'bt-row-preview' }, preview) : null) : null, unreadMark) - const settings = wide && bot.remote ? h('button', { - type: 'button', className: 'bt-row-settings', 'aria-label': t('Bot settings'), title: t('Bot settings'), + // Hover/focus gear on every wide row (#276/#173): own Bots and friends' Bots alike. + const settingsLabel = bot.remote ? t('Bot settings') : t('Edit Bot') + const settings = wide ? h('button', { + type: 'button', className: 'bt-row-settings', 'aria-label': settingsLabel, title: settingsLabel, onClick: (event) => { event.stopPropagation(); actions.openSettings('bots', id) }, }, h(GearIcon)) : null if (settings) return h('div', { className: 'bt-row-wrap', 'data-bot-id': id }, row, settings) diff --git a/core/test/looks.test.js b/core/test/looks.test.js index 0edd548ed..9abf6c271 100644 --- a/core/test/looks.test.js +++ b/core/test/looks.test.js @@ -52,6 +52,9 @@ test('Bot settings is the only settings entry; nothing is called Team settings', assert.match(text('remote-bot-settings.js'), /focus\?\.\(\{ preventScroll: true \}\)/) assert.match(text('sidebar.js'), /\.bt-tree-item>\.bt-row-wrap\{flex:1 1 auto;width:auto;min-width:0\}/) assert.match(text('sidebar.js'), /bt-row-settings/) + // #276: gear is on every wide Bot row (own and friends'), not only remotes. + assert.match(text('sidebar.js'), /const settings = wide \? h\('button'/) + assert.doesNotMatch(text('sidebar.js'), /wide && bot\.remote \? h\('button'/) assert.match(text('header.js'), /setAttribute\('aria-label', composerLabel\)/) assert.match(text('settings.js'), /filter\(bot => !bot\.remote\)/) // #276: only the user's own Bots are offered as a Main Bot, in both selects. diff --git a/docs/subsystems/core-client.md b/docs/subsystems/core-client.md index 0269c4abd..ac74f1b67 100644 --- a/docs/subsystems/core-client.md +++ b/docs/subsystems/core-client.md @@ -68,7 +68,7 @@ The overlay (`flybot-slots.js` `FlyBotApprovals`) is the fallback, not a second - The "@" reader is shared with the host (`core/src/shared/mentions.js`, bundled into `core/client.js`). #287 added `unmatchedMentions` there (the `@` words that name nobody, used by the host for the face-room hint); `mentionSpans` and what the bubbles draw are unchanged. - A friend's Bot settings surface is not the local editor (#160): no rename, avatar editor, model, folder, instructions, Main Bot, Hide, Duplicate, or Delete. The details gear and context menu say **Bot settings**. The menu is Mark as Unread, Bot settings, and Copy conversation ID. Per-friend **Auto-send** and **Remove this Bot** stay on that page (`flybot.bot.details`); Remove closes the overlay so a stale local editor cannot appear. The Bots settings list is local Bots only. - A friend's Bot details overview is that compact panel: handle plus `FlyBotDetails` (Auto-send and a muted Remove link), no Browser tab, no Usage / model / folder. Opening the gear uses `remote-bot-settings` (`RemoteBotSettingsSheet`): a bottom sheet on narrow viewports and a centered card from 520px up, with the scrim behind the dialog (`z-index` stacking like `bt-settings` / `bt-mem`) so the sheet stays clickable through enter/leave animations. The surrounding DSH settings-shell chrome (back/title) is not redesigned (#166). The sheet takes focus when it opens (`tabIndex -1`, `preventScroll`), closes on Escape from its own `onKeyDown` as well as `useEscape`, and gives focus back without scrolling; in the tree a friend's `.bt-row-wrap` (row plus gear) shrinks like a plain row (`.bt-tree-item>.bt-row-wrap`), so the gear never overflows the sidebar and focusing it cannot scroll it sideways (#274). -- A listed friend's Bot row in wide sidebar mode is a `bt-row-wrap`: the session `bt-row` and a sibling gear (`bt-row-settings`) that opens Bot settings — not a button inside the row (#166). +- Every listed Bot row in wide sidebar mode is a `bt-row-wrap`: the session `bt-row` and a sibling gear (`bt-row-settings`, hover/focus) that opens Edit Bot for an own Bot or Bot settings for a friend's (#166, #276). - `HeaderPill` sets the composer placeholder CSS var and mirrors the localized **Message {name}** string on `[data-composer-input]`'s `aria-label`; cleanup restores the prior label when the header unmounts or the session changes (#166). ### Face-room held bubble and overview diff --git a/patches/ds-bot/0003-flybot-api-client.patch b/patches/ds-bot/0003-flybot-api-client.patch index 38b75276b..4b311d3a7 100644 --- a/patches/ds-bot/0003-flybot-api-client.patch +++ b/patches/ds-bot/0003-flybot-api-client.patch @@ -980,7 +980,7 @@ diff --git a/src/client/sidebar.js b/src/client/sidebar.js import { showsDshRow } from './dsh-offer.js' // ------------------------------------------------------------------------- -@@ -37,56 +37,88 @@ const workKeys = text => (text === '' ? [] : text.split(',').map((key) => { +@@ -37,56 +37,90 @@ const workKeys = text => (text === '' ? [] : text.split(',').map((key) => { })) const workLine = (lead, tool, more) => h(Shimmer, null, `${lead} · ${workPhrase(tool)}…${more > 0 ? ` +${more}` : ''}`) @@ -1042,8 +1042,10 @@ diff --git a/src/client/sidebar.js b/src/client/sidebar.js awayLine ? h('span', { className: 'bt-row-preview' }, awayLine) : preview ? h('span', { className: 'bt-row-preview' }, preview) : null) : null, - wide && unread && !active ? h('span', { className: 'bt-unread', 'aria-hidden': true }) : null) + unreadMark) -+ const settings = wide && bot.remote ? h('button', { -+ type: 'button', className: 'bt-row-settings', 'aria-label': t('Bot settings'), title: t('Bot settings'), ++ // Hover/focus gear on every wide row (#276/#173): own Bots and friends' Bots alike. ++ const settingsLabel = bot.remote ? t('Bot settings') : t('Edit Bot') ++ const settings = wide ? h('button', { ++ type: 'button', className: 'bt-row-settings', 'aria-label': settingsLabel, title: settingsLabel, + onClick: (event) => { event.stopPropagation(); actions.openSettings('bots', id) }, + }, h(GearIcon)) : null + if (settings) return h('div', { className: 'bt-row-wrap', 'data-bot-id': id }, row, settings) @@ -1596,7 +1598,7 @@ diff --git a/test/create-bot.test.js b/test/create-bot.test.js diff --git a/test/looks.test.js b/test/looks.test.js --- a/test/looks.test.js +++ b/test/looks.test.js -@@ -40,7 +40,24 @@ test('Bot settings is the only settings entry; nothing is called Team settings', +@@ -40,7 +40,27 @@ test('Bot settings is the only settings entry; nothing is called Team settings', assert.match(text('palette.js'), /title: t\('Bot settings'\), run: \(\) => actions\.openSettings\('general'\)/) // One Bot's own entries open that Bot, so they read Edit Bot instead. assert.match(text('context-menu.js'), /\[t\('Edit Bot'\), GearIcon, \(\) => actions\.openSettings\('bots', bot\.id\)\]/) @@ -1613,6 +1615,9 @@ diff --git a/test/looks.test.js b/test/looks.test.js + assert.match(text('remote-bot-settings.js'), /focus\?\.\(\{ preventScroll: true \}\)/) + assert.match(text('sidebar.js'), /\.bt-tree-item>\.bt-row-wrap\{flex:1 1 auto;width:auto;min-width:0\}/) + assert.match(text('sidebar.js'), /bt-row-settings/) ++ // #276: gear is on every wide Bot row (own and friends'), not only remotes. ++ assert.match(text('sidebar.js'), /const settings = wide \? h\('button'/) ++ assert.doesNotMatch(text('sidebar.js'), /wide && bot\.remote \? h\('button'/) + assert.match(text('header.js'), /setAttribute\('aria-label', composerLabel\)/) + assert.match(text('settings.js'), /filter\(bot => !bot\.remote\)/) + // #276: only the user's own Bots are offered as a Main Bot, in both selects. diff --git a/patches/ds-bot/0008-client-bundle.patch b/patches/ds-bot/0008-client-bundle.patch index e46b9323d..e0b68fcba 100644 --- a/patches/ds-bot/0008-client-bundle.patch +++ b/patches/ds-bot/0008-client-bundle.patch @@ -3644,7 +3644,7 @@ diff --git a/client.js b/client.js "button", { type: "button", -@@ -4802,21 +4802,65 @@ function BotRow({ bot, roster, actions, useSessions, useSessionStatus, useSessio +@@ -4802,21 +4802,66 @@ function BotRow({ bot, roster, actions, useSessions, useSessionStatus, useSessio onClick: () => actions.openSession(id), onContextMenu }, @@ -3664,11 +3664,12 @@ diff --git a/client.js b/client.js - wide && unread && !active ? (0, import_react8.createElement)("span", { className: "bt-unread", "aria-hidden": true }) : null + unreadMark + ); -+ const settings = wide && bot.remote ? (0, import_react9.createElement)("button", { ++ const settingsLabel = bot.remote ? t("Bot settings") : t("Edit Bot"); ++ const settings = wide ? (0, import_react9.createElement)("button", { + type: "button", + className: "bt-row-settings", -+ "aria-label": t("Bot settings"), -+ title: t("Bot settings"), ++ "aria-label": settingsLabel, ++ title: settingsLabel, + onClick: (event) => { + event.stopPropagation(); + actions.openSettings("bots", id);