fix(bot-mode): highlight selected group chat
Signed-off-by: Shawn Wang <32839114+enwaiax@users.noreply.github.com>
This commit is contained in:
@@ -4659,13 +4659,16 @@ function activeBots(roster, activeProfile, gatewayState, now = Date.now()) {
|
||||
function BotRow({ bot, onDelete, onEdit, onGroup }) {
|
||||
const activeProfile = useValue(host.state.profile)
|
||||
const focusedProfile = useValue($focusedBotProfile)
|
||||
const activeGroup = useValue($groupChatWorkspace)
|
||||
const meta = botRosterMeta(bot, useValue($botMeta))
|
||||
const groups = botGroups(meta)
|
||||
const last = bot.last_session
|
||||
// Highlight follows the chat on screen (focused session's owner), not the
|
||||
// gateway socket's home — a focused tab doesn't swap the socket, and on the
|
||||
// old keying the wrong bot stayed highlighted while you read another's chat.
|
||||
const isActive = !bot.remoteSource && bot.name === focusedProfile
|
||||
// A selected group chat suppresses every bot-row highlight: the group row
|
||||
// owns the selection then (#88979).
|
||||
const isActive = !activeGroup && !bot.remoteSource && bot.name === focusedProfile
|
||||
// Turn-busy is a SOCKET fact: only the gateway-home profile can be mid-turn.
|
||||
const isGatewayHome = !bot.remoteSource && bot.name === activeProfile
|
||||
const { shape, color, image } = botAppearance(bot.name, meta)
|
||||
@@ -4722,6 +4725,7 @@ function BotRow({ bot, onDelete, onEdit, onGroup }) {
|
||||
const open = async () => {
|
||||
const generation = ++botOpenGeneration
|
||||
haptic('tap')
|
||||
$groupChatWorkspace.set(null)
|
||||
$selectedBot.set(bot.name)
|
||||
|
||||
if (bot.remoteSource) {
|
||||
@@ -9137,6 +9141,10 @@ function closeGroupChatMainTab(group) {
|
||||
|
||||
groupChatMainTabs.delete(group)
|
||||
|
||||
if ($groupChatWorkspace.get() === group) {
|
||||
$groupChatWorkspace.set(null)
|
||||
}
|
||||
|
||||
if (typeof close === 'function') {
|
||||
try {
|
||||
close()
|
||||
@@ -9164,6 +9172,7 @@ function GroupChatMainView({ group }) {
|
||||
* view on desktops whose SDK predates the main-area door. */
|
||||
function openGroupChat(group) {
|
||||
$groupNeedsYou.set({ ...$groupNeedsYou.get(), [group]: false })
|
||||
$groupChatWorkspace.set(group)
|
||||
|
||||
if (typeof host.openWorkspace === 'function') {
|
||||
try {
|
||||
@@ -9171,7 +9180,13 @@ function openGroupChat(group) {
|
||||
title: group,
|
||||
minWidth: '24rem',
|
||||
render: () => jsx(GroupChatMainView, { group }),
|
||||
onClose: () => groupChatMainTabs.delete(group)
|
||||
onClose: () => {
|
||||
groupChatMainTabs.delete(group)
|
||||
|
||||
if ($groupChatWorkspace.get() === group) {
|
||||
$groupChatWorkspace.set(null)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
groupChatMainTabs.set(group, close)
|
||||
@@ -9182,7 +9197,8 @@ function openGroupChat(group) {
|
||||
}
|
||||
}
|
||||
|
||||
$groupChatWorkspace.set(group)
|
||||
// The selected-group atom was set before trying the main-window door, so
|
||||
// older desktops naturally render the in-panel room as the fallback.
|
||||
}
|
||||
|
||||
/** One group chat as ONE roster row — the Discord shape: stacked member
|
||||
@@ -9190,7 +9206,7 @@ function openGroupChat(group) {
|
||||
* (markdown flattened), relative time of the last activity, and the
|
||||
* needs-you badge on the row itself. Sorts into the same recency ordering
|
||||
* as bot rows; clicking opens the room in the main chat window. */
|
||||
function GroupRow({ group, members, needsYou, onOpen }) {
|
||||
function GroupRow({ active, group, members, needsYou, onOpen }) {
|
||||
const rooms = useValue($groupChats)
|
||||
const allMeta = useValue($botMeta)
|
||||
const room = rooms[group] || { log: [] }
|
||||
@@ -9214,7 +9230,8 @@ function GroupRow({ group, members, needsYou, onOpen }) {
|
||||
},
|
||||
className: cn(
|
||||
'flex w-full min-w-0 max-w-full items-center gap-2.5 overflow-hidden rounded-md px-2 py-2 text-left transition-colors',
|
||||
'hover:bg-(--chrome-action-hover)'
|
||||
'hover:bg-(--chrome-action-hover)',
|
||||
active && 'bg-(--ui-row-active-background)'
|
||||
),
|
||||
children: [
|
||||
// Room picture when the user set one; else a composite avatar of up to
|
||||
@@ -9639,6 +9656,7 @@ function BotsPane() {
|
||||
? jsx(
|
||||
GroupRow,
|
||||
{
|
||||
active: groupChatName === row.name,
|
||||
group: row.name,
|
||||
members: row.members,
|
||||
needsYou: Boolean(groupNeedsYou[row.name]),
|
||||
|
||||
@@ -93,7 +93,7 @@ function load(turnScript) {
|
||||
.replace(/^import .* from 'react\/jsx-runtime'\r?\n/m, '')
|
||||
.replace('export default {', 'globalThis.plugin = {')
|
||||
.concat(
|
||||
'\nglobalThis.__gc = { sendToGroupChat, runGroupChatRounds, harvestStrandedGroupReply, resolveGroupResponders, parseGroupChatMentions, rotateGroupSpeakers, isGroupPassText, formatGroupChatLine, buildGroupChatTurnPrompt, trimGroupChatLog, disbandGroupChat, updateGroupChat, $groupChats, $groupNeedsYou, $groupChatWorkspace, $botMeta, GROUP_CHAT_MAX_ROUNDS, GROUP_CHAT_MAX_MESSAGES };\n'
|
||||
'\nglobalThis.__gc = { sendToGroupChat, runGroupChatRounds, harvestStrandedGroupReply, resolveGroupResponders, parseGroupChatMentions, rotateGroupSpeakers, isGroupPassText, formatGroupChatLine, buildGroupChatTurnPrompt, trimGroupChatLog, disbandGroupChat, updateGroupChat, openGroupChat, closeGroupChatMainTab, $groupChats, $groupNeedsYou, $groupChatWorkspace, $botMeta, GROUP_CHAT_MAX_ROUNDS, GROUP_CHAT_MAX_MESSAGES };\n'
|
||||
)
|
||||
vm.runInNewContext(source, context, { filename: 'plugin.js' })
|
||||
const storageWrites = new Map()
|
||||
@@ -101,7 +101,7 @@ function load(turnScript) {
|
||||
storage: { get: () => null, set: (key, value) => storageWrites.set(key, value) },
|
||||
register: () => undefined
|
||||
})
|
||||
return { ...context.__gc, calls, sessions, storageWrites }
|
||||
return { ...context.__gc, calls, host: context.host, sessions, storageWrites }
|
||||
}
|
||||
|
||||
const MEMBERS = [{ name: 'research', title: '' }, { name: 'builder', title: '' }, { name: 'ops', title: 'The Ops' }]
|
||||
@@ -344,6 +344,39 @@ test('source contract: workspace + main-window door + prompt rules are wired', (
|
||||
assert.match(pluginSource, /\[Group chat: "\$\{groupName\}"\]/)
|
||||
})
|
||||
|
||||
test('group selection follows main-window open and close', () => {
|
||||
const gc = load(() => '(pass)')
|
||||
let onClose
|
||||
|
||||
gc.host.openWorkspace = (_id, options) => {
|
||||
onClose = options.onClose
|
||||
return () => onClose()
|
||||
}
|
||||
|
||||
gc.openGroupChat('Core')
|
||||
assert.equal(gc.$groupChatWorkspace.get(), 'Core')
|
||||
|
||||
onClose()
|
||||
assert.equal(gc.$groupChatWorkspace.get(), null)
|
||||
})
|
||||
|
||||
test('closing an older selected group does not clear the newer selection', () => {
|
||||
const gc = load(() => '(pass)')
|
||||
|
||||
gc.host.openWorkspace = () => () => undefined
|
||||
gc.openGroupChat('Core')
|
||||
gc.openGroupChat('Ops')
|
||||
gc.closeGroupChatMainTab('Core')
|
||||
|
||||
assert.equal(gc.$groupChatWorkspace.get(), 'Ops')
|
||||
})
|
||||
|
||||
test('source contract: active group styling suppresses bot styling', () => {
|
||||
assert.match(pluginSource, /const isActive = !activeGroup && !bot\.remoteSource && bot\.name === focusedProfile/)
|
||||
assert.match(pluginSource, /active && 'bg-\(--ui-row-active-background\)'/)
|
||||
assert.match(pluginSource, /active: groupChatName === row\.name/)
|
||||
})
|
||||
|
||||
test('disband: removes only this membership, room log, workspace, and needs-you state', async () => {
|
||||
const gc = load(() => '(pass)')
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ function renderBotRow(input = 'alpha') {
|
||||
$botMeta: atom({}),
|
||||
$botUnread: atom({}),
|
||||
$focusedBotProfile: atom('default'),
|
||||
$groupChatWorkspace: atom(null),
|
||||
$lastRoster: atom([]),
|
||||
$selectedBot: atom('default'),
|
||||
botAppearance: () => ({ shape: 'round', color: '#000', image: null }),
|
||||
@@ -170,6 +171,7 @@ test('behavior: remote default does not open this-device chat when the source di
|
||||
$botMeta: atom({ default: { chat: 'this-device-chat' } }),
|
||||
$botUnread: atom({}),
|
||||
$focusedBotProfile: atom('default'),
|
||||
$groupChatWorkspace: atom(null),
|
||||
$lastRoster: atom([]),
|
||||
$selectedBot: atom('default'),
|
||||
botAppearance: () => ({ shape: 'round', color: '#000', image: null }),
|
||||
|
||||
Reference in New Issue
Block a user