@sparkelf/dsh-group-chat 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["styles","useState","useRef","useCallback","styles","Input","useState","styles","Menu","useState","Modal","styles","Button","Input","Switch","useState","useRef","Modal","invite","styles","Button","Input","IconLoadingOutlineRegular","IconCheckOutlineRegular","styles","Tooltip","IconQueueOutlineRegular","useSyncExternalStore","useState","useRef","useCallback","IconSearchOutlineRegular","IconCloseFillRegular","IconProjectAddOutlineRegular","createPortal","Modal","Button","IconSlidersTwoOutlineRegular","IconClockOutlineRegular","IconChevronsUpDownOutlineRegular","styles","useSyncExternalStore","createElement","useMemo","styles","useState","useCallback","Button","useRef","Input","styles","useSyncExternalStore","styles"],"sources":["../src/client/api.ts","../src/client/rows.ts","../src/client/Avatar.tsx","../src/client/Picker.tsx","../src/client/PresetSelect.tsx","../src/client/store.ts","../src/client/CreateRoomDialog.tsx","../src/client/RoomDialogs.tsx","../src/client/SidebarRooms.tsx","../src/client/quote.ts","../src/client/MessageSide.tsx","../src/session-id.ts","../src/client/MembersPanel.tsx","../src/client/GuideEntry.tsx","../src/client/QuoteComposer.tsx","../src/client/messageReference.ts","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["/**\n * Room-page transport.\n *\n * The browser talks only to the Host plugin's own route. The Host holds the\n * workspace credential and forwards to the server under the workspace identity, so\n * this module never sees a token and cannot disagree with the agent about who the\n * caller is.\n *\n * @module @sparkelf/dsh-group-chat/client/api\n */\n\n/** The Host route registered by this plugin's own Host half. */\nexport const GC_API = '/dsh-group-chat/api'\n\n/**\n * One event as the server stores it: the DSH envelope verbatim, with no\n * plugin-specific fields added.\n */\nexport interface RoomEvent {\n readonly id: string\n readonly event: {\n readonly type: string\n readonly seq?: number\n readonly time?: number\n readonly data?: Record<string, unknown>\n }\n}\n\nexport interface RoomSummary {\n readonly id: string\n readonly name: string\n readonly ownerUserId: string\n readonly invitePolicy: 'anyone' | 'admin'\n readonly discoverability: 'discoverable' | 'invite_only'\n /** The room's reply policy, which decides whether a message starts a turn. */\n readonly agentTrigger: 'mention' | 'always' | 'never'\n /** The room's agent working directory, or null for the workspace root. */\n readonly cwd: string | null\n /** The room's agent preset, or null for the default. */\n readonly agentPreset: string | null\n readonly memberCount: number\n readonly joined: boolean\n /**\n * Messages from others since this account last read the room.\n *\n * Absent on a deployment that predates the field: the sidebar then shows no badge rather\n * than claiming \"0 unread\", which would be indistinguishable from a room nobody wrote in.\n */\n readonly unreadCount?: number\n /** The subset of {@link unreadCount} that mentions this account. */\n readonly mentionCount?: number\n readonly createdAt: string\n readonly updatedAt: string\n /**\n * The room announcement, when this deployment stores one.\n *\n * `undefined` is a third state on purpose: it means the server answering this read has no\n * announcement feature at all (an older deployment, or one rolled back after the plugin\n * shipped), which the panel reports as unavailable. `null` is a feature that is present\n * and has nothing written in it — the distinction is what keeps an empty banner from\n * claiming the feature is broken.\n */\n readonly announcement?: string | null\n}\n\nexport interface RoomMember {\n readonly userId: string\n readonly displayName: string\n readonly owner: boolean\n readonly joinedAt: string\n /** The member's picture, when DataOps has one. Absent means \"draw initials instead\". */\n readonly avatarUrl?: string | null\n /**\n * The member's role, when the server reports one.\n *\n * Absent on a server that predates roles; `owner` above already carries the same fact, so\n * the panel derives the role from it rather than showing every member as ordinary.\n */\n readonly role?: 'owner' | 'member'\n}\n\n/**\n * One account that can still be added to a room.\n *\n * Separate from {@link RoomMember} because it describes a person who is *not* in the room:\n * a candidate carries the handle and picture the picker searches and draws, and no join\n * date or ownership, because neither exists before the invite is accepted.\n */\nexport interface InviteCandidate {\n readonly userId: string\n readonly displayName: string\n readonly username: string\n readonly avatarUrl?: string | null\n}\n\n/** Directory names the room picker can browse, relative to the browsing root. */\nexport interface DirectoryListing {\n readonly path: string\n readonly parent: string | null\n readonly entries: readonly { readonly name: string; readonly path: string }[]\n}\n\n/**\n * One agent preset the deployment offers.\n *\n * `id` is the registry's own identifier — the `config.id` a preset declares, not the filename\n * of the patch that declares it. The two happen to match for every shipped preset, which is\n * exactly why the distinction has to be carried by the type: a deployment that renames a file\n * must not silently offer a name the registry will reject at room creation.\n */\nexport interface PresetOption {\n readonly id: string\n /** Present only for a preset that names itself; a shipped preset's copy is a locale key. */\n readonly name?: string\n readonly description?: string\n readonly order?: number\n /** Whether this is the deployment's default preset. */\n readonly isDefault?: boolean\n /** Non-empty when the preset is declared but could not be activated. */\n readonly broken?: string\n}\n\n/** Preset roster and the directories the create dialog needs. */\nexport interface RoomOptions {\n readonly presets: readonly PresetOption[]\n}\n\nexport interface EventPage {\n readonly events: readonly RoomEvent[]\n readonly nextCursor: string | null\n readonly hasMore: boolean\n}\n\n/** A failure the page renders as copy rather than as an exception. */\nexport class RoomError extends Error {\n constructor(message: string, readonly status: number) {\n super(message)\n this.name = 'RoomError'\n }\n}\n\nconst call = async <T>(path: string, init?: RequestInit): Promise<T> => {\n let response: Response\n try {\n response = await fetch(`${GC_API}${path}`, {\n ...init,\n headers: {\n ...(init?.body === undefined ? {} : { 'content-type': 'application/json' }),\n ...init?.headers,\n },\n // Same-origin: the Host route is served from this page's own origin.\n credentials: 'same-origin',\n })\n } catch (cause) {\n throw new RoomError('unreachable', 0)\n }\n if (!response.ok) {\n // The Host relays the server's own status, so each failure keeps its cause instead of\n // collapsing into one generic sentence. 401 and 403 are distinct from 503: the plugin\n // did resolve a credential and the server refused it, which is what a logged-out or\n // expired DataOps session looks like — a reader who saw \"unreachable\" would go looking\n // at the network instead of signing in again.\n const status = response.status\n if (status === 503) throw new RoomError('credentialMissing', status)\n if (status === 401 || status === 403) throw new RoomError('credentialRejected', status)\n if (status === 404) throw new RoomError('notConfigured', status)\n // 409 is the server refusing a state change it will not make — the owner trying to leave\n // is the one this client can reach, and collapsing it into \"unreachable\" would send the\n // reader to inspect the network instead of transferring ownership.\n if (status === 409) throw new RoomError('ownerCannotLeave', status)\n throw new RoomError('unreachable', status)\n }\n if (response.status === 204) return undefined as T\n return await response.json() as T\n}\n\n/**\n * The account this workspace acts as.\n *\n * The message renderer needs it to tell the operator's own messages from another member's,\n * and the browser holds no credential of its own to ask DataOps directly — the Host resolves\n * it from the same identity that decides message routing, so the two cannot disagree.\n *\n * @returns the account id, or `undefined` when the Host cannot identify this workspace.\n */\nexport const readSelf = async (): Promise<{ id: string; administrator: boolean } | undefined> => {\n try {\n // The host answers `roleCodes`, not the raw DataOps role objects: the client half never\n // talks to DataOps itself, so the host is what carries the account's roles across.\n const me = await call<{ id?: unknown; roleCodes?: unknown }>('/me')\n if (typeof me?.id !== 'string' || me.id.length === 0) return undefined\n // The room server owns the authoritative check; this flag only decides whether to *offer*\n // dissolving a room the reader does not own. These two role names are the ones the\n // deployment grants `system:admin:manage` to by default, so offering the entry matches what\n // the server will accept.\n const administrator = Array.isArray(me.roleCodes)\n && me.roleCodes.some((code) => code === '管理员' || code === '超级管理员')\n return { id: me.id, administrator }\n } catch {\n // An unidentified workspace still shows its rooms; its own messages simply stay where\n // the official renderer puts them rather than being claimed as the operator's.\n return undefined\n }\n}\n\n/** Rooms this workspace has joined. */\nexport const listRooms = (): Promise<readonly RoomSummary[]> => call('/rooms')\n\n/**\n * Rooms this account could join, as opposed to the ones it is already in.\n *\n * Kept separate from {@link listRooms} because the two answer different questions: the sidebar\n * shows what you belong to, while this is consulted only when the operator is looking for\n * something to join. Merging them would make every list read ask for rooms the account has no\n * relationship with.\n *\n * @param query - optional narrowing keyword, applied by the server.\n * @returns matching rooms, each carrying `joined` so the caller can tell them apart.\n */\nexport const listDiscoverableRooms = (query?: string): Promise<readonly RoomSummary[]> => {\n const suffix = query === undefined || query.trim().length === 0\n ? ''\n : `?query=${encodeURIComponent(query.trim())}`\n return call(`/discover${suffix}`)\n}\n\n/**\n * Take a seat in a room the server allows this account to join.\n *\n * The decision to allow it is the server's: it re-reads discoverability, invite policy and\n * membership at the moment of the press, so a room offered a minute ago may still be refused.\n * That refusal arrives as a `RoomError` and is shown as-is rather than being pre-empted here.\n *\n * @param roomId - the room to join.\n * @returns the server's answer, including whether the account was already a member.\n */\nexport const joinRoom = (roomId: string): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/join`, { method: 'POST' })\n\nexport const listMembers = (roomId: string): Promise<readonly RoomMember[]> =>\n call(`/rooms/${encodeURIComponent(roomId)}/members`)\n\n/**\n * Accounts that match `query` and are not yet in the room.\n *\n * The search runs on the Host, which holds the credential: the browser has no DataOps token\n * of its own, so a client-side filter over a full directory is not an option here. An empty\n * query is valid and returns a browsable first page rather than nothing, which is what makes\n * the picker usable before the operator has typed anything.\n */\nexport const searchInviteCandidates = (\n roomId: string,\n query: string,\n): Promise<readonly InviteCandidate[]> => {\n const suffix = query.length === 0 ? '' : `?q=${encodeURIComponent(query)}`\n return call(`/rooms/${encodeURIComponent(roomId)}/invite-candidates${suffix}`)\n}\n\n/** Preset names this deployment offers. An empty list means the picker falls back to typing. */\nexport const readOptions = (): Promise<RoomOptions> => call('/options')\n\n/** Child directories of `path`, for the working-directory picker. */\nexport const listDirectories = (path?: string): Promise<DirectoryListing> => {\n const suffix = path === undefined || path.length === 0 ? '' : `?path=${encodeURIComponent(path)}`\n return call(`/directories${suffix}`)\n}\n\n/**\n * Whether the host has this room's session up.\n *\n * The host creates a room's session on its own poll, which starts when the plugin does. A\n * member who opens a room before that poll reaches it makes the shell retain an id the host\n * has not created yet — the open then fails with `sessions.retain: unknown session`, and the\n * room shows its history against a session scope nothing can write into.\n */\nexport interface RoomState {\n readonly roomId: string\n readonly open: boolean\n readonly memberCount: number\n}\n\nexport const readRoomState = (roomId: string): Promise<RoomState> =>\n call<RoomState>(`/rooms/${encodeURIComponent(roomId)}/state`)\n\nexport const listEvents = (roomId: string, cursor?: string): Promise<EventPage> => {\n const suffix = cursor === undefined ? '' : `?cursor=${encodeURIComponent(cursor)}`\n return call(`/rooms/${encodeURIComponent(roomId)}/events${suffix}`)\n}\n\n/**\n * Create a room.\n *\n * `cwd` and the preset are sent at creation because they decide the scope every member\n * will share; the server does not accept them afterwards.\n */\nexport const createRoom = (\n name: string,\n execution?: { cwd?: string; agentPreset?: string },\n): Promise<RoomSummary> =>\n call('/rooms', { method: 'POST', body: JSON.stringify({ name, ...execution }) })\n\n/**\n * Set or clear a room's announcement.\n *\n * Only the room owner may write it; the server decides that, so a refusal is surfaced as-is\n * rather than pre-empted here. An empty string clears the announcement — the server stores\n * that as no announcement, which is why the panel treats `''` and `null` alike when reading.\n *\n * @param roomId - the room whose announcement to write.\n * @param announcement - the new text, or an empty string to clear it.\n */\nexport const setAnnouncement = (roomId: string, announcement: string): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/announcement`, {\n method: 'POST',\n body: JSON.stringify({ announcement }),\n })\n\nexport const updateRoom = (\n roomId: string,\n patch: { name?: string; invitePolicy?: string; discoverability?: string; agentTrigger?: string },\n): Promise<RoomSummary> =>\n call(`/rooms/${encodeURIComponent(roomId)}/update`, { method: 'POST', body: JSON.stringify(patch) })\n\n/**\n * Hand ownership of a room to another member.\n *\n * The reason this exists at all is that a room's owner cannot leave it: leaving would strand the\n * room with nobody able to change its settings. Transferring is the way out, so the server owns\n * the rule (owner only, target must be a member) and this only carries the request.\n */\nexport const transferOwnership = (roomId: string, userId: string): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/transfer`, {\n method: 'POST',\n body: JSON.stringify({ userId }),\n })\n\nexport const inviteMembers = (roomId: string, userIds: readonly string[]): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/members`, {\n method: 'POST',\n body: JSON.stringify({ userIds }),\n })\n\nexport const leaveRoom = (roomId: string): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/members/me`, { method: 'DELETE' })\n\n/** One file in a room's shared folder. */\nexport interface SharedFile {\n readonly path: string\n readonly name: string\n readonly size: number\n readonly directory: boolean\n}\n\n/** The shared folder of one room. */\n/**\n * The group-chat UI preferences this workspace remembers.\n *\n * Read from and written to a file in this workspace's own volume, so they follow the user\n * across re-logins and browsers rather than living in one browser's storage.\n */\nexport interface GroupChatPreferences {\n /** Height of the sidebar's group-chat region, in pixels. */\n readonly sidebarHeightPx?: number\n}\n\n/** Reads the stored preferences. Never fails: an unreadable file reads as \"nothing saved\". */\nexport const readPreferences = async (): Promise<GroupChatPreferences> => {\n try {\n return await call<GroupChatPreferences>('/preferences')\n } catch {\n // The panel's height is not worth surfacing an error for; the default is a fine answer.\n return {}\n }\n}\n\n/** Stores the preferences and returns what the host settled on, clamped. */\nexport const savePreferences = (patch: GroupChatPreferences): Promise<GroupChatPreferences> =>\n call<GroupChatPreferences>('/preferences', {\n method: 'PATCH',\n body: JSON.stringify(patch),\n })\n\nexport interface RoomFiles {\n readonly path: string | null\n /** Where this listing is, relative to the room folder. Empty at the top. */\n readonly relativePath?: string\n /** One entry per level from the room folder down to here. */\n readonly crumbs?: readonly { readonly name: string; readonly path: string }[]\n readonly files: readonly SharedFile[]\n}\n\n/**\n * List a room's shared folder.\n *\n * An empty `path` means this deployment has no shared tree, which the panel renders as\n * \"not available\" rather than as an empty folder — the two read differently to a member.\n */\nexport const listRoomFiles = (roomId: string, path?: string): Promise<RoomFiles> =>\n call(`/rooms/${encodeURIComponent(roomId)}/files${\n path === undefined || path.length === 0 ? '' : `?path=${encodeURIComponent(path)}`\n }`)\n\n/** One shared file's text, as the preview shows it. */\nexport interface SharedFileText {\n readonly path: string\n readonly name: string\n readonly text: string\n readonly truncated: boolean\n readonly textLike: boolean\n /** Data URL when the file is an image the panel can draw inline. */\n readonly image?: string\n}\n\n/** Read one file out of a room's shared folder. */\nexport const getRoomFile = (roomId: string, path: string): Promise<SharedFileText> =>\n call(`/rooms/${encodeURIComponent(roomId)}/file-content?path=${encodeURIComponent(path)}`)\n\n/** Where an upload landed. */\nexport interface SharedFileWrite {\n readonly path: string\n readonly name: string\n readonly size: number\n}\n\n/**\n * Upload one file into a room's shared folder.\n *\n * Base64 in JSON rather than multipart: the plugin's request path reads one JSON envelope for\n * every operation, and a second body format would need its own parser, limits, and error\n * mapping for no gain.\n *\n * @param roomId - the room whose folder receives the file.\n * @param file - the browser's File object.\n * @returns the written file.\n */\nexport const uploadRoomFile = async (roomId: string, file: File): Promise<SharedFileWrite> => {\n const buffer = new Uint8Array(await file.arrayBuffer())\n // Chunked: `String.fromCharCode(...bytes)` overflows the argument limit on a file of a few\n // hundred kilobytes, and the spread is what makes the one-liner tempting.\n let binary = ''\n for (let index = 0; index < buffer.length; index += 1) {\n binary += String.fromCharCode(buffer[index] as number)\n }\n return call(`/rooms/${encodeURIComponent(roomId)}/files`, {\n method: 'POST',\n body: JSON.stringify({ name: file.name, data: btoa(binary) }),\n })\n}\n\n/**\n * Remove one member from a room.\n *\n * The reserved word `me` keeps its meaning: removal always names another account, and the\n * server rejects the owner as a target. Leaving therefore never goes through this call.\n */\nexport const removeMember = (roomId: string, userId: string): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/members/${encodeURIComponent(userId)}`, { method: 'DELETE' })\n\n/**\n * Dissolve a room outright.\n *\n * Distinct from {@link leaveRoom}: leaving removes one membership and the room keeps existing\n * for everyone else, while dissolving ends the room for every member. The server decides who\n * may do it (the owner, or an administrator), so a refusal surfaces as copy rather than being\n * pre-empted here.\n */\nexport const dissolveRoom = (roomId: string): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}`, { method: 'DELETE' })\n\n/**\n * Send one message.\n *\n * `rpcId` is the idempotency key: the server keys a retry on it, so a send that\n * succeeded but whose response was lost does not post twice.\n */\n/** One content block, as the room server stores it. */\nexport interface ContentBlock {\n readonly type: string\n readonly [key: string]: unknown\n}\n\n/**\n * Sends a message that carries more than text.\n *\n * The server's block type is open (`{ type: string; ... }`), so a quote rides through as its own\n * block instead of being flattened into the text — flattening would lose the message id the\n * renderer needs to draw the bar, and would make the quote look like something the author typed.\n */\n/** One message the room search matched, with enough context to open it. */\nexport interface MessageHit {\n readonly eventId: string\n readonly roomId: string\n readonly roomName: string\n readonly authorName: string | null\n readonly authorKind: 'human' | 'agent' | null\n readonly text: string\n readonly createdAt: string\n}\n\n/**\n * Searches message text across the rooms this account can read.\n *\n * The room server owns the visibility rule; this only passes the words. An empty query is not\n * sent at all — every message would match it.\n */\nexport const searchMessages = (query: string): Promise<MessageHit[]> => {\n const trimmed = query.trim()\n if (trimmed.length === 0) return Promise.resolve([])\n return call<MessageHit[]>(`/rooms/messages/search?query=${encodeURIComponent(trimmed)}`)\n}\n\n/**\n * Registers a pending reply with the host.\n *\n * The message itself is submitted by the official composer, which this plugin neither wraps nor\n * intercepts, so the quote travels beside it rather than inside it: the host attaches it to the\n * next message this member sends in the room and then forgets it.\n */\nexport const registerQuote = (\n roomId: string,\n quote: { readonly messageId: string; readonly preview: string; readonly authorName?: string },\n): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/quote`, {\n method: 'POST',\n body: JSON.stringify(quote),\n })\n\n/** Drops a pending reply that will not be sent. */\nexport const clearQuote = (roomId: string): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/quote`, { method: 'DELETE' })\n\nexport const sendBlocks = (\n roomId: string,\n content: readonly ContentBlock[],\n rpcId: string,\n mentions?: readonly string[],\n): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/messages`, {\n method: 'POST',\n body: JSON.stringify({\n content,\n rpcId,\n ...mentions === undefined || mentions.length === 0 ? {} : { mentions },\n }),\n })\n\nexport const sendMessage = (roomId: string, content: string, rpcId: string): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/messages`, {\n method: 'POST',\n body: JSON.stringify({ content, rpcId }),\n })\n\n/** Text carried by an event, ignoring any non-text block. */\nexport const eventText = (record: RoomEvent): string => {\n const content = (record.event.data as { content?: unknown } | undefined)?.content\n if (!Array.isArray(content)) return ''\n return content\n .filter((block): block is { type: string; text?: string } =>\n typeof block === 'object' && block !== null && (block as { type?: string }).type === 'text')\n .map((block) => block.text ?? '')\n .join('')\n}\n\n/** Who wrote an event, and whether an agent wrote it. */\nexport const eventAuthor = (record: RoomEvent): { name: string; isAgent: boolean; isSelf: boolean } => {\n const source = (record.event.data as { source?: Record<string, unknown> } | undefined)?.source ?? {}\n return {\n name: String(source.memberName ?? source.agentOwnerId ?? '—'),\n isAgent: source.authorKind === 'agent',\n isSelf: source.isSelf === true,\n }\n}\n","/**\n * The decisions the plugin's components make without a browser.\n *\n * They live outside their components on purpose. The components import the official\n * primitives, which bring CSS modules that this repository's test runner cannot transform,\n * so anything worth asserting has to be reachable without mounting a component. That\n * constraint turns out to match the risk: what an operator sees — which rows survive a\n * query, what a picture falls back to, who carries the owner tag, whether an empty\n * announcement area means \"write one\" or \"this deployment cannot store one\" — is exactly\n * what goes wrong quietly, and it is all decidable from plain values.\n *\n * @module @sparkelf/dsh-group-chat/client/rows\n */\n\nimport {\n RoomError, type InviteCandidate, type PresetOption, type RoomMember, type RoomSummary,\n} from './api.ts'\n\n/** One row a picker can draw. */\nexport interface PickerOption {\n readonly id: string\n readonly label: string\n /** What the operator searches by when the label is a display name. */\n readonly secondary?: string\n readonly avatarUrl?: string | null\n /** Initials and tint are keyed on this when it differs from the row's result id. */\n readonly avatarId?: string\n /**\n * Whether the row is shown but cannot be taken.\n *\n * Shown, not hidden: a row that is absent leaves the operator unable to tell a deployment\n * that never declared it from one that declared it unusable.\n */\n readonly disabled?: boolean\n}\n\n/**\n * The locale namespace the shipped presets publish their names in.\n *\n * Read as a foreign namespace rather than one this plugin registers: `standard` and the other\n * three are declared by the platform, and their copy is owned there. Reprinting that copy here\n * would freeze today's wording and drift from the hero chip the moment the platform changes it.\n */\nexport const PRESET_LOCALE_NS = 'settings.agentPreset'\n\n/** Locale keys the shipped presets use, keyed by the id the registry publishes. */\nconst BUILT_IN_PRESET_KEYS: Readonly<Record<string, { name: string; description: string }>> = {\n standard: { name: 'presetStandardName', description: 'presetStandardDescription' },\n ptc: { name: 'presetPtcName', description: 'presetPtcDescription' },\n minimal: { name: 'presetMinimalName', description: 'presetMinimalDescription' },\n cordis: { name: 'presetCordisName', description: 'presetCordisDescription' },\n}\n\n/**\n * A preset's display copy, following the platform's own rule.\n *\n * A shipped preset publishes no `name`, so its copy is a locale lookup; a preset that names\n * itself in YAML owns its own words and is never translated. The split matters because the two\n * arrive as the same `{ id, name? }` shape — treating a published name as a locale key would\n * render the key itself, and treating a shipped id as self-named would render `standard`.\n *\n * @param preset - one roster row.\n * @param t - lookup for the preset locale namespace; a missing key resolves to the key, so the\n * fallback below is what keeps a renamed platform key from reaching the operator.\n * @returns the name and optional description to draw.\n */\nexport const presetDisplay = (\n preset: PresetOption,\n t: (key: string) => string,\n): { readonly name: string; readonly description?: string } => {\n const keys = preset.name === undefined ? BUILT_IN_PRESET_KEYS[preset.id] : undefined\n if (keys === undefined) {\n return {\n name: preset.name ?? preset.id,\n ...preset.description === undefined ? {} : { description: preset.description },\n }\n }\n // An unresolved key comes back as itself; showing `presetStandardName` would be worse than\n // showing the id, so the id wins whenever the lookup did not actually resolve.\n const name = t(keys.name)\n const description = t(keys.description)\n return {\n name: name === keys.name ? preset.id : name,\n ...description === keys.description ? {} : { description },\n }\n}\n\n/**\n * Roster rows as picker rows, optional-broken preset included.\n *\n * A broken preset stays visible and disabled rather than disappearing: it is declared in the\n * deployment, so an operator who cannot see it has no way to learn why the room they are\n * about to create will not start. Selection is expressed through `primary`, which the caller\n * uses to keep the broken row out of reach.\n *\n * @param presets - roster rows in the order the Host reported them.\n * @param t - lookup for the preset locale namespace.\n * @returns picker rows, one per preset.\n */\nexport const presetOptions = (\n presets: readonly PresetOption[],\n t: (key: string) => string,\n): readonly PickerOption[] =>\n presets.map((preset) => {\n const { name, description } = presetDisplay(preset, t)\n return {\n id: preset.id,\n label: name,\n ...description === undefined ? {} : { secondary: description },\n ...preset.broken === undefined ? {} : { disabled: true },\n }\n })\n\n/**\n * Filter rows by what the operator typed.\n *\n * Matching happens here rather than only at the source because the source's answer arrives a\n * debounce behind the keystroke; re-filtering the rows already held narrows the list on the\n * same frame instead of one request later. `secondary` participates because a member's handle\n * is often what the operator knows them by.\n *\n * @param options - the rows currently held.\n * @param query - the raw input.\n * @returns the rows to draw, in their original order.\n */\nexport const filterOptions = (\n options: readonly PickerOption[],\n query: string,\n): readonly PickerOption[] => {\n const needle = query.trim().toLowerCase()\n if (needle.length === 0) return options\n return options.filter((option) =>\n option.label.toLowerCase().includes(needle)\n || (option.secondary ?? '').toLowerCase().includes(needle))\n}\n\n/**\n * Add or remove one person from the invite selection.\n *\n * The selection is kept apart from the search result rows because the two answer different\n * questions: the rows are \"what the current query found\", while the selection is \"who will be\n * invited\". A narrowed query legitimately hides a person already picked, and deriving the\n * selection from the visible rows would drop them the moment the operator typed another name.\n *\n * The row is stored whole, not by id, so a chip can still draw the name and picture after the\n * search that produced it has been replaced.\n *\n * @param chosen - the current selection, in pick order.\n * @param candidate - the row the operator pressed.\n * @returns the new selection; a second press on the same id removes it, and identity is stable\n * when the press changed nothing.\n */\nexport const toggleChosen = <T extends { readonly userId: string }>(\n chosen: readonly T[],\n candidate: T,\n): readonly T[] =>\n chosen.some((member) => member.userId === candidate.userId)\n ? chosen.filter((member) => member.userId !== candidate.userId)\n : [...chosen, candidate]\n\n/**\n * Narrow the invite candidates to what the operator typed.\n *\n * The same rule the server applies, applied again to the rows already held, for one reason:\n * the server's answer arrives a debounce behind the keystroke, and the list would otherwise\n * sit still while the operator types. Re-filtering locally makes the narrowing immediate; the\n * server request still happens, and its answer replaces these rows a moment later.\n *\n * Both the display name and the handle participate, because the handle is often what a\n * colleague is known by — the server's search matches the same pair.\n *\n * @param candidates - the rows currently held, from the last answer.\n * @param query - the raw input.\n * @returns the rows to draw, in their original order.\n */\nexport const filterCandidates = (\n candidates: readonly InviteCandidate[],\n query: string,\n): readonly InviteCandidate[] => {\n const needle = query.trim().toLowerCase()\n if (needle.length === 0) return candidates\n return candidates.filter((candidate) =>\n candidate.displayName.toLowerCase().includes(needle)\n || candidate.username.toLowerCase().includes(needle))\n}\n\n/**\n * Whether one candidate row is part of the selection.\n *\n * @param chosen - the current selection.\n * @param userId - the row's account id.\n * @returns whether the row draws as selected.\n */\nexport const isChosen = (\n chosen: readonly { readonly userId: string }[],\n userId: string,\n): boolean => chosen.some((member) => member.userId === userId)\n\n/**\n * Up to two initials from a display name.\n *\n * Names arriving here are DataOps display names, which are as often Chinese as Latin. A CJK\n * name has no spaces to split on, so the first two characters are taken as-is; a Latin name\n * takes the first letter of its first two words. Both paths return at most two glyphs, which\n * is what keeps the circle from overflowing at either size.\n *\n * @param name - the display name, possibly empty.\n * @returns one or two characters, or `'?'` when the name carries nothing to draw.\n */\nexport const initialsOf = (name: string): string => {\n const trimmed = name.trim()\n if (trimmed.length === 0) return '?'\n // CJK ranges: a leading character here means the name is written without word spaces.\n if (/^[\\u3400-\\u9fff\\uf900-\\ufaff]/u.test(trimmed)) return [...trimmed].slice(0, 2).join('')\n const words = trimmed.split(/\\s+/u).filter((word) => word.length > 0)\n if (words.length === 1) return [...words[0]!].slice(0, 2).join('').toUpperCase()\n return (words[0]![0]! + words[1]![0]!).toUpperCase()\n}\n\n\n/**\n * The rooms worth offering as joinable.\n *\n * Two filters, for two different failures. `joined` is belt-and-braces: the Host already strips\n * joined rooms from discovery, but this list arrives from a request the sidebar did not wait\n * for, so a room the operator joined a moment ago can still be in the response.\n *\n * The member-id check covers the other window: the discovery read and the sidebar's own list\n * come from separate requests, so a room can be in both for a moment. Without it the same room\n * would appear twice, and the copy under the discovery heading would offer a Join button that\n * can only fail.\n *\n * @param discoverable - what the discovery read returned.\n * @param memberIds - ids of the rooms the sidebar is already showing.\n * @returns the rooms to render under the discovery heading.\n */\nexport const joinableRooms = (\n discoverable: readonly RoomSummary[],\n memberIds: ReadonlySet<string>,\n): readonly RoomSummary[] =>\n discoverable.filter((room) => !room.joined && !memberIds.has(room.id))\n\n/**\n * The members panel's decisions, made away from the component.\n *\n * Same reason as the pickers above: the panel imports primitives whose CSS modules this\n * repository's test runner cannot transform, so everything worth asserting — who is pinned\n * to the top, who carries a role tag, what a search removes, which of the announcement's\n * three states a room is in — has to be decidable from plain values.\n */\n\n/** One row the members panel draws. */\nexport interface MemberRow {\n readonly userId: string\n readonly name: string\n readonly avatarUrl?: string | null\n /** Whether the row carries the owner tag; plain members are deliberately unmarked. */\n readonly owner: boolean\n /** Whether this row is the operator, marked so they can find themselves in a long roster. */\n readonly self: boolean\n}\n\n/**\n * Whether a member owns the room.\n *\n * `owner` is the field every server has ever sent, `role` is the one the announcement work\n * added. Preferring `role` and falling back to `owner` keeps the tag correct against either\n * server, and costs nothing: the two carry the same fact.\n *\n * @param member - one roster row.\n * @returns whether the row is the room's owner.\n */\nexport const memberIsOwner = (member: RoomMember): boolean =>\n member.role === undefined ? member.owner === true : member.role === 'owner'\n\n/**\n * Roster rows for the panel, owner pinned first.\n *\n * Only the owner is moved. Everyone else keeps the order the server sent, which is join\n * order — a roster re-sorted alphabetically would scramble the one fact a reader can\n * actually see in it (who has been here longest), and would do so differently for a CJK\n * name than for a Latin one.\n *\n * @param members - the roster as the store holds it.\n * @param selfId - this workspace's account id, `undefined` while identity is still unknown.\n * @returns rows to draw.\n */\nexport const memberRows = (\n members: readonly RoomMember[],\n selfId: string | undefined,\n): readonly MemberRow[] => {\n const rows = members.map((member): MemberRow => ({\n userId: member.userId,\n name: member.displayName.length > 0 ? member.displayName : member.userId,\n avatarUrl: member.avatarUrl,\n owner: memberIsOwner(member),\n self: selfId !== undefined && member.userId === selfId,\n }))\n // `Array.prototype.sort` is stable, so the non-owners keep the server's order.\n return rows.slice().sort((left, right) => Number(right.owner) - Number(left.owner))\n}\n\n/**\n * Narrow the roster to what the operator typed.\n *\n * The account id participates because it is what another member is often known by — the\n * invite picker searches the handle for the same reason.\n *\n * @param rows - the roster rows currently held.\n * @param query - the raw input.\n * @returns rows to draw, in their order.\n */\nexport const filterMemberRows = (\n rows: readonly MemberRow[],\n query: string,\n): readonly MemberRow[] => {\n const needle = query.trim().toLowerCase()\n if (needle.length === 0) return rows\n return rows.filter((row) =>\n row.name.toLowerCase().includes(needle) || row.userId.toLowerCase().includes(needle))\n}\n\n/**\n * What the announcement area has to draw, as one of exactly three states.\n *\n * The middle state matters: a feature that is present and empty must offer to add an\n * announcement, while a deployment that never shipped the field must not pretend the\n * operator can write one. `undefined` is the absence of the field, `null` or a blank string\n * is the empty one — so the distinction has to be made here, once, rather than by every\n * caller.\n */\nexport type AnnouncementView =\n | { readonly state: 'unavailable' }\n | { readonly state: 'empty' }\n | { readonly state: 'text'; readonly text: string }\n\n/**\n * Read one room's announcement into the state its area draws.\n *\n * @param announcement - the room summary's field.\n * @returns the state to render.\n */\nexport const announcementView = (announcement: string | null | undefined): AnnouncementView => {\n if (announcement === undefined) return { state: 'unavailable' }\n // `null` and a blank string are the same fact to the reader: the feature is here and\n // nothing is written in it. The server reads a cleared announcement back as `null`.\n const text = (announcement ?? '').trim()\n return text.length === 0 ? { state: 'empty' } : { state: 'text', text }\n}\n\n/**\n * Whether a failed write means this deployment has no announcement feature.\n *\n * A rolled-back deployment answers the route with 404 (no route) or 501/405 (a route that\n * refuses the method). Those are the only failures that should switch the area to\n * \"unavailable\": a 401 or a 5xx is this request's problem, and reporting it as a missing\n * feature would send the operator looking at the wrong thing.\n *\n * @param error - whatever the write threw.\n * @returns whether the feature should be regarded as absent.\n */\nexport const announcementMissing = (error: unknown): boolean =>\n error instanceof RoomError\n && (error.status === 404 || error.status === 405 || error.status === 501)\n\n/**\n * The announcement text a save should send.\n *\n * Trimming here rather than in the component keeps \"clearing is allowed\" a stated rule: the\n * server reads an empty string as \"remove the announcement\", and a field holding only spaces\n * must mean the same thing rather than storing invisible text.\n *\n * @param draft - what the editor holds.\n * @returns the body to send, empty when the announcement should be cleared.\n */\nexport const announcementDraft = (draft: string): string => draft.trim()\n","/**\n * A member's picture, with a stable stand-in when there is none.\n *\n * The official component set ships no avatar, so the two places a person is drawn — the\n * member roster and the invite picker — would otherwise each invent their own. Keeping one\n * component means the fallback, the size and the accessibility name cannot drift apart\n * between them.\n *\n * The initials are computed in `rows.ts`, which is what lets them be tested without mounting\n * this component.\n *\n * The fallback is deliberately not a silhouette: DataOps has pictures for only some accounts, so\n * most rows land on initials, and a generic icon repeated down a list reads as \"broken image\"\n * rather than as a person. The circle is the portal's own blue with white initials, so a member\n * looks the same here as beside their name on the DataOps pages.\n *\n * @module @sparkelf/dsh-group-chat/client/Avatar\n */\n\nimport type { ReactNode } from 'react'\nimport { initialsOf } from './rows.ts'\nimport styles from './Avatar.module.css'\n\n/** Sizes the callers use; the class is chosen here so the CSS owns the dimensions. */\nexport type AvatarSize = 'sm' | 'md'\n\nexport interface AvatarProps {\n /** The name to draw initials from and to describe the picture with. */\n readonly name: string\n /** The picture's URL, when DataOps has one. */\n readonly src?: string | null\n readonly size?: AvatarSize\n}\n\n/** One member's picture, or their initials when there is no picture to show. */\nexport const Avatar = (props: AvatarProps): ReactNode => {\n const { name, src, size = 'md' } = props\n const className = `${styles['avatar']} ${size === 'sm' ? styles['sm'] : styles['md']}`\n const fallback = (\n <span className={className} role=\"img\" aria-label={name}>\n {initialsOf(name)}\n </span>\n )\n if (typeof src !== 'string' || src.length === 0) return fallback\n // The initials are always rendered underneath and the picture is layered on top, so a\n // picture that fails after the list has painted — an expired remote URL is the common\n // case — uncovers the initials rather than leaving an empty circle.\n return (\n <span className={styles['stack']}>\n {fallback}\n <img\n className={`${styles['avatar']} ${size === 'sm' ? styles['sm'] : styles['md']} ${styles['over']}`}\n src={src}\n alt=\"\"\n aria-hidden=\"true\"\n onError={(event) => { event.currentTarget.style.display = 'none' }}\n />\n </span>\n )\n}\n","/**\n * A searchable list of choices, for the fields that used to be free text.\n *\n * Three of the room dialogs ask for values the deployment already knows — who can be\n * invited, which preset to run, which directory to work in — and each used to be an empty\n * box that only accepted the answer if it was spelled exactly right. A wrong userId failed\n * only after the invite was submitted, and a wrong directory silently became a room the\n * agent could not work in. This component is the shared answer so all three behave alike\n * rather than each growing its own behaviour.\n *\n * It is deliberately not a `<datalist>`: the options come from an async source, the row\n * needs a picture and a secondary line, and a datalist cannot group or report why it is\n * empty. It is also not built on the official primitives because none of them is a\n * combobox — the package ships Button, Input, Menu and friends, but no searchable select.\n *\n * @module @sparkelf/dsh-group-chat/client/Picker\n */\n\nimport { useCallback, useEffect, useRef, useState, type KeyboardEvent, type ReactNode } from 'react'\nimport { Input } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { Avatar } from './Avatar.tsx'\nimport { filterOptions, type PickerOption } from './rows.ts'\nimport styles from './Picker.module.css'\n\n/**\n * Sentinel id for the \"use what I typed\" row.\n *\n * A real id cannot collide with it because account ids, preset names and paths are all\n * printable, so a NUL is outside every id space this component is handed.\n */\nconst TYPED_ROW_ID = '\\u0000typed'\n\nexport interface PickerProps {\n readonly options: readonly PickerOption[]\n readonly placeholder: string\n /** Shown under the input when the source answered with nothing. */\n readonly emptyHint?: string\n /** Shown instead of the list while the source is in flight. */\n readonly loadingHint?: string\n readonly loading?: boolean\n /** Called on every keystroke; the caller debounces and owns the query. */\n readonly onQueryChange?: (query: string) => void\n /**\n * Lets the field's own text be committed as a value.\n *\n * Two fields this serves — the working directory and the preset — accept any string\n * server-side, so a picker that could only choose from a list would make valid values\n * unreachable. With this set, a non-empty query offers itself as the first row and Enter\n * commits the typed text instead of falling through to the dialog's submit.\n */\n readonly onTypedCommit?: (text: string) => void\n /** Copy for that first row, given the text it would commit. */\n readonly typedCommitLabel?: (text: string) => string\n /** Enter with no highlighted row, and the click on a row, both land here. */\n readonly onPick: (option: PickerOption) => void\n /** Cleared after each pick, so a picker can be used repeatedly without reselecting. */\n readonly value?: string\n readonly autoFocus?: boolean\n}\n\n/**\n * A text field with a filtered list beneath it.\n *\n * Keyboard support is the reason this is not a plain list of buttons: ↑/↓ move the highlight\n * without leaving the field, Enter takes the highlighted row, and Escape first closes the\n * list and only then reaches the dialog, so a stray Escape while searching does not discard\n * the whole dialog.\n */\nexport const Picker = (props: PickerProps): ReactNode => {\n const {\n options, placeholder, emptyHint, loadingHint, loading = false,\n onQueryChange, onTypedCommit, typedCommitLabel, onPick, value, autoFocus = false,\n } = props\n const [query, setQuery] = useState(value ?? '')\n // A picker that opens empty hides the very choices it exists to offer, so an auto-focused\n // field starts expanded: the operator is looking straight at the list they came for.\n const [open, setOpen] = useState(autoFocus)\n const [active, setActive] = useState(0)\n const boxRef = useRef<HTMLDivElement | null>(null)\n\n // The caller can clear the field (after a pick) without this component losing its own\n // typing state, which is what lets one picker invite several people in a row.\n useEffect(() => { setQuery(value ?? '') }, [value])\n\n // The typed value is offered as a row of its own, ahead of the filtered ones, so a value\n // the deployment does not list stays reachable by the same Enter key as a listed one.\n const typed: PickerOption[] = onTypedCommit !== undefined && query.trim().length > 0\n ? [{ id: TYPED_ROW_ID, label: typedCommitLabel?.(query.trim()) ?? query.trim() }]\n : []\n const visible = [...typed, ...filterOptions(options, query)]\n\n // A shrinking list must not keep a highlight that now points past its end.\n useEffect(() => { setActive(0) }, [query])\n\n const pick = useCallback((option: PickerOption | undefined): void => {\n if (option === undefined) return\n if (option.id === TYPED_ROW_ID) {\n onTypedCommit?.(query.trim())\n return\n }\n onPick(option)\n }, [onPick, onTypedCommit, query])\n\n // Clicking a row must not blur-close the list before the click lands, so the close is\n // driven by an outside pointerdown rather than by the field's blur.\n useEffect(() => {\n if (!open) return\n const onPointerDown = (event: PointerEvent): void => {\n if (boxRef.current !== null && !boxRef.current.contains(event.target as Node)) setOpen(false)\n }\n document.addEventListener('pointerdown', onPointerDown)\n return () => { document.removeEventListener('pointerdown', onPointerDown) }\n }, [open])\n\n const onKeyDown = (event: KeyboardEvent<HTMLInputElement>): void => {\n if (event.key === 'ArrowDown') {\n event.preventDefault()\n setOpen(true)\n setActive((current) => (visible.length === 0 ? 0 : (current + 1) % visible.length))\n return\n }\n if (event.key === 'ArrowUp') {\n event.preventDefault()\n setActive((current) => (visible.length === 0 ? 0 : (current - 1 + visible.length) % visible.length))\n return\n }\n if (event.key === 'Enter') {\n if (open && visible.length > 0) {\n // Enter belongs to the highlighted row while the list is open; the dialog's own\n // submit only runs when there is nothing to pick.\n event.preventDefault()\n pick(visible[active])\n }\n return\n }\n if (event.key === 'Escape' && open) {\n // Consumed here so Escape closes the list first and the dialog second.\n event.preventDefault()\n event.stopPropagation()\n setOpen(false)\n }\n }\n\n return (\n <div className={styles['box']} ref={boxRef}>\n <Input\n value={query}\n autoFocus={autoFocus}\n placeholder={placeholder}\n role=\"combobox\"\n aria-expanded={open}\n aria-autocomplete=\"list\"\n onFocus={() => { setOpen(true) }}\n onChange={(event) => {\n setQuery(event.target.value)\n setOpen(true)\n onQueryChange?.(event.target.value)\n }}\n onKeyDown={onKeyDown}\n />\n {open && (\n <div className={styles['list']} role=\"listbox\">\n {loading && <p className={styles['note']}>{loadingHint ?? ''}</p>}\n {!loading && visible.length === 0 && (\n <p className={styles['note']}>{emptyHint ?? ''}</p>\n )}\n {!loading && visible.map((option, index) => (\n <button\n key={option.id}\n type=\"button\"\n role=\"option\"\n aria-selected={index === active}\n className={index === active ? styles['rowActive'] : styles['row']}\n onMouseEnter={() => { setActive(index) }}\n onClick={() => { pick(option) }}\n >\n {option.avatarId !== undefined || option.avatarUrl !== undefined\n ? <Avatar name={option.label} src={option.avatarUrl} size=\"sm\" />\n : null}\n <span className={styles['label']}>{option.label}</span>\n {option.secondary !== undefined && option.secondary.length > 0 && (\n <span className={styles['secondary']}>{option.secondary}</span>\n )}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n}\n","/**\n * The agent-preset field, built on the platform's own menu.\n *\n * This is the same control the new-session screen shows beside the workspace picker: the\n * platform paints that one with `Menu` and draws each row as a name over its description.\n * Reproducing the choice as a text box — or as a differently-shaped list — would make the same\n * concept look like two things depending on where the operator met it, so the dialog borrows\n * the platform's materials instead:\n *\n * - `Menu` for the dropdown, so the keyboard walk, outside-click dismissal and the portaled\n * list behave exactly as they do on the hero chip.\n * - The platform's `settings.agentPreset` locale namespace for the shipped presets' names and\n * descriptions. That copy is owned by the platform; reprinting it here would freeze today's\n * wording and drift the moment the platform rewords it.\n *\n * What this deliberately does not do is import the platform's preset component: its props are\n * bound to the `conversation.hero.agentPreset` slot and carry a session plus a staged-selection\n * store this dialog has neither of. The roster comes from the Host instead, which reads the\n * same registry that slot renders.\n *\n * @module @sparkelf/dsh-group-chat/client/PresetSelect\n */\n\nimport { useState, type ReactNode } from 'react'\nimport { Menu, type MenuEntry } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { PresetOption } from './api.ts'\nimport { presetDisplay } from './rows.ts'\nimport styles from './PresetSelect.module.css'\n\nexport interface PresetSelectProps {\n /** Roster rows as the Host reported them, in its order. */\n readonly presets: readonly PresetOption[]\n /** The chosen preset id, or `''` for \"the deployment's default\". */\n readonly value: string\n /** Lookup for the platform's `settings.agentPreset` namespace. */\n readonly t: (key: string) => string\n /** Copy for the row that clears the choice back to the deployment default. */\n readonly defaultLabel: string\n readonly onChange: (id: string) => void\n}\n\n/**\n * Render the preset trigger and its menu.\n * @param props - roster, current choice, and the platform copy lookup.\n * @returns the anchored dropdown.\n */\nexport const PresetSelect = (props: PresetSelectProps): ReactNode => {\n const { presets, value, t, defaultLabel, onChange } = props\n const [open, setOpen] = useState(false)\n\n const chosen = presets.find((preset) => preset.id === value)\n const trigger = chosen === undefined ? defaultLabel : presetDisplay(chosen, t).name\n\n // A row's label is the name over its description, which the platform's menu styles as one\n // cell. `MenuEntry.label` is ReactNode, so the two lines travel together inside it rather\n // than through a second field the menu does not have.\n const entryOf = (preset: PresetOption): MenuEntry => {\n const { name, description } = presetDisplay(preset, t)\n return {\n id: preset.id,\n // A broken preset stays listed and disabled: hiding it would leave the operator unable to\n // tell a preset the deployment never declared from one it declared and could not activate.\n disabled: preset.broken !== undefined,\n label: (\n <span className={styles['row']}>\n <span className={styles['name']}>{name}</span>\n {description !== undefined && (\n <span className={styles['description']}>{description}</span>\n )}\n </span>\n ),\n }\n }\n // Shipped presets publish no name, so the split is the one the platform draws: the rows whose\n // copy it owns come first, and rows that name themselves in YAML follow.\n const items: MenuEntry[] = [\n ...presets.filter((preset) => preset.name === undefined).map(entryOf),\n ...presets.filter((preset) => preset.name !== undefined).map(entryOf),\n ]\n\n return (\n <Menu\n open={open}\n // Portaled: the dialog body scrolls and clips, and an in-place list would be cut off at\n // the field's edge once the roster grows past a couple of rows.\n portal\n side=\"bottom\"\n align=\"start\"\n items={items}\n selectedId={value.length === 0 ? undefined : value}\n onSelect={(id) => { onChange(id); setOpen(false) }}\n onClose={() => { setOpen(false) }}\n // The empty choice stays a menu row rather than a separate control: a room that omits the\n // preset inherits whatever the deployment defaults to, which is a different decision from\n // pinning today's default into the room's own record. Its id is the empty string the\n // dialog already uses for \"not set\", and no preset can shadow it — the registry rejects an\n // id that is empty.\n footer={[{ id: '', label: defaultLabel }]}\n anchor={(\n <button\n type=\"button\"\n className={styles['trigger']}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n onClick={() => { setOpen((current) => !current) }}\n >\n <span className={styles['triggerLabel']}>{trigger}</span>\n </button>\n )}\n />\n )\n}\n","/**\n * The room list both client surfaces read.\n *\n * It lives here rather than inside either component because the sidebar region and the\n * room page must never show different lists. It is a plain observable, not a framework\n * store: both consumers are components this plugin already owns.\n *\n * The slot caches the value its `inject` factory returns, so a snapshot taken at inject\n * time would freeze at the first read. Consumers therefore subscribe through\n * `useSyncExternalStore` and re-read on every change.\n *\n * @module @sparkelf/dsh-group-chat/client/store\n */\n\nimport {\n dissolveRoom, joinRoom, leaveRoom, removeMember, transferOwnership, listDiscoverableRooms, listMembers, listRooms, readSelf,\n type RoomMember, type RoomSummary, updateRoom,\n} from './api.ts'\nimport type { GroupChatKey } from './locales.ts'\n\n/** The three distinguishable failures the surfaces render as copy. */\nexport type RoomFailure = GroupChatKey | undefined\n\nexport interface RoomState {\n /** `undefined` until the first read completes, so the UI can tell loading from empty. */\n readonly rooms: readonly RoomSummary[] | undefined\n /** The room the main panel is showing, if any. */\n readonly activeRoomId: string | undefined\n readonly failure: RoomFailure\n /**\n * The sidebar's search box contents.\n *\n * Held here rather than in the region because the header collapses while a query is\n * present, and the region must survive that collapse with its text intact.\n */\n readonly searchQuery: string\n /**\n * This workspace's own account id, once the Host reports it.\n *\n * Held in the store because the message renderer needs it while rendering, and that\n * renderer is not a component that can wait on an effect: it decides a message's side\n * during the render in which the message appears. Undefined until the first read\n * completes, and the renderer treats \"unknown\" as \"not mine\" rather than guessing.\n */\n readonly selfId: string | undefined\n /**\n * Whether the deployment reports this reader as an administrator.\n *\n * Only used to decide whether to offer dissolving a room they do not own; the room server\n * decides on the request itself, so a stale `true` costs a refusal, not a wrong delete.\n */\n readonly administrator: boolean\n /**\n * Room members by room id, so a message can be drawn with its author's name and picture.\n *\n * A message event carries only the author's id. The name and the picture live on the roster,\n * which is a different request, so the renderer needs this lookup to avoid drawing a bare id\n * or initials derived from one.\n *\n * Kept per room rather than as one flat list because the same account can be a member of\n * several rooms and can leave some of them: a flat map would keep drawing a person in a room\n * they are no longer in.\n */\n readonly members: Readonly<Record<string, readonly RoomMember[]>>\n /**\n * Whether the create-room dialog is open.\n *\n * The sidebar's + button sets it and the page half renders the dialog: the sidebar seat\n * is a thin band, and a modal belongs to the surface that owns the window.\n */\n readonly pendingCreate: boolean\n /**\n * Rooms the operator could join, or `undefined` before the first discovery read.\n *\n * `undefined` rather than `[]` on purpose: an empty array means \"the search answered, and\n * nothing is joinable\", while `undefined` means \"nobody has asked yet\". Collapsing the two\n * would show an empty discovery section before the operator ever searched.\n */\n readonly discoverable: readonly RoomSummary[] | undefined\n /** The room dialog the row menu opened, if any. Rendered by the page half. */\n readonly dialog: RoomDialog | undefined\n}\n\n/**\n * A room dialog opened from a row's context menu.\n *\n * The menu lives in the sidebar and the dialogs live on the page, so the request travels\n * through the store rather than through component nesting.\n */\nexport type RoomDialog =\n | { readonly kind: 'invite'; readonly roomId: string }\n | { readonly kind: 'settings'; readonly roomId: string }\n | { readonly kind: 'members'; readonly roomId: string }\n /**\n * Hand the room over. Only reachable from the owner's own menu, because the action that\n * makes leaving possible for an owner is exactly this one.\n */\n | { readonly kind: 'transfer'; readonly roomId: string }\n\nexport interface RoomStore {\n getState: () => RoomState\n subscribe: (listener: () => void) => () => void\n /** Re-read the room list from the Host route. */\n refresh: () => Promise<void>\n /** Select a room; the main panel follows this. */\n open: (roomId: string) => void\n /** This workspace's own account id, or `undefined` while it is still unknown. */\n selfId: () => string | undefined\n administrator: () => boolean\n setFailure: (failure: RoomFailure) => void\n setSearch: (query: string) => void\n /**\n * Read the rooms this account could join.\n *\n * Separate from `refresh` because it is asked only when the operator is looking for\n * something to join: folding it into the sidebar's periodic read would put a second\n * cross-account query on every poll.\n *\n * @param query - optional narrowing keyword, applied by the server.\n */\n discover: (query?: string) => Promise<void>\n /**\n * Read one room's roster, so its messages can be drawn with their authors' names and pictures.\n *\n * Called when a room is opened rather than on every refresh: a roster changes rarely, and the\n * message stream does not require it to be current to be readable. A failed read leaves the\n * previous roster in place instead of clearing it, because an empty roster would redraw every\n * message in the room as an unknown author.\n *\n * @param roomId - the room whose roster to read.\n */\n loadMembers: (roomId: string) => Promise<void>\n /**\n * Join a room, then re-read both lists.\n *\n * The room moves from the discovery list to the sidebar in one step, so a successful join\n * cannot leave the same room visible in both places.\n *\n * @param roomId - the room to join.\n */\n join: (roomId: string) => Promise<void>\n /** Apply one policy to an existing room, then re-read so both surfaces agree. */\n setPolicy: (\n roomId: string,\n patch: { invitePolicy?: string; discoverability?: string; agentTrigger?: string },\n ) => Promise<void>\n /** Open the create-room dialog. */\n openCreate: () => void\n closeCreate: () => void\n /** Open one room dialog; `undefined` closes whatever is open. */\n setDialog: (dialog: RoomDialog | undefined) => void\n /**\n * Show the members panel for one room in the right sidebar.\n *\n * Supplied by the plugin's apply step rather than implemented here: opening a sidebar tab is a\n * shell concern, and this store owns room data only. Leaving it optional keeps the store\n * testable without a live sidebar and lets a profile without one keep the room list working.\n */\n openMembers: ((roomId: string) => void) | undefined\n /** Rename a room, then re-read so both surfaces show the new name. */\n rename: (roomId: string, name: string) => Promise<void>\n /** Leave a room. */\n leave: (roomId: string) => Promise<void>\n /**\n * Remove another member.\n *\n * Distinct from `leave`: this acts on someone else's membership, so the server restricts it\n * to the owner and refuses the owner as a target. Refreshing the list afterwards is what\n * updates every surface's roster.\n */\n removeMember: (roomId: string, userId: string) => Promise<void>\n /**\n * End a room for every member.\n *\n * Separate from `leave` on purpose: the two have different audiences and different\n * consequences, and a surface that offers one must not be mistaken for the other.\n */\n dissolve: (roomId: string) => Promise<void>\n /**\n * Hand ownership to another member.\n *\n * A room's owner cannot leave it, so this is the action that makes leaving possible for them.\n * The server decides both eligibility questions (is the caller the owner, is the target a\n * member), so the client only carries the choice.\n */\n transfer: (roomId: string, userId: string) => Promise<void>\n}\n\n/**\n * Create the shared store.\n * @returns a store whose `getState` returns a stable reference between changes, which is\n * what `useSyncExternalStore` requires to avoid a render loop.\n */\nexport function createRoomStore(): RoomStore {\n let state: RoomState = {\n rooms: undefined,\n activeRoomId: undefined,\n failure: undefined,\n searchQuery: '',\n selfId: undefined,\n administrator: false,\n members: {},\n pendingCreate: false,\n // Undefined, not empty: nobody has searched yet, which is a different fact from\n // \"searched and found nothing joinable\".\n discoverable: undefined,\n dialog: undefined,\n }\n const listeners = new Set<() => void>()\n\n const set = (patch: Partial<RoomState>): void => {\n // A fresh object per change, so `getState` is referentially stable between changes.\n state = { ...state, ...patch }\n for (const listener of listeners) listener()\n }\n\n /**\n * Read one room's roster into the store.\n *\n * A failure leaves the previous roster in place rather than clearing it: the room is still\n * readable without names (the renderer falls back to an unlabelled bubble), and a stale name is\n * still the right person far more often than no name at all.\n *\n * @param roomId - the room whose roster to read.\n */\n const loadMembersInto = async (roomId: string): Promise<void> => {\n try {\n const roster = await listMembers(roomId)\n set({ members: { ...state.members, [roomId]: roster } })\n } catch {\n // Deliberately silent: a roster that could not be read is not a room failure, and the\n // message list keeps working with what it already has.\n }\n }\n\n return {\n getState: () => state,\n subscribe: (listener) => {\n listeners.add(listener)\n return () => { listeners.delete(listener) }\n },\n refresh: async () => {\n // Resolved alongside the room list rather than in its own pass: identity is what the\n // message renderer needs, and a list that arrived while identity was still unknown\n // would draw the operator's own first messages on the wrong side.\n void readSelf().then((self) => {\n if (self !== undefined) set({ selfId: self.id, administrator: self.administrator })\n })\n try {\n const rooms = await listRooms()\n set({ rooms, failure: undefined })\n // Read every joined room's roster, not only the open one. A reload lands directly on a\n // room's Session without going through `open`, and a message must be attributable the\n // first time it paints — waiting for an interaction would leave the first screen of\n // every room unlabelled. Only rooms without a roster are read, so the periodic refresh\n // does not repeat this for data that rarely changes.\n for (const room of rooms) {\n if (!room.joined || state.members[room.id] !== undefined) continue\n void loadMembersInto(room.id)\n }\n } catch (error) {\n // The sidebar may load before the server is reachable; the page shows the\n // reason, and the next refresh clears it.\n set({ failure: failureOf(error) })\n }\n },\n open: (roomId) => {\n set({ activeRoomId: roomId })\n // Read on open rather than on every side-bar refresh: the roster changes rarely, and the\n // side bar refreshes every few seconds — fetching it there would be a steady stream of\n // requests for data that almost never differs.\n void loadMembersInto(roomId)\n },\n discover: async (query) => {\n try {\n set({ discoverable: await listDiscoverableRooms(query) })\n } catch (error) {\n // An unreadable discovery list is not a broken sidebar: the joined rooms are still\n // there and still usable, so the failure is reported without clearing the list.\n set({ discoverable: [], failure: failureOf(error) })\n }\n },\n join: async (roomId) => {\n await joinRoom(roomId)\n // Re-read both lists: the room is now a member room, so leaving it in `discoverable`\n // would let the operator press Join again on a room they are already in.\n set({ rooms: await listRooms(), discoverable: await listDiscoverableRooms() })\n },\n selfId: () => state.selfId,\n administrator: () => state.administrator,\n setFailure: (failure) => set({ failure }),\n setSearch: (searchQuery) => set({ searchQuery }),\n loadMembers: loadMembersInto,\n setPolicy: async (roomId, patch) => {\n await updateRoom(roomId, patch)\n set({ rooms: await listRooms() })\n },\n openCreate: () => set({ pendingCreate: true }),\n closeCreate: () => set({ pendingCreate: false }),\n setDialog: (dialog) => set({ dialog }),\n openMembers: undefined,\n rename: async (roomId, name) => {\n await updateRoom(roomId, { name })\n set({ rooms: await listRooms() })\n },\n leave: async (roomId) => {\n await leaveRoom(roomId)\n // The room stays listed under the \"show left rooms\" filter, so the selection is\n // cleared by refreshing rather than by forgetting the room.\n set({ rooms: await listRooms(), activeRoomId: undefined })\n },\n removeMember: async (roomId, userId) => {\n await removeMember(roomId, userId)\n // The roster is the only thing that changed; the room list itself keeps the same\n // rooms, so a full re-read is enough and refreshes the member count too.\n set({ rooms: await listRooms() })\n },\n dissolve: async (roomId) => {\n await dissolveRoom(roomId)\n // Unlike leaving, the room is gone for everyone: there is no \"left rooms\" entry to\n // refresh into, so the selection is cleared and the list re-read without it.\n set({ rooms: await listRooms(), activeRoomId: undefined })\n },\n transfer: async (roomId, userId) => {\n await transferOwnership(roomId, userId)\n // Ownership is a field on the room, so the refreshed list is what updates everyone's\n // notion of who runs it — including this reader's, who is now an ordinary member.\n set({ rooms: await listRooms() })\n },\n }\n}\n\n/** Map a transport failure onto the copy key the surfaces render. */\nexport const failureOf = (error: unknown): GroupChatKey => {\n const message = error instanceof Error ? error.message : ''\n if (message === 'credentialMissing') return 'credentialMissing'\n if (message === 'credentialRejected') return 'credentialRejected'\n if (message === 'notConfigured') return 'notConfigured'\n if (message === 'ownerCannotLeave') return 'ownerCannotLeave'\n return 'unreachable'\n}\n","/**\n * The create-room dialog.\n *\n * Three fields are required because each one decides something the room cannot change\n * later — the name, the working directory, and the agent preset. `cwd` is the real\n * security boundary: every member of the room can ask the agent to modify anything under\n * it, so the field carries a visible warning rather than a hint.\n *\n * The three policy switches are the room's settings, offered at creation so a room is not\n * born with defaults the creator would have chosen differently. The notice at the foot is\n * required wording: these settings control where a room appears, not who may reach it.\n *\n * @module @sparkelf/dsh-group-chat/client/CreateRoomDialog\n */\n\nimport { useEffect, useState, type ReactNode } from 'react'\nimport { Button, Input, Modal, Switch } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { createRoom, listDirectories, readOptions, type PresetOption } from './api.ts'\nimport { Picker } from './Picker.tsx'\nimport { PresetSelect } from './PresetSelect.tsx'\nimport { type PickerOption } from './rows.ts'\nimport { failureOf, type RoomStore } from './store.ts'\nimport type { GroupChatKey } from './locales.ts'\nimport styles from './CreateRoomDialog.module.css'\n\n/** Props the page half passes. */\nexport interface CreateRoomDialogProps {\n readonly t: (key: GroupChatKey) => string\n /**\n * Lookup for the platform's own preset copy.\n *\n * Separate from `t` because it is a different namespace: `t` resolves this plugin's keys,\n * while this one resolves `settings.agentPreset`, which the platform registers. Keeping them\n * apart is what lets a shipped preset show the platform's wording without this plugin\n * reprinting it.\n */\n readonly tPreset: (key: string) => string\n readonly store: RoomStore\n}\n\n/** Invite policy values the backend accepts. */\ntype InvitePolicy = 'anyone' | 'admin'\n/** Discoverability values the backend accepts. */\ntype Discoverability = 'discoverable' | 'invite_only'\n/** Agent trigger values the backend accepts. */\ntype AgentTrigger = 'mention' | 'always' | 'never'\n\n/**\n * Render the create-room dialog.\n * @param props - the copy resolver and shared store whose `pendingCreate` opens this.\n * @returns the dialog, or null while closed.\n */\nexport function CreateRoomDialog(props: CreateRoomDialogProps) {\n const { t, tPreset, store } = props\n const open = store.getState().pendingCreate\n\n const [name, setName] = useState('')\n const [cwd, setCwd] = useState('')\n const [preset, setPreset] = useState('')\n const [invitePolicy, setInvitePolicy] = useState<InvitePolicy>('anyone')\n const [discoverability, setDiscoverability] = useState<Discoverability>('discoverable')\n const [agentTrigger, setAgentTrigger] = useState<AgentTrigger>('mention')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<GroupChatKey | undefined>(undefined)\n // Preset names the deployment offers. Empty means the picker could not be filled, and the\n // field then falls back to typing — the dialog must not become unusable because a listing\n // failed.\n const [presets, setPresets] = useState<readonly PresetOption[]>([])\n const [directories, setDirectories] = useState<readonly PickerOption[]>([])\n const [dirPath, setDirPath] = useState('')\n\n // Read once when the dialog opens rather than at module load: the deployment's presets and\n // its directories can both change while the page is up, and a failed read must be retried\n // by reopening instead of being cached for the life of the page.\n useEffect(() => {\n if (!open) return\n let cancelled = false\n void (async () => {\n try {\n const options = await readOptions()\n if (!cancelled) setPresets(options.presets)\n } catch {\n if (!cancelled) setPresets([])\n }\n })()\n return () => { cancelled = true }\n }, [open])\n\n // The directory list follows `cwd` only after a pick. Re-listing on every keystroke would\n // fight the operator: a half-typed path is usually an invalid one, and the list would flip\n // to empty between characters.\n useEffect(() => {\n if (!open) return\n let cancelled = false\n void (async () => {\n try {\n const listing = await listDirectories(dirPath.length === 0 ? undefined : dirPath)\n if (cancelled) return\n const entries = listing.entries.map((entry) => ({\n id: entry.path,\n label: entry.name,\n secondary: entry.path,\n }))\n // The current directory is offered as the first row: the common case is accepting the\n // root, and without it the list can only ever move deeper.\n setDirectories([\n { id: listing.path, label: listing.path, secondary: t('pickThisDirectory') },\n ...(listing.parent === null\n ? []\n : [{ id: listing.parent, label: '..', secondary: listing.parent }]),\n ...entries,\n ])\n } catch {\n if (!cancelled) setDirectories([])\n }\n })()\n return () => { cancelled = true }\n }, [open, dirPath, t])\n\n // A reopened dialog starts clean, so a cancelled attempt leaves nothing behind.\n useEffect(() => {\n if (!open) {\n setName(''); setBusy(false); setError(undefined)\n }\n }, [open])\n\n const close = (): void => store.closeCreate()\n\n const submit = async (): Promise<void> => {\n if (name.trim().length === 0 || busy) return\n setBusy(true)\n try {\n const room = await createRoom(name.trim(), {\n // Empty means \"use the server's default\", so it is omitted rather than sent blank.\n ...cwd.trim().length === 0 ? {} : { cwd: cwd.trim() },\n ...preset.trim().length === 0 ? {} : { agentPreset: preset.trim() },\n })\n // The policies travel as a follow-up update rather than create-time fields, so a\n // failure to apply them still leaves a usable room instead of none.\n await Promise.all([\n invitePolicy === 'anyone' ? Promise.resolve() : store.setPolicy(room.id, { invitePolicy }),\n discoverability === 'discoverable' ? Promise.resolve() : store.setPolicy(room.id, { discoverability }),\n agentTrigger === 'mention' ? Promise.resolve() : store.setPolicy(room.id, { agentTrigger }),\n ])\n await store.refresh()\n store.open(room.id)\n close()\n } catch (cause) {\n setError(failureOf(cause))\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <Modal\n open={open}\n onClose={close}\n title={t('createTitle')}\n closeLabel={t('cancel')}\n footer={(\n <div className={styles.footer}>\n <Button variant=\"ghost\" size=\"md\" onClick={close}>{t('cancel')}</Button>\n <Button\n variant=\"primary\"\n size=\"md\"\n disabled={busy || name.trim().length === 0}\n onClick={() => void submit()}\n >\n {t('createSubmit')}\n </Button>\n </div>\n )}\n >\n <div className={styles.body}>\n <label className={styles.field}>\n <span className={styles.label}>{t('createName')}</span>\n <Input\n value={name}\n autoFocus\n onChange={(event) => setName(event.target.value)}\n onKeyDown={(event) => { if (event.key === 'Enter') void submit() }}\n />\n </label>\n\n {/* Required and security-relevant: this field decides what the agent can touch on\n behalf of everyone in the room, so it is shown with its consequence. */}\n <div className={styles.field}>\n <span className={styles.label}>{t('createCwd')}</span>\n <Picker\n value={cwd}\n options={directories}\n placeholder=\"/workspace\"\n emptyHint={t('optionEmpty')}\n onQueryChange={(next) => { setCwd(next) }}\n onTypedCommit={(text) => { setCwd(text); setDirPath(text) }}\n typedCommitLabel={(text) => text}\n onPick={(option) => {\n // Browsing and choosing are the same gesture here: taking a directory both sets\n // the value and moves the list into it, so the field never shows a path whose\n // contents the operator cannot see.\n setCwd(option.id)\n setDirPath(option.id)\n }}\n />\n <span className={styles.warn}>{t('createCwdHint')}</span>\n </div>\n\n <div className={styles.field}>\n <span className={styles.label}>{t('createPreset')}</span>\n {presets.length === 0\n // The roster could not be read. Falling back to a text box would accept any string,\n // and a preset the registry does not hold fails only after the room is submitted —\n // so the field states the one thing it can promise instead: the deployment default.\n ? <span className={styles.hint}>{t('createPresetUnavailable')}</span>\n : (\n <PresetSelect\n presets={presets}\n value={preset}\n t={tPreset}\n defaultLabel={t('createPresetDefault')}\n onChange={setPreset}\n />\n )}\n <span className={styles.hint}>{t('createPresetHint')}</span>\n </div>\n\n <div className={styles.switches}>\n <Switch\n checked={invitePolicy === 'anyone'}\n onChange={(next) => setInvitePolicy(next ? 'anyone' : 'admin')}\n label={t('createPolicy')}\n />\n <Switch\n checked={discoverability === 'discoverable'}\n onChange={(next) => setDiscoverability(next ? 'discoverable' : 'invite_only')}\n label={t('createVisibility')}\n />\n <Switch\n checked={agentTrigger === 'mention'}\n onChange={(next) => setAgentTrigger(next ? 'mention' : 'always')}\n label={t('createTrigger')}\n />\n <div className={styles.triggerExtra}>\n {(['mention', 'always', 'never'] as const).map((mode) => (\n <button\n key={mode}\n type=\"button\"\n className={agentTrigger === mode ? styles.pillActive : styles.pill}\n onClick={() => setAgentTrigger(mode)}\n >\n {mode === 'mention' ? t('triggerMention')\n : mode === 'always' ? t('triggerAlways') : t('triggerNever')}\n </button>\n ))}\n </div>\n </div>\n\n {/* Required wording: these settings are about appearance, not authorization. */}\n <p className={styles.notice}>{t('createNotice')}</p>\n {error !== undefined && <p className={styles.error}>{t(error)}</p>}\n </div>\n </Modal>\n )\n}\n","/**\n * The three per-room dialogs: invite, members, and settings.\n *\n * They share one module because they are one interaction family — each is opened from a\n * room row's context menu, each operates on exactly one room, and each writes through the\n * same store. Splitting them would triple the plumbing without separating anything.\n *\n * The settings dialog carries the required notice: its three switches change where a room\n * appears, not who may reach it. The server enforces permissions; these are preferences.\n *\n * @module @sparkelf/dsh-group-chat/client/RoomDialogs\n */\n\nimport { useEffect, useRef, useState, type KeyboardEvent } from 'react'\nimport {\n Button,\n IconCheckOutlineRegular,\n IconLoadingOutlineRegular,\n Input,\n Modal,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport {\n inviteMembers,\n listMembers,\n searchInviteCandidates,\n updateRoom,\n type InviteCandidate,\n type RoomMember,\n} from './api.ts'\nimport { Avatar } from './Avatar.tsx'\nimport { filterCandidates, isChosen, toggleChosen } from './rows.ts'\nimport { failureOf, type RoomStore } from './store.ts'\nimport type { GroupChatKey } from './locales.ts'\nimport styles from './CreateRoomDialog.module.css'\nimport invite from './InviteDialog.module.css'\n\nexport interface RoomDialogsProps {\n readonly t: (key: GroupChatKey) => string\n readonly store: RoomStore\n}\n\n/**\n * Render whichever room dialog is open.\n * @param props - the copy resolver and the store holding the open dialog.\n * @returns the dialog, or null when none is open.\n */\nexport function RoomDialogs(props: RoomDialogsProps) {\n const { t, store } = props\n const dialog = store.getState().dialog\n if (dialog === undefined) return null\n const close = (): void => store.setDialog(undefined)\n if (dialog.kind === 'invite') {\n return <InviteDialog t={t} store={store} roomId={dialog.roomId} onClose={close} />\n }\n if (dialog.kind === 'settings') {\n return <SettingsDialog t={t} store={store} roomId={dialog.roomId} onClose={close} />\n }\n if (dialog.kind === 'transfer') {\n return <TransferDialog t={t} store={store} roomId={dialog.roomId} onClose={close} />\n }\n return <MembersDialog t={t} roomId={dialog.roomId} onClose={close} />\n}\n\n/**\n * Add existing DataOps accounts to a room.\n *\n * The list is multi-select rather than one-pick-at-a-time: a room is usually started with\n * several colleagues, and a dialog that closed after each person made that a repeated\n * errand. Rows are native buttons with `aria-pressed`, so Tab, Enter and Space all work and\n * a screen reader hears the selected state; ↑/↓ move between rows without leaving the list.\n *\n * What is selected lives in `chosen` and not in the search result rows. A query can hide an\n * already-picked person — the server search returns only what matches — and keeping the two\n * separate is what stops the selection from silently emptying as the operator narrows the\n * list.\n */\nfunction InviteDialog(props: {\n t: (key: GroupChatKey) => string\n store: RoomStore\n roomId: string\n onClose: () => void\n}) {\n const { t, store, roomId, onClose } = props\n const [candidates, setCandidates] = useState<readonly InviteCandidate[]>([])\n const [query, setQuery] = useState('')\n const [chosen, setChosen] = useState<readonly InviteCandidate[]>([])\n const [searching, setSearching] = useState(true)\n const [busy, setBusy] = useState(false)\n const [searchError, setSearchError] = useState<GroupChatKey | undefined>(undefined)\n const [submitError, setSubmitError] = useState<GroupChatKey | undefined>(undefined)\n const [done, setDone] = useState(false)\n // Bumped by Retry. The query itself may not change, so the effect needs a dependency that\n // still re-runs the same read.\n const [attempt, setAttempt] = useState(0)\n const listRef = useRef<HTMLUListElement | null>(null)\n\n // Typing re-runs the search on a pause, so a name typed at speed is one request rather\n // than one per letter. An empty query is a real query — it is what fills the list before\n // anything has been typed.\n useEffect(() => {\n let cancelled = false\n setSearching(true)\n const timer = setTimeout(() => {\n void (async () => {\n try {\n const next = await searchInviteCandidates(roomId, query)\n if (!cancelled) {\n setCandidates(next)\n setSearchError(undefined)\n }\n } catch (cause) {\n if (!cancelled) setSearchError(failureOf(cause))\n } finally {\n if (!cancelled) setSearching(false)\n }\n })()\n }, 220)\n return () => { cancelled = true; clearTimeout(timer) }\n }, [roomId, query, attempt])\n\n const toggle = (candidate: InviteCandidate): void => {\n setDone(false)\n setSubmitError(undefined)\n setChosen((current) => toggleChosen(current, candidate))\n }\n\n const submit = async (): Promise<void> => {\n if (chosen.length === 0 || busy) return\n setBusy(true)\n setSubmitError(undefined)\n try {\n await inviteMembers(roomId, chosen.map((member) => member.userId))\n await store.refresh()\n setChosen([])\n setDone(true)\n // The people just invited are members now and must drop out of the candidates. Clearing\n // the query re-runs the read; when it was already empty, the attempt counter does.\n if (query.length === 0) setAttempt((current) => current + 1)\n else setQuery('')\n } catch (cause) {\n setSubmitError(failureOf(cause))\n } finally {\n setBusy(false)\n }\n }\n\n // Arrow keys walk the rows the way a listbox does; without this, reaching the tenth\n // candidate means ten Tabs. Enter and Space already toggle, because each row is a button.\n const onListKeyDown = (event: KeyboardEvent<HTMLUListElement>): void => {\n if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return\n const rows = [...(listRef.current?.querySelectorAll<HTMLButtonElement>('button[data-candidate]') ?? [])]\n if (rows.length === 0) return\n event.preventDefault()\n const index = rows.indexOf(document.activeElement as HTMLButtonElement)\n const step = event.key === 'ArrowDown' ? 1 : -1\n const next = index < 0 ? 0 : (index + step + rows.length) % rows.length\n rows[next]?.focus()\n }\n\n const moveToList = (): void => {\n listRef.current?.querySelector<HTMLButtonElement>('button[data-candidate]')?.focus()\n }\n\n // The narrowing is applied here as well as at the server, so the list answers a keystroke\n // on the same frame rather than one debounce later. The server's answer replaces these\n // rows when it arrives.\n const visible = filterCandidates(candidates, query)\n\n return (\n <Modal\n open\n onClose={onClose}\n title={t('inviteTitle')}\n closeLabel={t('cancel')}\n className={invite.dialog}\n contentClassName={invite.content}\n footer={(\n <div className={styles.footer}>\n <Button variant=\"ghost\" size=\"md\" onClick={onClose}>{t('cancel')}</Button>\n <Button variant=\"primary\" size=\"md\" disabled={busy || chosen.length === 0}\n onClick={() => void submit()}>\n {chosen.length === 0\n ? t('inviteSubmit')\n : t('inviteSubmitCount').replace('{count}', String(chosen.length))}\n </Button>\n </div>\n )}\n >\n <div className={styles.body}>\n {/* Search and its helper text are one field, the same grouping the create dialog\n uses for the fields it shares this stylesheet with. */}\n <div className={invite.field}>\n <div className={invite.searchRow}>\n <Input\n className={invite.search}\n value={query}\n data-modal-autofocus\n placeholder={t('invitePlaceholder')}\n aria-label={t('inviteSearchLabel')}\n onChange={(event) => { setQuery(event.target.value) }}\n onKeyDown={(event) => {\n if (event.key !== 'ArrowDown') return\n event.preventDefault()\n moveToList()\n }}\n />\n {searching && <IconLoadingOutlineRegular className={invite.spinner} size={14} />}\n </div>\n <span className={styles.hint}>{t('inviteHint')}</span>\n </div>\n\n {/* A live region mounted from the first render: a status element that appears\n together with its text is not reliably announced, and the count changing is\n exactly what a screen-reader user needs to hear after each toggle. */}\n <span className={invite.visuallyHidden} role=\"status\">\n {chosen.length === 0\n ? ''\n : t('inviteSelectedCount').replace('{count}', String(chosen.length))}\n </span>\n\n {/* The chips are the durable answer to \"who am I about to invite\": they survive a\n narrowed list and name everyone, rather than leaving that to a count alone. */}\n {chosen.length > 0 && (\n <div className={styles.field}>\n <span className={styles.label}>\n {t('inviteSelectedCount').replace('{count}', String(chosen.length))}\n </span>\n <ul className={invite.chips}>\n {chosen.map((member) => (\n <li key={member.userId} className={invite.chip}>\n {/* The chip already says the name, so the picture's own label would\n repeat it in the row's accessible name. */}\n <span aria-hidden=\"true\">\n <Avatar name={member.displayName || member.username}\n src={member.avatarUrl} size=\"sm\" />\n </span>\n <span className={invite.chipName}>{member.displayName || member.username}</span>\n <button\n type=\"button\"\n className={invite.chipRemove}\n aria-label={`${t('inviteRemove')} ${member.displayName || member.username}`}\n onClick={() => {\n setChosen((current) => current.filter((row) => row.userId !== member.userId))\n }}\n >×</button>\n </li>\n ))}\n </ul>\n </div>\n )}\n\n {/* One region, four states: a failed read with its retry, a read in flight, an answer\n with rows, and an answer with none. The panel keeps its place across all four so\n the dialog does not change height as the operator types. */}\n {searchError !== undefined && !searching ? (\n <div className={invite.errorRow}>\n <span className={invite.error}>{t(searchError)}</span>\n <Button size=\"sm\" variant=\"ghost\" onClick={() => { setAttempt((current) => current + 1) }}>\n {t('retry')}\n </Button>\n </div>\n ) : searching && visible.length === 0 ? (\n <p className={invite.note}>{t('inviteSearching')}</p>\n ) : visible.length === 0 ? (\n <p className={invite.note}>{t('inviteEmpty')}</p>\n ) : (\n <ul ref={listRef} className={invite.list} onKeyDown={onListKeyDown}>\n {visible.map((candidate) => {\n const selected = isChosen(chosen, candidate.userId)\n const name = candidate.displayName.length > 0 ? candidate.displayName : candidate.username\n return (\n <li key={candidate.userId}>\n <button\n type=\"button\"\n data-candidate\n className={selected ? invite.rowSelected : invite.row}\n aria-pressed={selected}\n onClick={() => { toggle(candidate) }}\n >\n {/* The row's name is already its text; hiding the picture's label\n keeps the button's accessible name from saying it twice. */}\n <span aria-hidden=\"true\">\n <Avatar name={name} src={candidate.avatarUrl} size=\"sm\" />\n </span>\n <span className={invite.rowName}>{name}</span>\n <span className={invite.rowHandle}>{candidate.username}</span>\n {/* Held in the layout even when unselected, so a pick does not shift\n the name towards the handle. */}\n <span className={selected ? invite.check : invite.checkHidden} aria-hidden=\"true\">\n <IconCheckOutlineRegular size={14} />\n </span>\n </button>\n </li>\n )\n })}\n </ul>\n )}\n\n {done && <p className={styles.hint}>{t('saved')}</p>}\n {submitError !== undefined && <p className={invite.error}>{t(submitError)}</p>}\n </div>\n </Modal>\n )\n}\n\n/** List a room's members. */\nfunction MembersDialog(props: {\n t: (key: GroupChatKey) => string\n roomId: string\n onClose: () => void\n}) {\n const { t, roomId, onClose } = props\n const [members, setMembers] = useState<readonly RoomMember[] | undefined>(undefined)\n const [error, setError] = useState<GroupChatKey | undefined>(undefined)\n\n useEffect(() => {\n let cancelled = false\n void (async () => {\n try {\n const next = await listMembers(roomId)\n if (!cancelled) setMembers(next)\n } catch (cause) {\n if (!cancelled) setError(failureOf(cause))\n }\n })()\n return () => { cancelled = true }\n }, [roomId])\n\n return (\n <Modal open onClose={onClose} title={t('membersTitle')} closeLabel={t('cancel')}\n footer={<div className={styles.footer}><Button variant=\"ghost\" size=\"md\" onClick={onClose}>{t('cancel')}</Button></div>}>\n <div className={styles.body}>\n {error !== undefined && <p className={styles.error}>{t(error)}</p>}\n {error === undefined && members === undefined && <p className={styles.hint}>{t('loading')}</p>}\n {members !== undefined && (\n <ul className={styles.memberList}>\n {members.map((member) => (\n <li key={member.userId} className={styles.memberRow}>\n <Avatar name={member.displayName} src={member.avatarUrl} />\n <span className={styles.memberName}>{member.displayName}</span>\n {member.owner && <span className={styles.memberBadge}>{t('ownerBadge')}</span>}\n </li>\n ))}\n </ul>\n )}\n </div>\n </Modal>\n )\n}\n\n/** The room's three policy dimensions. */\n/**\n * Hand ownership of this room to another member.\n *\n * Exists because an owner cannot leave: a room without an owner has nobody who may change its\n * settings, so the server refuses the leave. Transferring is that way out, and this dialog is\n * where the owner names the successor.\n *\n * Only current members are offered, and the owner is excluded — the server rejects both a\n * non-member and a self-transfer, so listing them would only invite a refusal.\n */\nfunction TransferDialog(props: {\n t: (key: GroupChatKey) => string\n store: RoomStore\n roomId: string\n onClose: () => void\n}) {\n const { t, store, roomId, onClose } = props\n const room = store.getState().rooms?.find((candidate) => candidate.id === roomId)\n const [members, setMembers] = useState<readonly RoomMember[] | undefined>(undefined)\n const [chosen, setChosen] = useState<string | undefined>(undefined)\n const [busy, setBusy] = useState(false)\n const [failure, setFailure] = useState<GroupChatKey | undefined>(undefined)\n\n useEffect(() => {\n let cancelled = false\n void listMembers(roomId)\n .then((rows) => { if (!cancelled) setMembers(rows) })\n .catch(() => { if (!cancelled) setMembers([]) })\n return () => { cancelled = true }\n }, [roomId])\n\n const candidates = (members ?? []).filter((member) => member.userId !== store.selfId())\n\n const submit = async (): Promise<void> => {\n if (busy || chosen === undefined) return\n setBusy(true)\n setFailure(undefined)\n try {\n await store.transfer(roomId, chosen)\n onClose()\n } catch (cause) {\n setFailure(failureOf(cause))\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <Modal\n open\n onClose={busy ? () => {} : onClose}\n title={t('menuTransferTitle')}\n closeLabel={t('cancel')}\n footer={(\n <div className={styles.footer}>\n <Button variant=\"ghost\" size=\"md\" disabled={busy} onClick={onClose}>\n {t('cancel')}\n </Button>\n <Button variant=\"primary\" size=\"md\" disabled={busy || chosen === undefined}\n onClick={() => void submit()}>\n {t('menuTransferConfirm')}\n </Button>\n </div>\n )}\n >\n <div className={invite.content}>\n <p className={invite.note}>{t('menuTransferHint')}</p>\n {room !== undefined && <p className={invite.note}>{room.name}</p>}\n {members === undefined && <p className={invite.note}>…</p>}\n {members !== undefined && candidates.length === 0 && (\n <p className={invite.note}>{t('menuTransferEmpty')}</p>\n )}\n {candidates.length > 0 && (\n <ul className={invite.list} role=\"listbox\" aria-label={t('menuTransferTitle')}>\n {candidates.map((member) => (\n <li key={member.userId}>\n <button type=\"button\" role=\"option\"\n aria-selected={chosen === member.userId}\n className={chosen === member.userId ? invite.rowSelected : invite.row}\n onClick={() => setChosen(member.userId)}>\n <Avatar size=\"sm\" name={member.displayName} src={member.avatarUrl ?? undefined} />\n <span className={invite.rowName}>{member.displayName}</span>\n </button>\n </li>\n ))}\n </ul>\n )}\n {failure !== undefined && <p className={invite.error} role=\"alert\">{t(failure)}</p>}\n </div>\n </Modal>\n )\n}\n\nfunction SettingsDialog(props: {\n t: (key: GroupChatKey) => string\n store: RoomStore\n roomId: string\n onClose: () => void\n}) {\n const { t, store, roomId, onClose } = props\n const room = store.getState().rooms?.find((candidate) => candidate.id === roomId)\n const [invitePolicy, setInvitePolicy] = useState(room?.invitePolicy ?? 'anyone')\n const [discoverability, setDiscoverability] = useState(room?.discoverability ?? 'discoverable')\n const [agentTrigger, setAgentTrigger] = useState(room?.agentTrigger ?? 'mention')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<GroupChatKey | undefined>(undefined)\n\n const save = async (): Promise<void> => {\n setBusy(true)\n try {\n await updateRoom(roomId, { invitePolicy, discoverability, agentTrigger })\n await store.refresh()\n onClose()\n } catch (cause) {\n setError(failureOf(cause))\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <Modal\n open\n onClose={onClose}\n title={t('settingsTitle')}\n closeLabel={t('cancel')}\n footer={(\n <div className={styles.footer}>\n <Button variant=\"ghost\" size=\"md\" onClick={onClose}>{t('cancel')}</Button>\n <Button variant=\"primary\" size=\"md\" disabled={busy} onClick={() => void save()}>\n {t('save')}\n </Button>\n </div>\n )}\n >\n <div className={styles.body}>\n {/* Three switches, each a distinct control point: who may invite, who can find the\n room, and whether the agent answers. */}\n <div className={styles.switches}>\n <PolicyRow t={t} label={t('createPolicy')}\n value={invitePolicy === 'anyone'} onChange={(next) => setInvitePolicy(next ? 'anyone' : 'admin')}\n onLabel={t('policyAnyone')} offLabel={t('policyAdmin')} />\n <PolicyRow t={t} label={t('createVisibility')}\n value={discoverability === 'discoverable'} onChange={(next) => setDiscoverability(next ? 'discoverable' : 'invite_only')}\n onLabel={t('discoverable')} offLabel={t('inviteOnly')} />\n </div>\n <div className={styles.switches} style={{ borderTop: 'none', paddingTop: 0 }}>\n <span className={styles.label}>{t('createTrigger')}</span>\n <div className={styles.triggerExtra}>\n {(['mention', 'always', 'never'] as const).map((mode) => (\n <button\n key={mode}\n type=\"button\"\n className={agentTrigger === mode ? styles.pillActive : styles.pill}\n onClick={() => setAgentTrigger(mode)}\n >\n {mode === 'mention' ? t('triggerMention') : mode === 'always' ? t('triggerAlways') : t('triggerNever')}\n </button>\n ))}\n </div>\n </div>\n <p className={styles.notice}>{t('createNotice')}</p>\n {error !== undefined && <p className={styles.error}>{t(error)}</p>}\n </div>\n </Modal>\n )\n}\n\n/** One two-state policy row, labelled with what each side means. */\nfunction PolicyRow(props: {\n t: (key: GroupChatKey) => string\n label: string\n value: boolean\n onChange: (next: boolean) => void\n onLabel: string\n offLabel: string\n}) {\n const { label, value, onChange, onLabel, offLabel } = props\n return (\n <div className={styles.policyRow}>\n <span className={styles.label}>{label}</span>\n <div className={styles.triggerExtra}>\n <button type=\"button\" className={value ? styles.pillActive : styles.pill}\n onClick={() => onChange(true)}>{onLabel}</button>\n <button type=\"button\" className={!value ? styles.pillActive : styles.pill}\n onClick={() => onChange(false)}>{offLabel}</button>\n </div>\n </div>\n )\n}\n","/**\n * The group-chat region of the left sidebar.\n *\n * Structured after `ui-workspace`'s browsing region so the two read as the same design\n * system rather than a lookalike: a 36px header holding a 14px section label, a\n * collapsible search slot, and the trailing icon actions (view options, new room), then\n * a flat list of rows. Sizes, tokens, and icon set are the ones that package uses — the\n * group-chat region is the workspace region with the grouping removed, because a room is\n * 1:1 with its session while a workspace is 1:N.\n *\n * Registered into `sidebar.footer.action`, the official list seat at the sidebar foot. The\n * shell renders it inside `footerActions` (full width, `flex: none`), under\n * `sidebar.workspaces` and above Settings.\n *\n * @module @sparkelf/dsh-group-chat/client/SidebarRooms\n */\n\nimport { useCallback, useEffect, useRef, useState, useSyncExternalStore, type ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport {\n IconChevronsUpDownOutlineRegular,\n IconQueueOutlineRegular,\n IconClockOutlineRegular,\n IconCloseFillRegular,\n IconProjectAddOutlineRegular,\n IconSearchOutlineRegular,\n IconSlidersTwoOutlineRegular,\n Button,\n Modal,\n Tooltip,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport {\n createRoom,\n readPreferences,\n savePreferences,\n searchMessages,\n updateRoom,\n type MessageHit,\n type RoomSummary,\n} from './api.ts'\nimport { CreateRoomDialog } from './CreateRoomDialog.tsx'\nimport { RoomDialogs } from './RoomDialogs.tsx'\nimport { joinableRooms } from './rows.ts'\nimport { failureOf, type RoomStore } from './store.ts'\nimport type { GroupChatKey } from './locales.ts'\nimport styles from './SidebarRooms.module.css'\n\n/** Props the `sidebar.footer.action` seat injects. */\nexport interface SidebarRoomsInjected {\n /** Namespace-bound copy resolver, supplied by the Client entry. */\n readonly t: (key: GroupChatKey) => string\n /** Lookup for the platform's `settings.agentPreset` namespace, bound by the Client entry. */\n readonly tPreset: (key: string) => string\n /** The list both sidebar and page read, owned by the Client entry. */\n readonly store: RoomStore\n /**\n * The seat passes the shell's own `wide` flag: false when the sidebar is\n * collapsed to the icon rail.\n *\n * Collapsed shows the LIST's entry as one compact icon, NOT the room rows: the rail has\n * room for an icon, not for a room name, and a clipped name (\"文融合分析\" cut mid-glyph)\n * reads as a rendering fault rather than as a collapsed list. The icon is what the\n * reader clicks to get the rooms back, so a collapsed rail still advertises that this\n * region exists -- rendering nothing made the feature disappear entirely.\n *\n * Optional because a host that does not supply the flag keeps the previous\n * always-render behaviour.\n */\n readonly wide?: boolean\n}\n\n/**\n * Height bounds for the region, in pixels.\n *\n * The floor is one room row plus the header; the ceiling keeps the region from crowding\n * out the workspace list above it, which is the same reason the stylesheet capped it at\n * `46vh` before this was adjustable. The host clamps to the same pair, so a stored value\n * cannot arrive outside the range the region can draw.\n */\nconst HEIGHT_MIN = 80\nconst HEIGHT_MAX = 1200\n\n/** Clamps a requested height into the range the region can draw. */\nconst clampHeight = (value: number): number =>\n Math.min(HEIGHT_MAX, Math.max(HEIGHT_MIN, Math.round(value)))\n\n/** How often the list re-reads, so a peer's new room appears without a reload. */\nconst REFRESH_MS = 10_000\n\n/** Sort orders the view menu offers, mirroring the workspace menu's updated/manual pair. */\ntype OrderBy = 'updated' | 'manual'\n\n/** Whether rooms the operator left are listed. */\ntype ArchivedFilter = 'default' | 'all'\n\n/**\n * Render the sidebar's group-chat region.\n * @param props - the injected copy resolver and the shared room store.\n * @returns the section header plus the flat room list.\n */\n/**\n * The hit text with the matched words marked.\n *\n * Splitting on the query gives the reader the one thing a result row has to offer: where in a\n * long message the words are. Case comes from the source text; only the comparison folds case.\n */\nconst markQuery = (text: string, query: string): ReactNode => {\n const needle = query.trim()\n if (needle.length === 0) return text\n const parts: ReactNode[] = []\n const lower = text.toLowerCase()\n const target = needle.toLowerCase()\n let from = 0\n let at = lower.indexOf(target)\n let key = 0\n while (at !== -1) {\n if (at > from) parts.push(text.slice(from, at))\n parts.push(<mark key={key} className={styles['mark']}>{text.slice(at, at + needle.length)}</mark>)\n key += 1\n from = at + needle.length\n at = lower.indexOf(target, from)\n }\n if (from < text.length) parts.push(text.slice(from))\n return parts\n}\n\n/**\n * The collapsed rail's stand-in for this region: one icon, no rows.\n *\n * `onSelect` is the same \"open the group chat\" action the room list header offers, so the\n * collapsed rail is not a dead end — clicking it gets the reader to the rooms.\n * @param props - the realm's injected copy resolver and the open action.\n * @returns The compact control.\n */\nfunction SidebarRoomsCollapsed(props: {\n t: (key: GroupChatKey) => string\n /** What clicking the rail icon does: open the rooms (see the caller). */\n onOpen: () => void\n}): ReactNode {\n return (\n <Tooltip label={props.t('title')} side=\"right\" delayMs={400}>\n <button\n type=\"button\"\n className={styles['railButton']}\n aria-label={props.t('title')}\n onClick={props.onOpen}\n >\n <IconQueueOutlineRegular size={18} />\n </button>\n </Tooltip>\n )\n}\n\nexport function SidebarRooms(props: SidebarRoomsInjected) {\n const { t, tPreset, store } = props\n if (props.wide === false) {\n // Collapsed: the icon is the whole region. Clicking it opens the active room — the same\n // destination the row would have taken the reader to, so the rail is not a dead end.\n // `openCreate` is deliberately NOT used: a reader clicking the rail wants their\n // conversations, not a new-room form.\n const firstRoom = store.getState().rooms?.[0]\n return (\n <SidebarRoomsCollapsed\n t={t}\n onOpen={() => {\n if (firstRoom !== undefined) void store.open(firstRoom.id)\n }}\n />\n )\n }\n const state = useSyncExternalStore(store.subscribe, store.getState, store.getState)\n const { rooms, activeRoomId, failure, searchQuery, discoverable } = state\n\n const [searchExpanded, setSearchExpanded] = useState(false)\n const [viewOpen, setViewOpen] = useState(false)\n const [orderBy, setOrderBy] = useState<OrderBy>('updated')\n const [archivedFilter, setArchivedFilter] = useState<ArchivedFilter>('default')\n const [joining, setJoining] = useState<string | undefined>(undefined)\n const searchInput = useRef<HTMLInputElement | null>(null)\n /**\n * Opens the room a hit belongs to and scrolls to that message.\n *\n * The conversation is still loading when this fires, so the row is not in the DOM yet: poll\n * for the anchor instead of guessing one delay, and give up quietly rather than scrolling to\n * a stranger's message.\n */\n const openHit = (hit: MessageHit): void => {\n void store.open(hit.roomId)\n store.setSearch('')\n setSearchExpanded(false)\n if (typeof document === 'undefined') return\n let attempts = 0\n const look = (): void => {\n const node = document.querySelector(`[data-group-chat-message=\"${hit.eventId}\"]`)\n if (node !== null) {\n node.scrollIntoView({ block: 'center', behavior: 'smooth' })\n return\n }\n attempts += 1\n if (attempts < 40) window.setTimeout(look, 250)\n }\n window.setTimeout(look, 400)\n }\n /**\n * Message hits for the same box the room rows are filtered by.\n *\n * One field, two questions: the rows below answer \"which room is this\" and this list answers\n * \"where is this said\". Two separate boxes made the reader choose which kind of search they\n * wanted before they knew what they were looking for.\n */\n const [hits, setHits] = useState<readonly MessageHit[]>([])\n const [searching, setSearching] = useState(false)\n /** Issue counter so a slow query cannot overwrite the results of a later one. */\n const searchTicket = useRef(0)\n /**\n * Height of this region, in pixels, once the user has dragged it.\n *\n * `undefined` until either the saved value arrives or a drag happens: the region then keeps\n * the stylesheet's `max-height` and shrinks to its content, which is what every workspace\n * looked like before this existed. Storing a number here switches it to a fixed height.\n */\n const [heightPx, setHeightPx] = useState<number | undefined>(undefined)\n /** The height being dragged to; `undefined` when no drag is in progress. */\n const [draggingPx, setDraggingPx] = useState<number | undefined>(undefined)\n const dragStart = useRef<{ y: number; height: number } | null>(null)\n\n useEffect(() => { void store.refresh() }, [store])\n\n // The saved height belongs to the user, not to this browser: it lives in the workspace's own\n // volume so signing in from another machine restores the same layout.\n useEffect(() => {\n let live = true\n void readPreferences().then((saved) => {\n if (!live || saved.sidebarHeightPx === undefined) return\n setHeightPx(saved.sidebarHeightPx)\n })\n return () => { live = false }\n }, [])\n useEffect(() => {\n const timer = setInterval(() => { void store.refresh() }, REFRESH_MS)\n return () => clearInterval(timer)\n }, [store])\n\n /**\n * Start a height drag from the grip on the region's top edge.\n *\n * The delta is inverted because the grip sits above the region: dragging up (a smaller `y`)\n * makes the region taller, which is the same direction the right sidebar's grip works in.\n * Listeners go on the document so the pointer may leave the 4px grip — and the window —\n * without dropping the drag halfway.\n */\n const beginResize = (event: React.PointerEvent<HTMLDivElement>): void => {\n event.preventDefault()\n const section = event.currentTarget.parentElement\n if (section === null) return\n dragStart.current = { y: event.clientY, height: section.getBoundingClientRect().height }\n setDraggingPx(dragStart.current.height)\n\n const onMove = (move: PointerEvent): void => {\n const start = dragStart.current\n if (start === null) return\n setDraggingPx(clampHeight(start.height + (start.y - move.clientY)))\n }\n const onUp = (): void => {\n document.removeEventListener('pointermove', onMove)\n document.removeEventListener('pointerup', onUp)\n document.removeEventListener('pointercancel', onUp)\n dragStart.current = null\n setDraggingPx((settled) => {\n if (settled !== undefined) {\n setHeightPx(settled)\n // Best-effort: a failed save leaves the layout correct for this session and the\n // stored value unchanged, which is a better outcome than blocking the drop.\n void savePreferences({ sidebarHeightPx: settled }).catch(() => undefined)\n }\n return undefined\n })\n }\n document.addEventListener('pointermove', onMove)\n document.addEventListener('pointerup', onUp)\n document.addEventListener('pointercancel', onUp)\n }\n\n /**\n * Message search, driven by the header box.\n *\n * Debounced and latest-wins: typing issues several queries, and an early one resolving late\n * would replace the results of a later, more specific one.\n */\n useEffect(() => {\n if (!searchExpanded) return\n const trimmed = searchQuery.trim()\n if (trimmed.length === 0) {\n setHits([])\n setSearching(false)\n return\n }\n const ticket = searchTicket.current + 1\n searchTicket.current = ticket\n setSearching(true)\n const timer = setTimeout(() => {\n void searchMessages(trimmed)\n .then((found) => { if (searchTicket.current === ticket) setHits(found) })\n .catch(() => { if (searchTicket.current === ticket) setHits([]) })\n .finally(() => { if (searchTicket.current === ticket) setSearching(false) })\n }, 250)\n return () => { clearTimeout(timer) }\n }, [searchQuery, searchExpanded])\n\n // The search box closes on Escape and on a click outside, the same way the workspace\n // one does, so the header returns to its resting width without a second control.\n useEffect(() => {\n if (!searchExpanded) return\n const onDown = (event: MouseEvent): void => {\n const target = event.target as HTMLElement | null\n if (target?.closest(`.${styles.search}`) === null) {\n if (searchQuery.length === 0) setSearchExpanded(false)\n }\n }\n document.addEventListener('mousedown', onDown)\n return () => document.removeEventListener('mousedown', onDown)\n }, [searchExpanded, searchQuery.length])\n\n const query = searchQuery.trim().toLowerCase()\n const visible = (rooms ?? [])\n // A room the operator left is hidden by default and reachable through the view menu,\n // which is the same shape the workspace list uses for archived sessions.\n .filter((room) => archivedFilter === 'all' || room.joined)\n .filter((room) => query.length === 0 || room.name.toLowerCase().includes(query))\n .slice()\n .sort((a, b) => orderBy === 'manual'\n ? 0\n : Date.parse(b.updatedAt || b.createdAt) - Date.parse(a.updatedAt || a.createdAt))\n\n const onPickOrder = useCallback(async (mode: OrderBy): Promise<void> => {\n setOrderBy(mode)\n setViewOpen(false)\n }, [])\n\n // Discovery runs only for a non-empty query, and debounced: every keystroke would otherwise\n // be a cross-account search, and the answer for \"a\" is never what the operator wanted.\n useEffect(() => {\n const keyword = searchQuery.trim()\n if (keyword.length === 0) return\n let cancelled = false\n const timer = setTimeout(() => {\n if (!cancelled) void store.discover(keyword)\n }, 300)\n return () => {\n cancelled = true\n clearTimeout(timer)\n }\n }, [searchQuery, store])\n\n // A room already in the sidebar must not also appear as joinable: the two lists answer\n // different questions, and a room in both would offer a Join button that cannot do anything.\n const joinable = joinableRooms(discoverable ?? [], new Set((rooms ?? []).map((room) => room.id)))\n\n const onJoin = async (roomId: string): Promise<void> => {\n setJoining(roomId)\n try {\n await store.join(roomId)\n // Opened on success: the operator pressed Join to get into the room, so landing in it is\n // the outcome they asked for rather than a second step they must discover.\n store.open(roomId)\n store.setSearch('')\n setSearchExpanded(false)\n } finally {\n setJoining(undefined)\n }\n }\n\n return (\n <section\n className={styles.root}\n aria-label={t('title')}\n // Always a fixed height. Without one the region shrink-wrapped its rows up to the\n // stylesheet's `46vh`, which on a tall window pushed the workspace list far up the\n // sidebar and made a two-room region look like the main surface. A reader who has never\n // dragged gets the floor: a handful of rows, and the sidebar keeps its proportions.\n style={{ height: `${String(draggingPx ?? heightPx ?? HEIGHT_MIN)}px`, maxHeight: 'none' }}\n >\n {/* The grip sits on the top edge, hidden until the region is hovered or focused: it is a\n pointer affordance, and a permanently visible bar would take a row of vertical space\n from the list it resizes. `onPointerDown` (not mouse) so a pen and a touch work too. */}\n <div\n className={styles.resizer}\n role=\"separator\"\n aria-orientation=\"horizontal\"\n aria-label={t('resizeRegion')}\n aria-valuenow={Math.round(draggingPx ?? heightPx ?? 0) || undefined}\n tabIndex={0}\n onPointerDown={beginResize}\n onKeyDown={(event) => {\n // Both pairs move towards the same physical edge: up/left grow the region.\n const step = event.shiftKey ? 40 : 10\n const grow = event.key === 'ArrowUp' || event.key === 'ArrowLeft'\n const shrink = event.key === 'ArrowDown' || event.key === 'ArrowRight'\n if (!grow && !shrink) return\n event.preventDefault()\n const section = event.currentTarget.parentElement\n const current = heightPx ?? HEIGHT_MIN\n const next = clampHeight(current + (grow ? step : -step))\n setHeightPx(next)\n void savePreferences({ sidebarHeightPx: next }).catch(() => undefined)\n }}\n />\n <div className={styles.sectionHeader}>\n {!searchExpanded && <span className={styles.sectionLabel}>{t('title')}</span>}\n\n <div className={searchExpanded ? styles.searchSlotExpanded : styles.searchSlot}>\n <div\n className={searchExpanded ? styles.searchExpanded : styles.search}\n onClick={() => { setSearchExpanded(true); searchInput.current?.focus() }}\n >\n <Tooltip label={t('search')} side=\"bottom\" delayMs={500} disabled={searchExpanded}>\n <button\n type=\"button\"\n className={styles.searchButton}\n aria-label={t('search')}\n aria-expanded={searchExpanded}\n onClick={(event) => {\n event.stopPropagation()\n setSearchExpanded((v) => !v)\n }}\n >\n <IconSearchOutlineRegular size={searchExpanded ? 11 : 14} />\n </button>\n </Tooltip>\n <input\n ref={searchInput}\n className={styles.searchInput}\n type=\"text\"\n placeholder={t('searchPlaceholder')}\n value={searchQuery}\n tabIndex={searchExpanded ? 0 : -1}\n onChange={(event) => store.setSearch(event.target.value)}\n onKeyDown={(event) => {\n if (event.key !== 'Escape') return\n store.setSearch('')\n setSearchExpanded(false)\n }}\n />\n {searchExpanded && (\n <button\n type=\"button\"\n className={styles.clearButton}\n aria-label={t('searchClear')}\n onClick={(event) => {\n event.stopPropagation()\n store.setSearch('')\n setSearchExpanded(false)\n }}\n >\n <IconCloseFillRegular />\n </button>\n )}\n </div>\n </div>\n\n {!searchExpanded && (\n <div className={styles.headerActions}>\n <ViewOptionsMenu\n t={t}\n open={viewOpen}\n orderBy={orderBy}\n archivedFilter={archivedFilter}\n onToggle={() => setViewOpen((v) => !v)}\n onPickOrder={(mode) => void onPickOrder(mode)}\n onPickArchived={(mode) => { setArchivedFilter(mode); setViewOpen(false) }}\n />\n <Tooltip label={t('createRoom')} side=\"bottom\" delayMs={500}>\n <button\n type=\"button\"\n className={styles.iconButton}\n aria-label={t('createRoom')}\n onClick={() => store.openCreate()}\n >\n <IconProjectAddOutlineRegular size={16} />\n </button>\n </Tooltip>\n </div>\n )}\n </div>\n\n <div className={styles.listArea}>\n\n {searchExpanded && query.length > 0 ? (\n <div className={styles.hitList} role=\"listbox\" aria-label={t('search')}>\n {searching && hits.length === 0 ? (\n <p className={styles.hitHint}>{t('searching')}</p>\n ) : hits.length === 0 ? null : (\n hits.map((hit) => (\n <button\n key={`${hit.roomId}:${hit.eventId}`}\n type=\"button\"\n role=\"option\"\n aria-selected={false}\n className={styles.hit}\n onClick={() => { openHit(hit) }}\n >\n <span className={styles.hitHead}>\n <span className={styles.hitRoom}>{hit.roomName}</span>\n {hit.authorName === null ? null : (\n <span className={styles.hitAuthor}>{hit.authorName}</span>\n )}\n </span>\n <span className={styles.hitText}>{markQuery(hit.text, searchQuery)}</span>\n </button>\n ))\n )}\n </div>\n ) : null}\n\n <div className={styles.list}>\n {failure !== undefined && <p className={styles.empty}>{t(failure)}</p>}\n {failure === undefined && rooms === undefined && <p className={styles.empty}>{t('loading')}</p>}\n {failure === undefined && rooms !== undefined && visible.length === 0 && (\n /* The same shape the workspace region uses when it has no sessions: a centred\n glyph above the copy and nothing else. The header already carries the way to\n create a room, so an action here would repeat it. */\n <div className={styles.emptyState}>\n <IconQueueOutlineRegular size={28} />\n <span>{query.length > 0 ? t('searchEmpty') : t('emptyRooms')}</span>\n </div>\n )}\n {visible.length > 0 && (\n <ul className={styles.flatList}>\n {visible.map((room) => (\n <li key={room.id}>\n <RoomRow\n room={room}\n t={t}\n store={store}\n selected={room.id === activeRoomId}\n onOpen={() => store.open(room.id)}\n />\n </li>\n ))}\n </ul>\n )}\n\n {/* Rooms this account could join. Shown only while searching, because that is the\n only moment the operator is looking for something they are not already in; a\n permanent section would put other people's rooms in a sidebar that otherwise\n answers \"what do I belong to\". */}\n {query.length > 0 && discoverable !== undefined && joinable.length > 0 && (\n <div className={styles.discoverSection}>\n <p className={styles.discoverHeading}>{t('discoverHeading')}</p>\n <ul className={styles.flatList}>\n {joinable.map((room) => (\n <li key={room.id}>\n <div className={styles.discoverRow}>\n <span className={styles.discoverName}>{room.name}</span>\n <span className={styles.discoverMeta}>\n {t('discoverMembers').replace('{count}', String(room.memberCount))}\n </span>\n <button\n type=\"button\"\n className={styles.joinButton}\n disabled={joining === room.id}\n onClick={() => { void onJoin(room.id) }}\n >\n {joining === room.id ? t('joining') : t('joinRoom')}\n </button>\n </div>\n </li>\n ))}\n </ul>\n </div>\n )}\n </div>\n {/* Matches the workspace list's bottom fade, so the list reads as scrollable\n without a visible scrollbar track. */}\n <div className={styles.fade} aria-hidden=\"true\" />\n </div>\n\n {/* The dialogs live here rather than on the room page. `Modal` portals itself to\n `document.body`, so it only needs to be rendered by a mounted component — and\n this region is always mounted, while the room page is not: pressing \"New room\"\n from the sidebar must work before any room has ever been opened. */}\n <CreateRoomDialog t={t} tPreset={tPreset} store={store} />\n <RoomDialogs t={t} store={store} />\n </section>\n )\n}\n\n/** One room row: glyph, name, and the member count where a session row shows its time. */\nfunction RoomRow(props: {\n room: RoomSummary\n t: (key: GroupChatKey) => string\n selected: boolean\n onOpen: () => void\n store: RoomStore\n}) {\n const { room, t, selected, onOpen, store } = props\n // The rect is captured when the menu opens, not read during render: the menu is\n // positioned against where the row was at that moment, so scrolling the list while the\n // menu is open does not drag it away from the row the operator aimed at.\n const [menuAnchor, setMenuAnchor] = useState<DOMRect | null>(null)\n // The leave confirmation lives on the row, not on the menu: choosing \"leave room\" closes\n // the menu, and a confirmation owned by the menu would unmount with it. The row itself\n // stays mounted while the room is still listed, so the dialog can stay open long enough to\n // report a refused leave.\n const [confirmingLeave, setConfirmingLeave] = useState(false)\n const [leaving, setLeaving] = useState(false)\n const [leaveFailure, setLeaveFailure] = useState<GroupChatKey | undefined>(undefined)\n // Dissolving gets its own dialog and its own failure slot: it ends the room for everyone,\n // unlike leaving, so sharing the leave state would print one act's refusal in the other's\n // dialog.\n const [confirmingDissolve, setConfirmingDissolve] = useState(false)\n const [dissolving, setDissolving] = useState(false)\n const [dissolveFailure, setDissolveFailure] = useState<GroupChatKey | undefined>(undefined)\n\n // A room this account has not joined has no reading position, so it never carries a badge:\n // the server reports 0 for it and the row keeps showing \"left\".\n const unread = room.joined ? room.unreadCount ?? 0 : 0\n const mentioned = unread > 0 && (room.mentionCount ?? 0) > 0\n\n const closeLeave = (): void => {\n setConfirmingLeave(false)\n setLeaveFailure(undefined)\n }\n\n const closeDissolve = (): void => {\n setConfirmingDissolve(false)\n setDissolveFailure(undefined)\n }\n\n const leave = async (): Promise<void> => {\n if (leaving) return\n setLeaving(true)\n setLeaveFailure(undefined)\n try {\n await store.leave(room.id)\n setConfirmingLeave(false)\n } catch (cause) {\n // The operator is still a member, so the dialog stays open and names the reason\n // instead of closing as if the leave had worked.\n setLeaveFailure(failureOf(cause))\n } finally {\n setLeaving(false)\n }\n }\n\n const dissolve = async (): Promise<void> => {\n if (dissolving) return\n setDissolving(true)\n setDissolveFailure(undefined)\n try {\n await store.dissolve(room.id)\n setConfirmingDissolve(false)\n } catch (cause) {\n // The room still exists, so the dialog stays open and names why the dissolve was\n // refused — closing here would read as success.\n setDissolveFailure(failureOf(cause))\n } finally {\n setDissolving(false)\n }\n }\n\n return (\n <div className={styles.rowWrap}>\n <button\n type=\"button\"\n className={selected ? styles.sessionRowSelected : styles.sessionRow}\n onClick={onOpen}\n onContextMenu={(event) => {\n event.preventDefault()\n setMenuAnchor(event.currentTarget.getBoundingClientRect())\n }}\n aria-current={selected}\n >\n <span className={styles.rowIcon} aria-hidden=\"true\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n strokeWidth=\"1.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M2.5 3.5A1.5 1.5 0 0 1 4 2h5a1.5 1.5 0 0 1 1.5 1.5v3A1.5 1.5 0 0 1 9 8H5.5L3 10V8h-.5A1.5 1.5 0 0 1 2.5 6.5z\" />\n <path d=\"M11 5.5h1A1.5 1.5 0 0 1 13.5 7v3.5A1.5 1.5 0 0 1 12 12h-.5v2l-2.5-2H7\" />\n </svg>\n </span>\n <span className={styles.title}>{room.name}</span>\n {unread > 0 ? (\n // The unread badge replaces the member count rather than sitting beside it: the row\n // has one trailing slot, and \"3 unread\" is what the reader needs from it. A mention\n // gets its own colour so \"someone wrote\" and \"someone wrote to me\" are distinguishable\n // at a glance without reading the number.\n <span\n className={mentioned ? styles.unreadMentioned : styles.unread}\n aria-label={mentioned\n ? t('unreadMentionAria').replace('{count}', String(unread))\n : t('unreadAria').replace('{count}', String(unread))}\n >\n {unread > 99 ? '99+' : String(unread)}\n </span>\n ) : (\n <span className={styles.time}>\n {room.joined ? t('memberCountShort').replace('{count}', String(room.memberCount)) : t('left')}\n </span>\n )}\n </button>\n {menuAnchor !== null && (\n <RoomMenu t={t} room={room} store={store} anchor={menuAnchor}\n onClose={() => setMenuAnchor(null)}\n onRequestLeave={() => { setMenuAnchor(null); setConfirmingLeave(true) }}\n onRequestDissolve={() => { setMenuAnchor(null); setConfirmingDissolve(true) }} />\n )}\n {confirmingLeave && (\n <LeaveRoomDialog t={t} room={room} busy={leaving} failure={leaveFailure}\n onCancel={closeLeave} onConfirm={() => void leave()} />\n )}\n {confirmingDissolve && (\n <DissolveRoomDialog t={t} room={room} busy={dissolving} failure={dissolveFailure}\n onCancel={closeDissolve} onConfirm={() => void dissolve()} />\n )}\n </div>\n )\n}\n\n/**\n * The row's context menu.\n *\n * Two official workspace entries are absent here rather than relabelled. Archiving would\n * duplicate \"leave room\", and forking copies history whose events carry no member\n * attribution — producing a room that looks continuous but whose earlier messages belong\n * to nobody. The remaining entries are the ones the design settled on, and each is shown\n * only when it applies to this room and this caller.\n */\nfunction RoomMenu(props: {\n t: (key: GroupChatKey) => string\n room: RoomSummary\n store: RoomStore\n onClose: () => void\n /** Ask the row to open the leave confirmation; the menu closes as it does. */\n onRequestLeave: () => void\n /** Ask the row to open the dissolve confirmation; the menu closes as it does. */\n onRequestDissolve: () => void\n /** Viewport rect of the row that opened the menu, which the portal positions against. */\n anchor: DOMRect\n}) {\n const { t, room, store, onClose, anchor, onRequestLeave, onRequestDissolve } = props\n const [renaming, setRenaming] = useState(false)\n const [draftName, setDraftName] = useState(room.name)\n const [busy, setBusy] = useState(false)\n\n const run = async (action: () => Promise<void>): Promise<void> => {\n setBusy(true)\n try {\n await action()\n onClose()\n } finally {\n setBusy(false)\n }\n }\n\n // Rendered into `document.body`, not beside the row. The room list scrolls\n // (`overflow-y: auto`), and a positioned menu inside a scrolling box is clipped at the\n // box's edge — the lower rooms' menus lost their bottom entries. The portal puts the menu\n // outside every clipping ancestor, and the anchor rect keeps it visually attached to the\n // row that opened it.\n //\n // The menu flips above the row when the row sits too low in the viewport to hold it:\n // pinning `top` unconditionally pushed the last rooms' menus past the bottom edge, which\n // clips exactly the entries the operator was reaching for. `bottom` is measured from the\n // viewport edge, so the flip needs no knowledge of the menu's own height.\n const below = window.innerHeight - anchor.bottom - 8\n const flip = below < 220 && anchor.top > below\n const placement = flip\n ? { bottom: window.innerHeight - anchor.top + 4 }\n : { top: anchor.bottom + 4 }\n const portal = (body: React.ReactNode): React.ReactPortal =>\n createPortal(\n <div className={styles.menuLayer} style={{ ...placement, left: anchor.left }}\n onClick={(event) => event.stopPropagation()}>\n {body}\n </div>,\n document.body,\n )\n\n if (renaming) {\n const commit = (): void => {\n void run(() => store.rename(room.id, draftName.trim()))\n }\n const cancel = (): void => {\n setRenaming(false)\n setDraftName(room.name)\n }\n return portal(\n // A wider panel than the menu rows need: the field and its two actions read as one\n // small form, and a 200px menu left the label, field and button stacked edge to edge.\n <div className={styles.menuRename} role=\"dialog\" aria-label={t('renameTitle')}>\n <span className={styles.menuHeading}>{t('renameTitle')}</span>\n <input\n className={styles.menuInput}\n value={draftName}\n autoFocus\n placeholder={t('renamePlaceholder')}\n onChange={(event) => setDraftName(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && draftName.trim().length > 0) commit()\n if (event.key === 'Escape') cancel()\n }}\n />\n <div className={styles.menuActions}>\n <button type=\"button\" className={styles.menuCancel} onClick={cancel}>\n {t('cancel')}\n </button>\n <button\n type=\"button\"\n className={styles.menuConfirm}\n disabled={busy || draftName.trim().length === 0}\n onClick={commit}\n >\n {t('save')}\n </button>\n </div>\n </div>,\n )\n }\n\n return portal(\n <div className={styles.menu} role=\"menu\">\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => setRenaming(true)}>\n {t('menuRename')}\n </button>\n {/* Inviting and leaving are member actions; the server decides whether this caller\n may invite, so the entry is offered to members and a refusal surfaces as copy. */}\n {room.joined && (\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => { store.setDialog({ kind: 'invite', roomId: room.id }); onClose() }}>\n {t('menuInvite')}\n </button>\n )}\n {/* Members are a side panel, not a dialog: a roster is consulted while reading, and a modal\n would cover the conversation it describes. `openMembers` is absent on a profile without\n the official right sidebar, where the entry would have nowhere to lead. */}\n {store.openMembers !== undefined && (\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => { store.openMembers?.(room.id); onClose() }}>\n {t('menuMembers')}\n </button>\n )}\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => { store.setDialog({ kind: 'settings', roomId: room.id }); onClose() }}>\n {t('menuSettings')}\n </button>\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => { void navigator.clipboard?.writeText(room.id); onClose() }}>\n {t('copyRoomId')}\n </button>\n {/* The owner is offered a transfer instead of a leave: leaving is refused by the server\n for them (a room without an owner has nobody able to change its settings), so showing\n \"leave room\" would only produce a refusal. Everyone else keeps the plain leave. */}\n {room.joined && store.selfId() === room.ownerUserId && (\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => { store.setDialog({ kind: 'transfer', roomId: room.id }); onClose() }}>\n {t('menuTransfer')}\n </button>\n )}\n {room.joined && store.selfId() !== room.ownerUserId && (\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={onRequestLeave}>\n {t('menuLeave')}\n </button>\n )}\n {/* Dissolving ends the room for everyone, so it is offered only to the people the\n server will accept it from: the owner, and an administrator. The server still\n decides on the request itself. */}\n {(store.selfId() === room.ownerUserId || store.administrator()) && (\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={onRequestDissolve}>\n {t('menuDissolve')}\n </button>\n )}\n </div>,\n )\n}\n\n /**\n * The dissolve-room confirmation.\n *\n * A separate dialog from leaving, not a variant of it: leaving removes one membership and the\n * room carries on for everyone else, while dissolving ends it for every member and takes the\n * messages with it. Sharing one dialog would let the far more destructive act read as the\n * mild one.\n *\n * Like the leave dialog, a refusal keeps this open and prints the mapped reason: the room is\n * still there, so closing would read as success.\n */\n function DissolveRoomDialog(props: {\n t: (key: GroupChatKey) => string\n room: RoomSummary\n busy: boolean\n failure: GroupChatKey | undefined\n onCancel: () => void\n onConfirm: () => void\n }) {\n const { t, room, busy, failure, onCancel, onConfirm } = props\n return (\n <Modal\n open\n onClose={onCancel}\n title={t('menuDissolve')}\n closeLabel={t('cancel')}\n footer={(\n <div className={styles.footer}>\n <Button data-modal-autofocus variant=\"ghost\" size=\"md\" disabled={busy} onClick={onCancel}>\n {t('cancel')}\n </Button>\n {/* Destructive, so it is red rather than the primary fill: the primitive has no\n danger variant, and colour is the only thing separating this from a harmless\n confirm at a glance. */}\n <Button variant=\"ghost\" size=\"md\" disabled={busy}\n className={styles.leaveConfirmDanger}\n onClick={onConfirm}>\n {t('menuDissolve')}\n </Button>\n </div>\n )}\n >\n <div className={styles.leaveBody}>\n <p className={styles.leaveCopy}>{t('menuDissolveConfirm')}</p>\n {/* A bare room name read as a stray label once the dialog opened from a menu that had\n already closed. Naming the target explicitly is what lets a reader confirm they are\n about to delete the room they meant to. */}\n <p className={styles.leaveRoomName}>\n {t('menuDissolveTarget')}<strong>{room.name}</strong>\n </p>\n {failure !== undefined && (\n <p className={styles.leaveError} role=\"alert\">{t(failure)}</p>\n )}\n </div>\n </Modal>\n )\n }\n\n\n/**\n * The leave-room confirmation.\n *\n * It exists because leaving is not undoable from this surface: the sidebar keeps the room\n * under the \"left rooms\" filter, but rejoining is a separate discovery step. The copy names\n * that the agent's recorded history stays, which is the part a reader cannot infer.\n *\n * A refused leave keeps this dialog open and prints the mapped failure. Closing on failure\n * was the old behaviour and read as success — the only sign it had gone wrong was the room\n * still being in the list.\n */\nfunction LeaveRoomDialog(props: {\n t: (key: GroupChatKey) => string\n room: RoomSummary\n busy: boolean\n failure: GroupChatKey | undefined\n onCancel: () => void\n onConfirm: () => void\n}) {\n const { t, room, busy, failure, onCancel, onConfirm } = props\n return (\n <Modal\n open\n onClose={onCancel}\n title={t('menuLeave')}\n closeLabel={t('cancel')}\n footer={(\n <div className={styles.footer}>\n {/* Cancel is the initial focus: the safe answer is the default for a destructive\n dialog, and Escape maps to the same command. */}\n <Button data-modal-autofocus variant=\"ghost\" size=\"md\" disabled={busy} onClick={onCancel}>\n {t('cancel')}\n </Button>\n <Button variant=\"primary\" size=\"md\" disabled={busy} onClick={onConfirm}>\n {t('menuLeave')}\n </Button>\n </div>\n )}\n >\n <div className={styles.leaveBody}>\n <p className={styles.leaveCopy}>{t('menuLeaveConfirm')}</p>\n <p className={styles.leaveRoomName}>{room.name}</p>\n {failure !== undefined && (\n <p className={styles.leaveError} role=\"alert\">{t(failure)}</p>\n )}\n </div>\n </Modal>\n )\n}\n\n/** The trailing view menu: ordering and whether rooms the operator left are listed. */\nfunction ViewOptionsMenu(props: {\n t: (key: GroupChatKey) => string\n open: boolean\n orderBy: OrderBy\n archivedFilter: ArchivedFilter\n onToggle: () => void\n onPickOrder: (mode: OrderBy) => void\n onPickArchived: (mode: ArchivedFilter) => void\n}) {\n const { t, open, orderBy, archivedFilter, onToggle, onPickOrder, onPickArchived } = props\n return (\n <div className={styles.viewWrap}>\n <Tooltip label={t('viewOptions')} side=\"bottom\" delayMs={500}>\n <button\n type=\"button\"\n className={styles.iconButton}\n aria-label={t('viewOptions')}\n aria-expanded={open}\n onClick={onToggle}\n >\n <IconSlidersTwoOutlineRegular size={16} />\n </button>\n </Tooltip>\n {open && (\n <div className={styles.menuInline} role=\"menu\">\n <span className={styles.menuHeading}>{t('orderBy')}</span>\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => onPickOrder('updated')}>\n <IconClockOutlineRegular size={14} />\n {t('orderByUpdated')}\n {orderBy === 'updated' && <span className={styles.check}>✓</span>}\n </button>\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => onPickOrder('manual')}>\n <IconChevronsUpDownOutlineRegular size={14} />\n {t('orderByManual')}\n {orderBy === 'manual' && <span className={styles.check}>✓</span>}\n </button>\n <span className={styles.menuHeading}>{t('archivedFilter')}</span>\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => onPickArchived('default')}>\n {t('archivedHide')}\n {archivedFilter === 'default' && <span className={styles.check}>✓</span>}\n </button>\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => onPickArchived('all')}>\n {t('archivedShow')}\n {archivedFilter === 'all' && <span className={styles.check}>✓</span>}\n </button>\n </div>\n )}\n </div>\n )\n}\n","import { clearQuote, registerQuote } from './api.ts'\n\n/**\n * The message a reply is being written against, shared by the three surfaces that need it.\n *\n * Replying spans two parts of the UI that do not otherwise talk to each other: the button on a\n * message that starts a reply, and the composer that has to show what is being answered. A store\n * this plugin already owns is the smallest thing that lets them agree, and it keeps the pending\n * reply in one place instead of in three components' props.\n *\n * The value is per room, not global: a member can be writing a reply in one room and switch to\n * another, and carrying the quote across that switch would attach it to a message the reader\n * never picked.\n *\n * @module @sparkelf/dsh-group-chat/client/quote\n */\n\n/**\n * The block type a reply uses to carry the message it answers.\n *\n * Declared in this shared module rather than beside the renderer that reads it: the same string\n * is written by the host when it publishes the reply, and a second literal there would let the\n * two drift into a quote nothing renders.\n */\nexport const QUOTE_BLOCK_TYPE = 'group-chat-quote'\n\n/**\n * The parts of a message row this module reads.\n *\n * A minimal structural shape rather than the official message type: the renderer receives the\n * official `UserMessage` and this module only ever looks at these fields, so naming the larger\n * type would tie it to a package this plugin does not depend on.\n */\nexport interface MessageLike {\n readonly source?: {\n /** Producer of the message; `group-chat-quote` marks model-only context. */\n readonly kind?: unknown\n readonly roomId?: unknown\n readonly memberId?: unknown\n readonly authorKind?: unknown\n readonly memberName?: unknown\n /** The room's log row this message was stored as; what a reply points at. */\n readonly messageId?: unknown\n } | null\n readonly content?: unknown\n readonly id?: unknown\n}\n\n/**\n * The message a reply points at, as the reply carries it.\n *\n * The quote rides inside the message's own `content` as a block, not as a top-level field: the\n * event envelope is the official `UserMessage` and anything outside `content`/`source` is\n * dropped on the way to the surface. A block survives, and the backend's block type is open\n * (`AiChatContentBlock` is `{ type: string; ... }`), so an unknown type passes through instead\n * of failing validation.\n */\nexport interface QuotedMessage {\n /** The id of the message being answered. */\n readonly messageId: string\n /** That message's author, as their name read when the reply was written. */\n readonly authorName?: string\n /** The first line of what they said, already trimmed for display. */\n readonly preview: string\n}\n\n/** How much of the quoted message to keep. One line, like the composer shows. */\nconst QUOTE_PREVIEW_CHARS = 120\n\n/**\n * Reads the quoted message out of a reply, if it has one.\n *\n * Returns `undefined` rather than a partial object: a quote with no preview has nothing to show,\n * and drawing an empty bar above the bubble would look like a rendering fault.\n *\n * @param message - the message data the node carries.\n * @returns the quote to draw, or `undefined` for an ordinary message.\n */\n/**\n * The text of a message, joined from its text blocks.\n *\n * Only `text` blocks contribute: an image or a file has no words to quote, and including a\n * placeholder for it would put text in the reply that the author never wrote.\n *\n * @param message - the message data the node carries.\n * @returns the collapsed text, or `''` when the message has none.\n */\nexport const previewOf = (message: MessageLike | undefined): string => {\n const content = message?.content\n // A text-only message carries its body as a plain string, not as a block list: that is the\n // shape the official composer submits, and reading only arrays left every ordinary message\n // with an empty preview — which silently removed the reply control from all of them.\n if (typeof content === 'string') return content.replace(/\\s+/g, ' ').trim()\n if (!Array.isArray(content)) return ''\n const parts: string[] = []\n for (const block of content) {\n if (typeof block !== 'object' || block === null) continue\n const record = block as Record<string, unknown>\n if (record['type'] !== 'text') continue\n const text = record['text']\n if (typeof text === 'string' && text.trim().length > 0) parts.push(text)\n }\n return parts.join(' ').replace(/\\s+/g, ' ').trim()\n}\n\nexport const quoteOf = (message: MessageLike | undefined): QuotedMessage | undefined => {\n const content = message?.content\n if (!Array.isArray(content)) return undefined\n for (const block of content) {\n if (typeof block !== 'object' || block === null) continue\n const record = block as Record<string, unknown>\n if (record['type'] !== QUOTE_BLOCK_TYPE) continue\n const messageId = record['messageId']\n const preview = record['preview']\n if (typeof messageId !== 'string' || messageId.length === 0) return undefined\n if (typeof preview !== 'string') return undefined\n const authorName = typeof record['authorName'] === 'string' && record['authorName'].trim().length > 0\n ? record['authorName'].trim()\n : undefined\n return { messageId, ...authorName === undefined ? {} : { authorName }, preview }\n }\n return undefined\n}\n\n/**\n * Builds the block a reply carries.\n *\n * Whitespace collapses to single spaces and the result is cut to one line: the quote is a\n * pointer, and a bar showing a paragraph would push the actual reply off the screen.\n *\n * @param messageId - the message being answered.\n * @param preview - what it said, taken from its text blocks.\n * @param authorName - who said it, when known.\n * @returns the block to append to the reply's content.\n */\nexport const quoteBlock = (\n messageId: string,\n preview: string,\n authorName?: string,\n): Record<string, unknown> => {\n const collapsed = preview.replace(/\\s+/g, ' ').trim()\n const trimmed = collapsed.length > QUOTE_PREVIEW_CHARS\n ? `${collapsed.slice(0, QUOTE_PREVIEW_CHARS - 1)}…`\n : collapsed\n return {\n type: QUOTE_BLOCK_TYPE,\n messageId,\n preview: trimmed,\n ...authorName === undefined ? {} : { authorName },\n }\n}\n\n\n/** What a pending reply points at, resolved to what the composer bar has to draw. */\nexport interface PendingQuote {\n /** The room the quoted message belongs to. */\n readonly roomId: string\n /** The id of the message being answered, as the room's event log knows it. */\n readonly messageId: string\n /** The author's display name, as the roster gave it when the reply was started. */\n readonly authorName?: string\n /** One line of what they said, already collapsed and trimmed for display. */\n readonly preview: string\n}\n\n/** The read side the components subscribe to, and the write side they call. */\nexport interface QuoteStore {\n /** The pending quote for one room, retold on every change. */\n readonly getQuote: (roomId: string) => PendingQuote | undefined\n /** Notified whenever any room's pending quote changes. */\n readonly subscribe: (listener: () => void) => () => void\n /** Starts a reply in one room, replacing whatever was pending there. */\n readonly start: (quote: PendingQuote) => void\n /** Drops the pending reply for one room, after it was sent or dismissed. */\n readonly clear: (roomId: string) => void\n /**\n * Drops the pending reply on this side only, leaving the host's copy for it to consume.\n *\n * Used when the composer submits: the host attaches the quote to the message it is about to\n * publish and then forgets it. Telling the host to delete at that moment would erase the quote\n * before the message arrives, and the reply would go out bare.\n */\n readonly clearLocal: (roomId: string) => void\n /** Reads the current quote for a room without subscribing, for one-shot decisions. */\n readonly peek: (roomId: string) => PendingQuote | undefined\n}\n\n/**\n * Creates the store.\n *\n * A plain observable rather than a framework store, for the same reason the room list is one:\n * every consumer is a component this plugin already owns, so the subscription contract is just\n * `useSyncExternalStore`.\n *\n * @returns the store the client entry hands to the components that need it.\n */\nexport const createQuoteStore = (): QuoteStore => {\n const quotes = new Map<string, PendingQuote>()\n const listeners = new Set<() => void>()\n\n const notify = (): void => {\n for (const listener of [...listeners]) listener()\n }\n\n return {\n getQuote: (roomId) => quotes.get(roomId),\n peek: (roomId) => quotes.get(roomId),\n subscribe: (listener) => {\n listeners.add(listener)\n return () => { listeners.delete(listener) }\n },\n start: (quote) => {\n quotes.set(quote.roomId, quote)\n notify()\n // The host has to know too: it is the side that attaches the quote, because the message\n // itself is submitted by the official composer and never passes through this plugin.\n // Fire-and-forget — the bar is already on screen, and a failed registration only means the\n // sent message arrives without the quote.\n void registerQuote(quote.roomId, quote).catch(() => undefined)\n },\n clear: (roomId) => {\n const had = quotes.delete(roomId)\n notify()\n if (had) void clearQuote(roomId).catch(() => undefined)\n },\n clearLocal: (roomId) => {\n if (!quotes.delete(roomId)) return\n notify()\n },\n }\n}\n","/**\n * Left/right placement for group-chat messages.\n *\n * The official Chat renderer draws every user message right-aligned — its own CSS is\n * unconditional (`align-items: flex-end`), with no self/other distinction, because in an\n * ordinary Session the only author is the operator. A room has several authors, so the\n * side has to carry a meaning the official renderer does not compute: this workspace's own\n * messages stay right, everyone else's move left.\n *\n * It is implemented by **wrapping the official renderer, not replacing it**. The official\n * component is read back out of the slot registry and rendered as an element, so markdown,\n * images, file and skill references, per-message actions and the streaming behaviour all\n * stay the official implementation's. Re-drawing the bubble here would have to track every\n * one of those forever.\n *\n * An ordinary Session is untouched: a message with no room source is rendered through the\n * official component with no wrapper at all, which is what keeps the two paths equal in\n * what they draw.\n *\n * Another author's message also carries their name and picture above the bubble. The official\n * renderer draws neither — in an ordinary Session every message is the operator's own, so there\n * is no author to attribute — and the event itself carries only the author's id. The name and\n * picture therefore come from the roster, looked up by that id. When the roster has not loaded,\n * or the author has since left, the message renders bare rather than labelled with an id: a\n * wrong name is worse than no name.\n *\n * @module @sparkelf/dsh-group-chat/client/MessageSide\n */\n\nimport { createElement, useSyncExternalStore, type MouseEvent, type ReactNode } from 'react'\nimport { Avatar } from './Avatar.tsx'\nimport type { RoomMember } from './api.ts'\nimport { previewOf, quoteOf, type MessageLike, type QuotedMessage, type QuoteStore } from './quote.ts'\nimport type { GroupChatKey } from './locales.ts'\nimport styles from './MessageSide.module.css'\n\n\n/**\n * Which side a message belongs on, or `undefined` when it is not a room message.\n *\n * `roomId` is the discriminator: the Host stamps it on every message it admits to a room,\n * and an ordinary Session's message carries `{kind, rpcId, clientTimeZone}` instead. The\n * remaining cases split the room's own authors — an agent is always someone else, and a\n * human is \"self\" only when the id matches this workspace's account. A message whose\n * author cannot be identified is placed left, because assuming \"self\" would silently\n * attribute another member's words to the operator.\n *\n * @param message - the message data the node carries.\n * @param selfId - this workspace's own account id, when known.\n * @returns `'self'`, `'other'`, or `undefined` for a non-room message.\n */\nexport const messageSide = (message: MessageLike | undefined, selfId: string | undefined): 'self' | 'other' | undefined => {\n const source = message?.source\n if (source === undefined || source === null) return undefined\n if (typeof source.roomId !== 'string' || source.roomId.length === 0) return undefined\n if (source.authorKind === 'agent') return 'other'\n return selfId !== undefined && source.memberId === selfId ? 'self' : 'other'\n}\n\n/**\n * The roster entry for one message's author, when the room knows it.\n *\n * The bubble needs the name and the picture, and neither is on the event: a message carries only\n * `source.memberId`. The roster is the only place those two facts live, so this is the lookup\n * that lets another author's message say who wrote it.\n *\n * @param message - the message data the node carries.\n * @param roster - that room's members, or `undefined` before the roster has been read.\n * @returns the matching member, or `undefined` when the author is unknown or has left.\n */\nexport const authorOf = (\n message: MessageLike | undefined,\n roster: readonly RoomMember[] | undefined,\n): RoomMember | undefined => {\n const memberId = message?.source?.memberId\n if (typeof memberId !== 'string' || memberId.length === 0) return undefined\n return roster?.find((member) => member.userId === memberId)\n}\n\n/**\n * The name to draw above another author's message.\n *\n * Falls back to the roster's display name only, never to the id: an unlabelled bubble is\n * readable, but a bubble labelled `u_7f3a91` is not, and it would also leak an internal id into\n * the conversation.\n *\n * @param member - the author's roster entry.\n * @returns the name, or `undefined` when the roster has no usable one.\n */\nexport const authorNameOf = (member: RoomMember | undefined): string | undefined => {\n const name = member?.displayName?.trim() ?? ''\n return name.length === 0 ? undefined : name\n}\n\n/** The official node view, as the registry hands it over. */\ntype OfficialView = (props: Record<string, unknown>) => ReactNode\n\n/** Props this wrapper adds to whatever the official node view receives. */\nexport interface MessageSideProps {\n readonly node?: { readonly data?: unknown }\n /** The official node view, read from the slot registry. */\n readonly Official: unknown\n /** This workspace's own account id, when known — read as a snapshot. */\n readonly getSelfId: () => string | undefined\n /** Notified when the account id arrives, so the side is re-decided. */\n readonly subscribeSelf: (listener: () => void) => () => void\n /** Whether this occurrence is the keyed `user` cell, which is the only one that has sides. */\n readonly sided: boolean\n /**\n * Read one room's roster as a snapshot, or `undefined` before it has been read.\n *\n * A function rather than the array itself, for the same reason [getSelfId] is: the roster is\n * read after the first paint, so a value captured when this renderer was registered would stay\n * `undefined` and every bubble would render unlabelled forever.\n */\n readonly getRoster: (roomId: string) => readonly RoomMember[] | undefined\n /**\n * Where a reply started on this message is recorded.\n *\n * The message is the only place that knows which row the reader pointed at, so the button has\n * to live here rather than on the composer: a composer-side control would have to ask \"which\n * message?\" and answer it from a hover that the composer never sees.\n */\n readonly quotes?: QuoteStore\n /**\n * Copy resolver for this plugin's own strings.\n *\n * Kept separate from the `t` the seat supplies: that one is bound to the official `chat`\n * namespace, because the official bubble looks its own copy up there and would print raw keys\n * if this plugin replaced it. Our keys live in this plugin's namespace, so they arrive under\n * their own name.\n */\n readonly tOwn?: (key: GroupChatKey) => string\n}\n\n/**\n * Render one node with its room side applied.\n *\n * Two details this has to get right, both of which fail silently otherwise:\n *\n * The official view is created as a React **element**, never called as a plain function. It\n * is a `React.memo` component, which compiles to a non-callable object, so calling it throws\n * `TypeError: … is not a function`; and even when a view is callable, calling it directly\n * would run its Hooks as this component's and drop its memoization.\n *\n * The account id is read through `useSyncExternalStore` rather than captured. It is fetched\n * after the first paint, and a value captured when this renderer was registered would stay\n * `undefined` for the life of the page — every message would keep the unknown-author\n * placement, and nothing would ever correct it. The store is the same observable the room\n * list uses, and the snapshot is a plain string, so an unrelated room-list change notifies\n * without re-rendering: React compares the snapshot by identity.\n *\n * @param props - the official props plus this wrapper's placement inputs.\n * @returns the official view, wrapped only when the message is another author's room message.\n */\n/**\n * The bar above a bubble showing which message it answers.\n *\n * Drawn by this plugin rather than by the official view: the quote travels as a content block\n * whose type the official renderer does not know, so it would otherwise be dropped. The bar sits\n * outside the bubble so the reply's own text keeps the official layout.\n *\n * @param props - the quote to show.\n * @returns the bar, or `null` when there is nothing worth quoting.\n */\nexport const QuoteBar = ({ quote }: { readonly quote: QuotedMessage }): ReactNode => {\n if (quote.preview.length === 0) return null\n return (\n <div className={styles['quoteBar']} data-group-chat-quote={quote.messageId}>\n <span className={styles['quoteMark']} aria-hidden=\"true\" />\n <span className={styles['quoteBody']}>\n {quote.authorName === undefined ? null : (\n <span className={styles['quoteAuthor']}>{quote.authorName}</span>\n )}\n <span className={styles['quoteText']}>{quote.preview}</span>\n </span>\n </div>\n )\n}\n\n/**\n * The control that starts a reply to one message.\n *\n * Rendered inside the wrapper this plugin already draws around another author's row, which is\n * the only place the message id is known. It reads its own hover state so the button appears\n * over the row it belongs to and nowhere else.\n *\n * @param props - the message it replies to, and the store the pick lands in.\n * @returns the button, or `null` when the message cannot be replied to.\n */\n/** Source kind of the model-only quoted-message context; it must never be drawn as a message. */\nconst QUOTE_CONTEXT_KIND = 'group-chat-quote'\n\nconst ReplyControl = (props: {\n readonly roomId: string\n readonly messageId: string\n readonly authorName?: string\n readonly preview: string\n readonly quotes: QuoteStore\n readonly t: (key: GroupChatKey) => string\n}): ReactNode => {\n const { roomId, messageId, authorName, preview, quotes, t } = props\n if (preview.length === 0) return null\n return (\n <button\n type=\"button\"\n className={styles['replyButton']}\n data-group-chat-reply=\"\"\n aria-label={t('reply')}\n title={t('reply')}\n onClick={() => {\n quotes.start({ roomId, messageId, preview, ...authorName === undefined ? {} : { authorName } })\n }}\n >\n {/* Inline rather than an icon from the primitives package: importing that package pulls its\n CSS into every module that renders a bubble, and the plain-function tests import this\n module without a bundler. The shape is a corner-turning arrow, the usual \"answers that\n message\" mark. */}\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M6 4 2.5 7.5 6 11\" />\n <path d=\"M2.5 7.5h7A3.5 3.5 0 0 1 13 11v1.5\" />\n </svg>\n </button>\n )\n}\n\nexport const MessageSide = (props: MessageSideProps): ReactNode => {\n const { node, Official, getSelfId, subscribeSelf, sided, getRoster, quotes } = props\n // One subscription drives both lookups: identity and roster arrive from separate requests, so\n // subscribing twice would re-render on the same change and could draw the author before the\n // name is known.\n const selfId = useSyncExternalStore(subscribeSelf, getSelfId, getSelfId)\n if (typeof Official !== 'function' && typeof Official !== 'object') return null\n // The official props ride through unchanged apart from this plugin's own additions, which a\n // React component ignores. `t` in particular must survive: the seat supplies it from the\n // namespace this plugin's registration declares.\n const view = createElement(Official as OfficialView, props as unknown as Record<string, unknown>)\n const message = node?.data as MessageLike | undefined\n // The quoted-message context the host injects before a model step is background for the\n // model, not part of the conversation. It carries its own source kind so it is recognizable\n // here; drawing it would put an untrusted-background snapshot in the room under the member's\n // name, and the message it belongs to already shows the quote as a pointer.\n if (message?.source?.kind === QUOTE_CONTEXT_KIND) return null\n const side = sided ? messageSide(message, selfId) : undefined\n const quote = quoteOf(message)\n const roomId = message?.source?.roomId\n const roomKnown = typeof roomId === 'string' && roomId.length > 0\n // The id the room's log knows this message by. `source.messageId` is that row; `id` is the\n // message's own uuid. A reply and a search jump both point at the row — it is what the room\n // stores and what a reply carries — so it is resolved once here, before any branch uses it.\n const sourceMessageId = message?.source?.messageId\n const rawId = message?.id\n const messageId = typeof sourceMessageId === 'string' && sourceMessageId.length > 0\n ? sourceMessageId\n : typeof rawId === 'string' && rawId.length > 0 ? rawId : undefined\n // A message outside a room has no roster to attribute it from and no room to reply into, so it\n // stays exactly as the official renderer drew it. A quote can still be present on a message\n // whose room id went missing; the bar is shown rather than dropped, because losing it would\n // make a reply look like it answered nothing.\n if (side === undefined || !roomKnown) {\n if (quote === undefined) return view\n return (\n <div\n className={styles['selfRow']}\n data-group-chat-side=\"self\"\n {...messageId === undefined ? {} : { 'data-group-chat-message': messageId }}\n >\n <QuoteBar quote={quote} />\n {view}\n </div>\n )\n }\n\n const member = authorOf(message, getRoster(roomId))\n const name = authorNameOf(member)\n // The avatar and the name are drawn only when the room knows this author. An unknown author\n // still moves left — the side is decided from a fact the message carries — but is left\n // unlabelled rather than attributed to a guessed name or an internal id.\n const head = name === undefined || member === undefined\n ? null\n : (\n <div className={styles['author']} data-group-chat-author={member.userId}>\n <Avatar name={name} src={member.avatarUrl} size=\"sm\" />\n <span className={styles['authorName']}>{name}</span>\n </div>\n )\n\n // The host prefixes what it publishes with the speaker's name so the model can tell who is\n // talking. That prefix is part of the stored text, so a quote taken from it would read\n // \"DataOps Admin:DataOps Admin:…\" once the bar draws the name itself.\n const rawPreview = previewOf(message)\n const preview = name === undefined\n ? rawPreview\n : rawPreview.replace(new RegExp(`^${name.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\\\s*[::]\\\\s*`), '')\n // Both halves are required: an id with no text has nothing to quote, and a message with no id\n // cannot be pointed at. Offering the control in either case would store a quote nobody can\n // resolve.\n const replying = quotes !== undefined && messageId !== undefined && preview.length > 0\n\n const actions = replying ? (\n <div className={side === 'self' ? styles['rowActionsSelf'] : styles['rowActions']}>\n <ReplyControl\n roomId={roomId}\n messageId={messageId}\n {...name === undefined ? {} : { authorName: name }}\n preview={preview}\n quotes={quotes}\n t={props.tOwn ?? ((key) => key)}\n />\n </div>\n ) : null\n\n // A message I wrote keeps the official right alignment; the reply control sits on its left,\n // where the bubble has room and where the pointer arrives from.\n if (side === 'self') {\n return (\n <div\n className={styles['selfRow']}\n data-group-chat-side=\"self\"\n {...messageId === undefined ? {} : { 'data-group-chat-message': messageId }}\n >\n {quote === undefined ? null : <QuoteBar quote={quote} />}\n <div className={styles['selfBody']}>\n {view}\n {actions}\n </div>\n </div>\n )\n }\n\n return (\n <div\n className={styles['otherRow']}\n data-group-chat-side=\"other\"\n {...messageId === undefined ? {} : { 'data-group-chat-message': messageId }}\n >\n {head}\n {quote === undefined ? null : <QuoteBar quote={quote} />}\n <div className={styles['rowBody']}>\n {view}\n {actions}\n </div>\n </div>\n )\n}\n","/**\n * The room/session binding, shared by both halves of the plugin.\n *\n * The browser half needs it to open a room's Session; the host half needs it to create and\n * recognize one. It lives in its own module because the two halves are built for different\n * runtimes: importing it from the host entry would drag the whole server side into the client\n * bundle, which the build's platform guard refuses — correctly, since none of it can run there.\n *\n * @module @sparkelf/dsh-group-chat/session-id\n */\n\n/**\n * Every room session id starts with this, so a room's session is recognizable after a restart\n * and an ordinary session never matches.\n *\n * The binding lives in the id rather than in `SessionHeader` because the header carries no\n * free-form metadata field.\n */\nexport const ROOM_SESSION_PREFIX = 'group-room-'\n\n/** Deterministic session id for a room. */\nexport const roomSessionId = (roomId: string): string => `${ROOM_SESSION_PREFIX}${roomId}`\n\n/**\n * Reads the room a session *id* is bound to, or `undefined` for an ordinary session.\n *\n * Separate from {@link roomIdOfSession} because the two call sites receive different things: a\n * seat hands the composer a bare id string, while a session event hands over the session object.\n * Passing a string to the object form silently returns `undefined` — it checks for an object\n * first — which is how a correctly registered composer seat ended up drawing nothing.\n */\nexport const roomIdOfSessionId = (sessionId: string): string | undefined => {\n if (!sessionId.startsWith(ROOM_SESSION_PREFIX)) return undefined\n const roomId = sessionId.slice(ROOM_SESSION_PREFIX.length)\n return roomId.length > 0 ? roomId : undefined\n}\n\n/** Reads the room a session is bound to, or `undefined` for an ordinary session. */\nexport const roomIdOfSession = (session: unknown): string | undefined => {\n if (typeof session !== 'object' || session === null) return undefined\n const id = (session as { id?: unknown }).id\n if (typeof id !== 'string' || !id.startsWith(ROOM_SESSION_PREFIX)) return undefined\n const roomId = id.slice(ROOM_SESSION_PREFIX.length)\n return roomId.length > 0 ? roomId : undefined\n}\n","/**\n * The members panel, shown in the right sidebar beside a room's conversation.\n *\n * A room is a Session, so the shell already draws its messages; what the shell has no reason to\n * know about is the roster behind them. This panel is that missing view: who is in the room, who\n * owns it, and the one message every member is meant to see.\n *\n * It is a **tab in the official right sidebar**, not a dialog. A roster is something a reader\n * consults while reading — opening it must not cover the conversation it describes — and the\n * shell already owns tab placement, width, persistence and the ability to put it side by side\n * with the document preview. Drawing our own overlay would have to track all of that.\n *\n * What the panel shows comes from the shared store rather than its own requests: the sidebar\n * already reads the roster, and a second reader would let the two disagree about who is present.\n * The one exception is writing the announcement, which no other surface can do.\n *\n * @module @sparkelf/dsh-group-chat/client/MembersPanel\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { Button, Input } from '@deepseek-ai/dsh-client-ui-primitives'\nimport {\n announcementMissing,\n announcementView,\n filterMemberRows,\n memberRows,\n type MemberRow,\n} from './rows.ts'\nimport { getRoomFile, listRoomFiles, setAnnouncement, uploadRoomFile, type RoomFiles, type SharedFileText } from './api.ts'\nimport { Avatar } from './Avatar.tsx'\nimport { roomIdOfSession } from '../session-id.ts'\nimport type { GroupChatKey } from './locales.ts'\nimport type { RoomStore } from './store.ts'\nimport styles from './MembersPanel.module.css'\n\n/** The tab type this panel is opened as. */\nexport const MEMBERS_TAB_KIND = 'groupChatMembers'\n\n/**\n * Props the panel reads.\n *\n * `sessionId` comes from the tab seat rather than from navigation parameters: the sidebar\n * persists a tab's place but not the parameters its opener passed, so a reload hands this\n * component a tab with no params at all. Deriving the room from the Session it belongs to is\n * what keeps the panel correct across a reload, and it also decides whether the panel belongs\n * here at all — an ordinary Session has no room, and therefore no roster.\n */\nexport interface MembersPanelInjected {\n /** The Session this tab was opened beside. */\n readonly sessionId: string\n /** Localized copy. */\n readonly t: (key: GroupChatKey) => string\n /** The shared room store, which already holds the roster. */\n readonly store: RoomStore\n}\n\n/**\n * Render the roster and announcement for the room this Session belongs to.\n *\n * @param props - the Session, copy, and shared store.\n * @returns the panel, or a short notice when this Session is not a room.\n */\nexport function MembersPanel(props: MembersPanelInjected): ReactNode {\n const { sessionId, t, store } = props\n const roomId = roomIdOfSession({ id: sessionId })\n const state = useRoomState(store)\n\n // Re-read the roster when the panel opens. The sidebar loads rosters for the rooms it lists,\n // but a reload that lands directly on a room's Session may paint this panel before that read\n // completes, and an empty roster would read as \"nobody is here\".\n useEffect(() => {\n if (roomId !== undefined) void store.loadMembers(roomId)\n }, [roomId, store])\n\n const members = roomId === undefined ? undefined : state.members[roomId]\n const rows = useMemo(\n () => (members === undefined ? undefined : memberRows(members, state.selfId)),\n [members, state.selfId],\n )\n const room = state.rooms?.find((candidate) => candidate.id === roomId)\n\n if (roomId === undefined) {\n return <p className={styles['notice']}>{t('membersNotARoom')}</p>\n }\n\n return (\n <div className={styles['root']}>\n <Announcement\n t={t}\n roomId={roomId}\n announcement={room?.announcement}\n canEdit={room !== undefined && room.ownerUserId === state.selfId}\n onSaved={() => { void store.refresh() }}\n />\n <SharedFiles t={t} roomId={roomId} />\n <Roster\n t={t}\n rows={rows}\n // Only the owner may remove another member: the server enforces the same rule, so\n // hiding the control elsewhere keeps the roster from offering an action that will be\n // refused. The owner's own row never shows it — they cannot remove themselves.\n canRemove={room !== undefined && room.ownerUserId === state.selfId}\n onRemove={(userId) => { void store.removeMember(roomId, userId) }}\n />\n </div>\n )\n}\n\n/**\n * Subscribe to the shared store.\n *\n * Written as a hook rather than read inline so a roster that arrives after the first paint\n * re-renders the panel: the store is a plain observable, not a framework store.\n *\n * @param store - the shared room store.\n * @returns its current state, re-read on every change.\n */\nfunction useRoomState(store: RoomStore): ReturnType<RoomStore['getState']> {\n const [snapshot, setSnapshot] = useState(() => store.getState())\n useEffect(() => {\n setSnapshot(store.getState())\n return store.subscribe(() => { setSnapshot(store.getState()) })\n }, [store])\n return snapshot\n}\n\n/** Props for the announcement area. */\ninterface AnnouncementProps {\n readonly t: (key: GroupChatKey) => string\n readonly roomId: string\n /** The room's announcement, or `undefined` when this deployment has no such field. */\n readonly announcement: string | null | undefined\n /** Whether the operator may write it. Only the owner may, and the server enforces it. */\n readonly canEdit: boolean\n /** Called after a successful save so the room list re-reads the stored value. */\n readonly onSaved: () => void\n}\n\n/**\n * The announcement area: read it, and for the owner, write it.\n *\n * Saving re-reads the room list instead of patching local state. The server stores a cleared\n * announcement as `null`, so a local patch would leave this panel and the sidebar showing\n * different spellings of the same absence.\n *\n * @param props - the text, the permission, and how to report a save.\n * @returns the section.\n */\nfunction Announcement(props: AnnouncementProps): ReactNode {\n const { t, roomId, announcement, canEdit, onSaved } = props\n const view = announcementView(announcement)\n const [editing, setEditing] = useState(false)\n const [draft, setDraft] = useState('')\n const [saving, setSaving] = useState(false)\n const [missing, setMissing] = useState(false)\n const [failure, setFailure] = useState<GroupChatKey | undefined>(undefined)\n\n const begin = useCallback((): void => {\n setDraft(view.state === 'text' ? view.text : '')\n setFailure(undefined)\n setEditing(true)\n }, [view])\n\n const save = useCallback(async (): Promise<void> => {\n setSaving(true)\n setFailure(undefined)\n try {\n await setAnnouncement(roomId, draft.trim())\n setEditing(false)\n onSaved()\n } catch (error) {\n // A deployment without the feature answers 404/405/501. That is a different fact from a\n // refused or failed write, and confusing the two would send the operator looking for a\n // network problem when the workspace simply predates the feature.\n if (announcementMissing(error)) setMissing(true)\n else setFailure('announcementFailed')\n } finally {\n setSaving(false)\n }\n }, [draft, onSaved, roomId])\n\n return (\n <CollapsibleSection\n t={t}\n title={t('announcementTitle')}\n actions={canEdit && !editing && !missing && view.state !== 'unavailable' ? (\n <button type=\"button\" className={styles['linkButton']} onClick={begin}>\n {view.state === 'empty' ? t('announcementAdd') : t('announcementEdit')}\n </button>\n ) : undefined}\n >\n {missing || view.state === 'unavailable' ? (\n <p className={styles['unavailable']}>{t('announcementUnavailable')}</p>\n ) : editing ? (\n <div className={styles['editor']}>\n <textarea\n className={styles['textarea']}\n value={draft}\n placeholder={t('announcementPlaceholder')}\n onChange={(event) => { setDraft(event.target.value) }}\n onKeyDown={(event) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n setEditing(false)\n return\n }\n // Enter saves and Shift+Enter breaks the line: an announcement is a short notice,\n // and a text area with no save key reads as a form the operator cannot submit.\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault()\n void save()\n }\n }}\n rows={4}\n />\n <div className={styles['editorFooter']}>\n {saving\n ? <span className={styles['hint']}>{t('announcementSaving')}</span>\n : <span className={styles['hint']}>{t('announcementCleared')}</span>}\n <Button size=\"sm\" onClick={() => { setEditing(false) }}>{t('cancel')}</Button>\n <Button size=\"sm\" variant=\"primary\" onClick={() => void save()} disabled={saving}>\n {t('save')}\n </Button>\n </div>\n </div>\n ) : view.state === 'text' ? (\n <p className={styles['announcementText']}>{view.text}</p>\n ) : (\n // One line, not two: \"no announcement yet\" and \"only the owner can edit\" say the same\n // thing twice when the reader is a plain member, and the second alone is what they can\n // act on. The owner-only note is therefore folded into the empty state instead of\n // stacked beneath it.\n <p className={styles['unavailable']}>\n {canEdit ? t('announcementEmpty') : t('announcementOwnerOnly')}\n </p>\n )}\n\n {failure !== undefined && <p className={styles['error']}>{t(failure)}</p>}\n </CollapsibleSection>\n )\n}\n\n/**\n * A collapsible section header.\n *\n * The right sidebar can be dragged narrow, and this panel stacks three sections in it. A\n * section that stays open at 200px squeezes the roster until names collide, so each section\n * can collapse to its title — the title row is the thinnest thing that still tells a member\n * what exists below. Collapsed state is local: a member who folded \"shared files\" to make\n * room for the roster is describing what they are looking at now, not a preference to\n * persist across rooms.\n *\n * @param props - visible title, optional count, and the body to hide.\n * @returns the section.\n */\nfunction CollapsibleSection(props: {\n title: string\n count?: number\n defaultOpen?: boolean\n actions?: ReactNode\n t: (key: GroupChatKey) => string\n children: ReactNode\n}): ReactNode {\n const { title, count, defaultOpen = true, actions, t, children } = props\n const [open, setOpen] = useState(defaultOpen)\n return (\n <section className={styles['section']}>\n <div className={styles['sectionHeader']}>\n <button\n type=\"button\"\n className={styles['sectionToggle']}\n aria-expanded={open}\n // The label is the disclosure control: a separate caret button would put two targets\n // in a 200px row and leave neither big enough to hit.\n onClick={() => { setOpen((value) => !value) }}\n >\n <span className={styles['caret']} aria-hidden=\"true\">{open ? '\\u25BE' : '\\u25B8'}</span>\n <span className={styles['sectionLabel']}>{title}</span>\n </button>\n {count !== undefined && count > 0 && <span className={styles['count']}>{count}</span>}\n {actions}\n </div>\n {open && <div className={styles['sectionBody']}>{children}</div>}\n </section>\n )\n}\n\n/**\n * The room's shared folder, as the members panel shows it.\n *\n * Read on demand rather than kept in the shared store: the folder changes when someone\n * uploads or when the room's agent writes a file, and a panel that only refreshes on room\n * switch would show a stale list for the whole time it is open. Re-reading each time the\n * panel opens keeps the list honest without a poll of its own.\n *\n * @param props - the room and copy.\n * @returns the section.\n */\nfunction SharedFiles(props: { t: (key: GroupChatKey) => string; roomId: string }): ReactNode {\n const { t, roomId } = props\n const [state, setState] = useState<RoomFiles | undefined>(undefined)\n const [preview, setPreview] = useState<SharedFileText | undefined>(undefined)\n const [previewFailure, setPreviewFailure] = useState<string | undefined>(undefined)\n const [uploading, setUploading] = useState(false)\n const [uploadFailure, setUploadFailure] = useState<string | undefined>(undefined)\n const [reloadKey, setReloadKey] = useState(0)\n const [subPath, setSubPath] = useState('')\n const fileInput = useRef<HTMLInputElement>(null)\n\n useEffect(() => {\n let live = true\n void listRoomFiles(roomId, subPath)\n .then((next) => { if (live) setState(next) })\n // A failed read is not an empty folder: showing \"no files\" would misreport a broken\n // mount as an empty one, so the section says so plainly instead.\n .catch(() => { if (live) setState({ path: null, files: [] }) })\n return () => { live = false }\n }, [roomId, reloadKey, subPath])\n\n if (state === undefined) return null\n // Breadcrumbs come from the server rather than being rebuilt here: it already resolved the\n // path, and a second resolver would disagree with it the first time either changed.\n const crumbs = state.crumbs ?? []\n // No shared tree on this deployment: the section would only ever say \"unavailable\", which\n // is noise for a member who never had the feature.\n if (state.path === null) return null\n\n const openPreview = (path: string): void => {\n setPreviewFailure(undefined)\n void getRoomFile(roomId, path)\n .then((next) => { setPreview(next) })\n .catch(() => { setPreviewFailure(t('membersFileUnreadable')) })\n }\n\n const upload = (picked: File): void => {\n setUploading(true)\n setUploadFailure(undefined)\n void uploadRoomFile(roomId, picked)\n .then(() => { setReloadKey((value) => value + 1) })\n .catch(() => { setUploadFailure(t('membersFileUploadFailed')) })\n .finally(() => {\n setUploading(false)\n // Reset so picking the same file twice still fires a change event.\n if (fileInput.current !== null) fileInput.current.value = ''\n })\n }\n\n return (\n <CollapsibleSection\n t={t}\n title={t('membersFilesTitle')}\n count={state.files.length}\n actions={(\n <>\n {/* A hidden input, opened by the button: the native file dialog is the only picker\n that can hand back a File without a second permission step. */}\n <input\n ref={fileInput}\n type=\"file\"\n className={styles['filePicker']}\n aria-label={t('membersFileUpload')}\n onChange={(event) => {\n const picked = event.target.files?.[0]\n if (picked !== undefined) upload(picked)\n }}\n />\n <button\n type=\"button\"\n className={styles['refresh']}\n aria-label={t('membersFileUpload')}\n title={t('membersFileUpload')}\n disabled={uploading}\n onClick={() => { fileInput.current?.click() }}\n >\n {uploading ? '\\u22EF' : '+'}\n </button>\n {/* The folder changes when the room's agent writes a file, and this panel can stay\n open for a long time; a manual refresh is cheaper than polling and honest about\n when the list is from. */}\n <button\n type=\"button\"\n className={styles['refresh']}\n aria-label={t('membersFilesRefresh')}\n title={t('membersFilesRefresh')}\n onClick={() => { setReloadKey((value) => value + 1) }}\n >\n {'\\u21BB'}\n </button>\n </>\n )}\n >\n {crumbs.length > 0 && (\n <nav className={styles['crumbs']} aria-label={t('membersFilesTitle')}>\n <button type=\"button\" className={styles['crumb']} onClick={() => { setSubPath('') }}>\n {t('membersFilesRoot')}\n </button>\n {crumbs.map((crumb) => (\n <span key={crumb.path} className={styles['crumbGroup']}>\n <span className={styles['crumbSep']} aria-hidden=\"true\">/</span>\n <button\n type=\"button\"\n className={styles['crumb']}\n onClick={() => { setSubPath(crumb.path) }}\n >\n {crumb.name}\n </button>\n </span>\n ))}\n </nav>\n )}\n {state.files.length === 0 ? (\n <p className={styles['hint']}>{t('membersFilesEmpty')}</p>\n ) : (\n <ul className={styles['list']}>\n {state.files.map((file) => (\n <li key={file.path} className={styles['row']}>\n {file.directory ? (\n <button\n type=\"button\"\n className={styles['fileButton']}\n onClick={() => { setSubPath(joinPath(subPath, file.path)) }}\n title={file.path}\n >\n <span className={styles['fileIcon']} aria-hidden=\"true\">{'\\u{1F4C1}'}</span>\n <span className={styles['name']}>{file.name}</span>\n </button>\n ) : (\n <button\n type=\"button\"\n className={styles['fileButton']}\n onClick={() => { openPreview(joinPath(subPath, file.path)) }}\n title={file.path}\n >\n <span className={styles['fileIcon']} aria-hidden=\"true\">{'\\u{1F4C4}'}</span>\n <span className={styles['name']}>{file.name}</span>\n <span className={styles['fileSize']}>{formatBytes(file.size)}</span>\n </button>\n )}\n </li>\n ))}\n </ul>\n )}\n {previewFailure !== undefined && <p className={styles['hint']}>{previewFailure}</p>}\n {uploadFailure !== undefined && <p className={styles['error']}>{uploadFailure}</p>}\n {preview !== undefined && (\n <section className={styles['preview']} aria-label={preview.name}>\n <div className={styles['previewHeader']}>\n <span className={styles['previewTitle']} title={preview.path}>{preview.name}</span>\n <button\n type=\"button\"\n className={styles['previewClose']}\n aria-label={t('membersFileClose')}\n onClick={() => { setPreview(undefined) }}\n >\n {'\\u00D7'}\n </button>\n </div>\n {preview.image !== undefined ? (\n <img className={styles['previewImage']} src={preview.image} alt={preview.name} />\n ) : preview.textLike ? (\n <>\n <pre className={styles['previewBody']}>{preview.text}</pre>\n {preview.truncated && <p className={styles['hint']}>{t('membersFileTruncated')}</p>}\n </>\n ) : (\n <p className={styles['hint']}>{t('membersFileBinary')}</p>\n )}\n </section>\n )}\n </CollapsibleSection>\n )\n}\n\n/** Joins a folder-relative path onto the folder currently shown. */\nconst joinPath = (prefix: string, name: string): string =>\n prefix === '' ? name : `${prefix}/${name}`\n\n/** Bytes as a short human string; the panel has one column, not a table. */\nconst formatBytes = (bytes: number): string => {\n if (bytes < 1024) return `${String(bytes)} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\n/** Props for the roster. */\ninterface RosterProps {\n readonly t: (key: GroupChatKey) => string\n readonly rows: readonly MemberRow[] | undefined\n /** Whether this reader may remove another member; the owner's own row is excluded too. */\n readonly canRemove: boolean\n /** Remove one member by account id. */\n readonly onRemove: (userId: string) => void\n}\n\n/**\n * The roster: a search box and one row per member.\n *\n * The search box appears only once the roster is long enough that scanning is slower than\n * typing. Below that it would be a control that cannot help.\n *\n * @param props - the rows and copy.\n * @returns the section.\n */\nfunction Roster(props: RosterProps): ReactNode {\n const { t, rows, canRemove, onRemove } = props\n const [query, setQuery] = useState('')\n // The field starts folded away: a roster of a handful of names reads faster than a control row,\n // and the header already carries the section's actions at the same weight. The button is the way\n // in; while the field is open the same button closes it and clears the query.\n const [searching, setSearching] = useState(false)\n const visible = useMemo(\n () => (rows === undefined ? undefined : filterMemberRows(rows, query)),\n [rows, query],\n )\n const closeSearch = (): void => { setSearching(false); setQuery('') }\n\n return (\n <CollapsibleSection\n t={t}\n title={t('membersRosterTitle')}\n count={rows?.length}\n actions={(\n <button\n type=\"button\"\n className={styles['searchToggle']}\n aria-label={searching ? t('membersSearchClear') : t('membersSearchOpen')}\n aria-expanded={searching}\n title={searching ? t('membersSearchClear') : t('membersSearchOpen')}\n onClick={() => { if (searching) closeSearch(); else setSearching(true) }}\n >\n {searching ? (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n strokeWidth=\"1.6\" strokeLinecap=\"round\" aria-hidden=\"true\">\n <path d=\"M4 4l8 8M12 4l-8 8\" />\n </svg>\n ) : (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n strokeWidth=\"1.6\" strokeLinecap=\"round\" aria-hidden=\"true\">\n <circle cx=\"7\" cy=\"7\" r=\"4.5\" />\n <path d=\"M10.5 10.5L14 14\" />\n </svg>\n )}\n </button>\n )}\n >\n\n {searching && rows !== undefined && (\n <div className={styles['search']}>\n <Input\n value={query}\n // Opened by an explicit action, so it takes focus; the reader asked for it and would\n // otherwise have to reach for it a second time.\n autoFocus\n placeholder={t('membersSearchPlaceholder')}\n aria-label={t('membersSearch')}\n onChange={(event) => { setQuery(event.target.value) }}\n />\n </div>\n )}\n\n {visible === undefined ? (\n <p className={styles['hint']}>{t('loading')}</p>\n ) : visible.length === 0 ? (\n <p className={styles['hint']}>\n {rows !== undefined && rows.length > 0 ? t('membersSearchEmpty') : t('membersEmpty')}\n </p>\n ) : (\n <ul className={styles['list']}>\n {visible.map((row) => (\n <li key={row.userId} className={styles['row']}>\n <Avatar name={row.name} src={row.avatarUrl} size=\"md\" />\n <span className={styles['name']} title={row.name}>{row.name}</span>\n {row.self && <span className={styles['selfTag']}>{t('membersSelf')}</span>}\n {row.owner && <span className={styles['ownerTag']}>{t('membersOwner')}</span>}\n {canRemove && !row.self && (\n <button\n type=\"button\"\n className={styles['remove']}\n title={t('removeMember')}\n aria-label={t('removeMember')}\n onClick={() => { onRemove(row.userId) }}\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n strokeWidth=\"1.6\" strokeLinecap=\"round\" aria-hidden=\"true\">\n <path d=\"M3 4.5h10M6.5 4.5V3h3v1.5M5 4.5l.6 8a1 1 0 0 0 1 1h2.8a1 1 0 0 0 1-1l.6-8\" />\n </svg>\n </button>\n )}\n </li>\n ))}\n </ul>\n )}\n </CollapsibleSection>\n )\n}\n\n/**\n * The chip that labels the members tab in the sidebar's tab strip.\n *\n * The sidebar calls this separately from the body so the strip can render without mounting every\n * placed tab; it therefore needs the label alone, and none of the body's data.\n *\n * @param props - the localized copy.\n * @returns the tab's label.\n */\nexport function MembersTabTitle(props: { readonly t: (key: GroupChatKey) => string }): ReactNode {\n return props.t('membersTitle')\n}\n","/**\n * The members page's box on the right sidebar's start guide, drawn only while a room is open.\n *\n * The start guide lists every registered page type so a reader can open one. Its list is\n * global — the registry builds it from every registration and offers no per-Session filter — so\n * a type that appears there is offered in every conversation, including ordinary ones that have\n * no room behind them. Picking it there would open a panel that can only say \"not a group chat\",\n * which is a dead end for the reader and noise in the guide.\n *\n * The guide renders each box through its own entry seat, keyed by the contributing type's id, so\n * a type may replace its own box. This draws the box while a room is open and nothing otherwise.\n * The cell wrapping it is a fixed-width, auto-height container with no border or background, so\n * an empty box leaves no gap.\n *\n * Rendering `null` rather than unregistering the type is deliberate: the guide is consulted once\n * per registration change, not per navigation, so a registration toggled by the current Session\n * would either be stale by the time the guide renders or churn on every room switch. The type\n * also has to stay registered for a placed tab to survive a reload.\n *\n * @module @sparkelf/dsh-group-chat/client/GuideEntry\n */\n\nimport type { ReactNode } from 'react'\nimport { roomIdOfSession } from '../session-id.ts'\nimport styles from './GuideEntry.module.css'\n\n/**\n * What the guide passes to one entry box.\n *\n * `sessionId` is the guide's own scope: its entry seat is declared `scope: 'session'`, so the\n * framework supplies the Session the guide is drawing beside. `entryId` and `kind` identify which\n * box is being drawn, and the resolved title and description are the guide's own copy — a\n * replacement re-renders them rather than deriving them again.\n */\nexport interface MembersGuideEntryProps {\n /** The contributing entry's id, as registered in this type's `guide` array. */\n readonly entryId: string\n /** The page type this box opens. */\n readonly kind: string\n /** The box's title, already localized by the guide. */\n readonly title: string\n /** The one-line description, present only while the guide lists few enough boxes. */\n readonly description?: string\n /** The Session this guide is drawing beside. */\n readonly sessionId: string\n /** Reveal this page type; supplied by the plugin, since a box's own opener is not passed in. */\n readonly onPick?: () => void\n}\n\n/**\n * Draw the members box, or nothing when the open conversation is not a room.\n *\n * @param props - the box's copy, its Session, and how to open the page.\n * @returns the box, or `null` outside a room.\n */\nexport function MembersGuideEntry(props: MembersGuideEntryProps): ReactNode {\n const { title, description, sessionId, onPick } = props\n // An ordinary conversation has no room, and this page has nothing to show in one. Drawing\n // nothing is what keeps the box off the guide; the wrapping cell collapses to no height.\n if (roomIdOfSession({ id: sessionId }) === undefined) return null\n\n return (\n <button\n type=\"button\"\n className={styles['entry']}\n data-group-chat-guide-entry={props.entryId}\n onClick={() => { onPick?.() }}\n >\n <span className={styles['icon']} aria-hidden=\"true\">\n {/* Drawn inline: this package carries no icon set, and one glyph does not justify a\n dependency. The paths are the familiar \"people\" mark, at the guide's own 26px. */}\n <svg width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n strokeWidth=\"1.7\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\" />\n <circle cx=\"9\" cy=\"7\" r=\"4\" />\n <path d=\"M22 21v-2a4 4 0 0 0-3-3.87\" />\n <path d=\"M16 3.13a4 4 0 0 1 0 7.75\" />\n </svg>\n </span>\n <span className={styles['text']}>\n <span className={styles['title']}>{title}</span>\n {description !== undefined && <span className={styles['description']}>{description}</span>}\n </span>\n </button>\n )\n}\n","/**\n * The two composer-side pieces of a reply: the button that starts one and the bar that shows\n * the message being answered.\n *\n * Both are seats on official composers, and both are deliberately dumb: they read a pending\n * quote from the store and call back into it. Nothing here sends a message — the quote reaches\n * the room because `MessageSide` attaches it when the reply is submitted, and keeping that\n * decision in one place is why these two do not also try to.\n *\n * @module @sparkelf/dsh-group-chat/client/QuoteComposer\n */\n\nimport { useSyncExternalStore, type ReactNode } from 'react'\nimport { IconShareOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { GroupChatKey } from './locales.ts'\nimport type { QuoteStore } from './quote.ts'\nimport type { RoomStore } from './store.ts'\nimport styles from './QuoteComposer.module.css'\n\n/** The message a reply would point at, once the reader has picked one. */\nexport interface ReplyTarget {\n /** The id the room's event log knows this message by. */\n readonly messageId: string\n /** One line of what it said. */\n readonly preview: string\n /** Who said it, when the roster knows them. */\n readonly authorName?: string\n}\n\n/** Props the reply button seat receives. */\nexport interface ReplyButtonProps {\n readonly t: (key: GroupChatKey) => string\n /** The room this composer is writing into, or `undefined` outside a room. */\n readonly roomId: string | undefined\n readonly quotes: QuoteStore\n /** The message the reader picked, or `undefined` while they have not picked one. */\n readonly target: ReplyTarget | undefined\n}\n\n/**\n * The button that begins a reply.\n *\n * The share glyph, which is what this plugin's icon set offers for \"point at another message\";\n * there is no reply or quote icon, and a hand-rolled SVG would be the only one in the region.\n *\n * Rendered only inside a room with a message under the cursor, because a reply needs a target.\n */\nexport const ReplyButton = (props: ReplyButtonProps): ReactNode => {\n const { t, roomId, quotes, target } = props\n if (roomId === undefined || target === undefined) return null\n return (\n <button\n type=\"button\"\n className={styles.button}\n aria-label={t('reply')}\n title={t('reply')}\n onClick={() => {\n quotes.start({ roomId, ...target })\n }}\n >\n <IconShareOutlineRegular size={14} />\n </button>\n )\n}\n\n/** Props the quote bar seat receives. */\nexport interface QuoteBarProps {\n readonly t: (key: GroupChatKey) => string\n readonly quotes: QuoteStore\n /**\n * The room list store, read only for `activeRoomId`.\n *\n * The composer's seat hands over a session id, but it is not the room session's: the seat the\n * composer renders for is an ordinary `session-*`, so deriving the room from it never resolves.\n * The store already tracks which room the main panel has open, which is the same room the\n * composer is writing into.\n */\n readonly store: RoomStore\n}\n\n/**\n * The bar above the composer showing what the reply answers.\n *\n * Sits in the composer's own stack rather than over the input, so the text the reader types is\n * never covered by it. Absent when nothing is being answered, which is the common case.\n */\nexport const PendingQuoteBar = (props: QuoteBarProps): ReactNode => {\n const { t, quotes, store } = props\n const roomId = useSyncExternalStore(store.subscribe, () => store.getState().activeRoomId, () => store.getState().activeRoomId)\n const read = (): ReturnType<QuoteStore['getQuote']> => roomId === undefined ? undefined : quotes.getQuote(roomId)\n const quote = useSyncExternalStore(quotes.subscribe, read, read)\n if (roomId === undefined || quote === undefined) return null\n return (\n <div className={styles.bar} data-group-chat-pending-quote={quote.messageId}>\n <span className={styles.mark} aria-hidden=\"true\" />\n <span className={styles.body}>\n {quote.authorName === undefined ? null : (\n <span className={styles.author}>{quote.authorName}</span>\n )}\n <span className={styles.text}>{quote.preview}</span>\n </span>\n <button\n type=\"button\"\n className={styles.dismiss}\n aria-label={t('replyCancel')}\n title={t('replyCancel')}\n onClick={() => { quotes.clear(roomId) }}\n >\n ×\n </button>\n </div>\n )\n}\n","/**\n * Room messages as `@` references in the composer.\n *\n * Registering a trigger source is the one supported way a plugin adds something to the `@`\n * menu, and the only way to get an official inline reference: the pipeline turns a pick into\n * an atomic chip, and the codec decides what that chip becomes for the model. Drawing a\n * separate quote bar would mean a second, unofficial representation of the same idea.\n *\n * A chip carries an id; the words are resolved through this module's cache because the codec\n * only ever receives `ref`. The cache is bounded to the messages listed most recently, which\n * is the same window the menu offers.\n *\n * @module @sparkelf/dsh-group-chat/client/messageReference\n */\n\nimport { listEvents } from './api.ts'\nimport type { RoomStore } from './store.ts'\n\n/** Unique source name; duplicate `(trigger, name)` pairs throw at registration. */\nconst SOURCE_NAME = '@sparkelf/dsh-group-chat/message'\n\n/** How many messages the menu offers at once. */\nconst MAX_CANDIDATES = 20\n\n/** How much of a message the menu row and the chip label show. */\nconst LABEL_CHARS = 28\n\n/** One resolved reference, kept for the codec and for the chip label. */\ninterface Resolved {\n readonly label: string\n readonly text: string\n readonly authorName?: string\n}\n\n/**\n * Reference id → what it points at.\n *\n * `ref` is `${roomId}:${messageId}` so it stays readable in storage and unique across rooms;\n * the words ride beside it because the codec is synchronous about identity and only receives\n * the id.\n */\nconst resolved = new Map<string, Resolved>()\n\n/** The text of a message body, whether it arrived as blocks or as one plain string. */\nconst textOf = (content: unknown): string => {\n if (typeof content === 'string') return content.replace(/\\s+/g, ' ').trim()\n if (!Array.isArray(content)) return ''\n const parts: string[] = []\n for (const block of content) {\n if (typeof block !== 'object' || block === null) continue\n const record = block as { readonly type?: unknown; readonly text?: unknown }\n if (record.type === 'text' && typeof record.text === 'string') parts.push(record.text)\n }\n return parts.join(' ').replace(/\\s+/g, ' ').trim()\n}\n\n/** One line, cut to `LABEL_CHARS` with an ellipsis. */\nconst shorten = (text: string): string =>\n text.length > LABEL_CHARS ? `${text.slice(0, LABEL_CHARS - 1)}…` : text\n\n/** What one message row shows and what a pick would insert. */\nconst rowOf = (roomId: string, record: { readonly id: string; readonly event: { readonly data?: Record<string, unknown> } }): {\n readonly ref: string\n readonly name: string\n readonly description: string\n readonly resolved: Resolved\n} | undefined => {\n const data = record.event.data\n if (data === undefined) return undefined\n const source = data['source'] as { messageId?: unknown; memberName?: unknown } | undefined\n const rowId = typeof source?.messageId === 'string' && source.messageId.length > 0\n ? source.messageId\n : typeof data['id'] === 'string' ? data['id'] : record.id\n const text = textOf(data['content'])\n if (text.length === 0) return undefined\n const authorName = typeof source?.memberName === 'string' && source.memberName.length > 0\n ? source.memberName\n : undefined\n const label = `${authorName ?? '消息'}:${shorten(text)}`\n return {\n ref: `${roomId}:${rowId}`,\n name: label,\n description: text.length > 60 ? `${text.slice(0, 59)}…` : text,\n resolved: { label, text, ...authorName === undefined ? {} : { authorName } },\n }\n}\n\n/** The slice of the trigger service this module registers into. */\nexport interface InputTriggersLike {\n registerSource(source: unknown): () => void\n}\n\n/**\n * Registers the room-message reference source.\n *\n * @param inputTriggers - the trigger pipeline service (`ctx.get('inputTriggers')`).\n * @param store - room store, read for the room the composer is writing into.\n * @returns the disposer.\n */\nexport const registerMessageSource = (inputTriggers: InputTriggersLike, store: RoomStore): (() => void) => {\n const source = {\n trigger: '@',\n name: SOURCE_NAME,\n order: 30,\n showGroupTitle: true,\n async candidates(_session: unknown, request: { readonly query: string; readonly signal: AbortSignal }) {\n const roomId = store.getState().activeRoomId\n if (roomId === undefined) return []\n const page = await listEvents(roomId)\n const needle = request.query.trim().toLowerCase()\n const out: { name: string; label: string; description: string; icon: string; value: string }[] = []\n // Newest first: a quote is nearly always of something just said.\n for (const record of [...page.events].reverse()) {\n if (request.signal.aborted) break\n const row = rowOf(roomId, record)\n if (row === undefined) continue\n if (needle.length > 0 && !row.resolved.text.toLowerCase().includes(needle)) continue\n resolved.set(row.ref, row.resolved)\n out.push({ name: row.name, label: row.name, description: row.description, icon: 'session', value: row.ref })\n if (out.length >= MAX_CANDIDATES) break\n }\n return out\n },\n onPick(pick: { readonly candidate: { readonly value?: string } }) {\n const ref = pick.candidate.value\n if (ref === undefined) return undefined\n const found = resolved.get(ref)\n if (found === undefined) return undefined\n return {\n insert: {\n source: SOURCE_NAME,\n ref,\n label: found.label,\n appearance: 'session',\n clipboardText: `@${found.label}`,\n },\n }\n },\n codec: {\n // What lands in the draft and the clipboard: the human-facing mention.\n clipboardText: (ref: string): string => {\n const found = resolved.get(ref)\n return found === undefined ? ref : `@${found.label}`\n },\n // What the model reads in place of the chip. A blockquote keeps the author on the first\n // line, which is what makes a quote readable after the session is compacted.\n serialize: (ref: string): Promise<string> => {\n const found = resolved.get(ref)\n if (found === undefined) return Promise.resolve(ref)\n const speaker = found.authorName === undefined ? '' : `${found.authorName}:`\n return Promise.resolve(`> ${speaker}${found.text}`)\n },\n },\n }\n return inputTriggers.registerSource(source)\n}","/**\n * Group-chat copy, in the shape every locale-backed DSH client plugin uses: one\n * English object is the key set, and each other locale is a complete record over\n * that key set so a missing translation fails to compile.\n *\n * @module @sparkelf/dsh-group-chat/client/locales\n */\n\n/** English group-chat copy. */\nexport const en = {\n nav: 'Group chat',\n title: 'Group chat',\n description: 'Rooms this workspace has joined. @ yourself to make the agent answer; every other message is recorded and read, but nothing runs.',\n loading: 'Loading rooms…',\n retry: 'Retry',\n\n emptyRooms: 'No rooms yet',\n createRoom: 'New room',\n roomName: 'Room name',\n create: 'Create',\n search: 'Search rooms',\n searchPlaceholder: 'Search rooms or messages…',\n searchClear: 'Clear search',\n searchEmpty: 'No room matches',\n discoverHeading: 'Rooms you can join',\n discoverMembers: '{count} members',\n joinRoom: 'Join',\n joining: 'Joining…',\n viewOptions: 'View options',\n orderBy: 'Sort',\n orderByUpdated: 'Recently active',\n orderByManual: 'Manual',\n archivedFilter: 'Left rooms',\n archivedHide: 'Hide',\n archivedShow: 'Show',\n copyRoomId: 'Copy room ID',\n left: 'Left',\n memberCountShort: '{count}',\n unreadAria: '{count} unread',\n unreadMentionAria: '{count} unread, one mentions you',\n createTitle: 'New room',\n createName: 'Room name',\n createCwd: 'Working directory',\n createCwdHint: 'Determines which files the agent may change. Members of this room can ask the agent to modify anything under this directory.',\n createPreset: 'Agent preset',\n createPresetHint: 'Determines which tools the agent has in this room.',\n createPresetDefault: \"The deployment's default preset\",\n createPresetUnavailable: 'The preset list is unavailable; the room will use the deployment default.',\n createMembers: 'Initial members',\n createMembersHint: 'You become the first member. Others can be added after the room exists.',\n createPolicy: 'Who may invite',\n createVisibility: 'Who can find this room',\n createTrigger: 'When the agent replies',\n createNotice: 'These settings control where the room appears in the interface. They are not access control.',\n menuRename: 'Rename',\n menuInvite: 'Invite members',\n menuSettings: 'Room settings',\n menuMembers: 'Manage members',\n menuTransfer: 'Transfer ownership',\n menuTransferTitle: 'Transfer ownership',\n menuTransferHint: 'Choose the member who becomes the owner. You become an ordinary member and can then leave.',\n menuTransferConfirm: 'Transfer',\n menuTransferEmpty: 'There is no other member to transfer to yet. Invite someone first.',\n menuOwnerCannotLeave: 'The owner cannot leave. Transfer ownership, or dissolve the room.',\n menuDissolve: 'Dissolve room',\n menuDissolveTarget: 'Room to dissolve: ',\n menuDissolveConfirm: 'Dissolve this room? Every member loses it, and its messages go with it. This cannot be undone.',\n menuLeave: 'Leave room',\n menuLeaveConfirm: 'Leave this room? The history your agent already recorded stays.',\n renameTitle: 'Rename room',\n renamePlaceholder: 'New name',\n membersTitle: 'Manage members',\n /** The roster section's own heading; the tab label above already says \"Manage members\". */\n membersRosterTitle: 'Members',\n membersSearch: 'Search members',\n membersSearchOpen: 'Search members',\n membersSearchPlaceholder: 'Search members…',\n membersSearchClear: 'Clear search',\n membersSearchEmpty: 'No member matches',\n membersEmpty: 'This room has no members yet.',\n membersError: 'The member list could not be read. Retry from the room menu.',\n announcementTitle: 'Announcement',\n announcementEmpty: 'No announcement yet',\n announcementAdd: 'Add announcement',\n announcementEdit: 'Edit',\n announcementPlaceholder: 'Write something every member should see',\n announcementUnavailable: 'Announcements are unavailable in this deployment',\n announcementSaving: 'Saving…',\n announcementOwnerOnly: 'Only the room owner can edit the announcement',\n announcementCleared: 'The announcement was cleared',\n announcementFailed: 'Could not save the announcement. Try again.',\n membersSelf: 'You',\n membersFilesTitle: 'Shared files',\n membersFilesEmpty: 'Nothing shared yet.',\n membersFilesRefresh: 'Refresh files',\n membersFileClose: 'Close preview',\n membersFileUnreadable: 'Could not open that file.',\n membersFileTruncated: 'Only the beginning is shown.',\n membersFileBinary: 'Binary file — no text preview.',\n membersSectionCollapsed: 'Collapsed',\n membersFileUpload: 'Upload a file',\n membersFileUploadFailed: 'Upload failed.',\n resizeRegion: 'Resize the group chat region',\n reply: 'Reply',\n replyCancel: 'Cancel reply',\n searchMessages: 'Search messages',\n searchMessagesPlaceholder: 'Search message text',\n searchMessagesEmpty: 'No message matches',\n searching: 'Searching…',\n membersFilesRoot: 'Files',\n membersOwner: 'Owner',\n membersNotARoom: 'This conversation is not a group chat, so it has no members.',\n membersGuideHint: 'Members and the announcement for this room',\n inviteTitle: 'Invite members',\n invitePlaceholder: 'Search DataOps accounts by name or handle',\n inviteHint: 'Only existing DataOps accounts can be added.',\n inviteSubmit: 'Invite',\n inviteEmpty: 'No matching accounts. They may already be in this room.',\n inviteSearching: 'Searching…',\n inviteRemove: 'Remove',\n inviteSearchLabel: 'Search accounts',\n inviteSelectedCount: '{count} selected',\n inviteSubmitCount: 'Invite ({count})',\n optionEmpty: 'Nothing to choose from.',\n pickThisDirectory: 'Use this directory',\n settingsTitle: 'Room settings',\n removeMember: 'Remove',\n ownerBadge: 'Owner',\n you: 'You',\n createSubmit: 'Create',\n cancel: 'Cancel',\n\n members: 'Members',\n memberCount: 'members',\n invite: 'Invite',\n leave: 'Leave room',\n leaveConfirm: 'Leave this room? Your own agent keeps the history it already recorded.',\n owner: 'Owner',\n\n sendPlaceholder: 'Message the room. @ yourself to have your agent answer.',\n send: 'Send',\n sending: 'Sending…',\n\n agentBadge: 'agent',\n triggeredHint: 'Your agent is answering.',\n\n notConfigured: 'The group-chat server is not configured, so no rooms can be listed.',\n unreachable: 'The group-chat server did not answer.',\n credentialMissing: 'No workspace credential is available, so rooms cannot be read.',\n credentialRejected: 'The workspace credential was refused. Sign in to DataOps again, then retry.',\n ownerCannotLeave: 'The owner cannot leave. Transfer ownership, or dissolve the room.',\n\n policy: 'Who may invite',\n policyAnyone: 'Any member',\n policyAdmin: 'Owner only',\n discoverability: 'Discoverability',\n discoverable: 'Listed in search',\n inviteOnly: 'Invite only',\n\n trigger: 'When the agent replies',\n triggerMention: 'Only when @-mentioned',\n triggerAlways: 'Every message',\n triggerNever: 'Never (record only)',\n\n save: 'Save',\n saved: 'Saved',\n}\n\n/** The key set every locale must cover. */\nexport type GroupChatKey = keyof typeof en\n\n/** Simplified Chinese group-chat copy. */\nexport const zh = {\n nav: '群聊',\n title: '群聊',\n description: '本工作区已加入的群聊。@ 自己会触发 agent 应答;其他消息都会记录并进入上下文,但不执行。',\n loading: '正在加载群聊…',\n retry: '重试',\n\n emptyRooms: '暂无群聊',\n createRoom: '新建群聊',\n roomName: '群聊名称',\n create: '创建',\n search: '搜索群聊',\n searchPlaceholder: '搜索群聊或消息…',\n searchClear: '清除搜索',\n searchEmpty: '无匹配结果',\n discoverHeading: '可以加入的群聊',\n discoverMembers: '{count} 位成员',\n joinRoom: '加入',\n joining: '加入中…',\n viewOptions: '视图选项',\n orderBy: '排序',\n orderByUpdated: '最近活跃',\n orderByManual: '手动',\n archivedFilter: '已退出的群聊',\n archivedHide: '隐藏',\n archivedShow: '显示',\n copyRoomId: '复制群聊 ID',\n left: '已退出',\n memberCountShort: '{count}',\n unreadAria: '{count} 条未读',\n unreadMentionAria: '{count} 条未读,其中有 @ 你',\n createTitle: '新建群聊',\n createName: '群聊名称',\n createCwd: '工作目录',\n createCwdHint: '决定机器人能操作哪些文件。此群成员可以让机器人修改该目录下的任何文件。',\n createPreset: 'Agent preset',\n createPresetHint: '决定机器人在本群可用的工具能力。',\n createPresetDefault: '跟随部署默认预设',\n createPresetUnavailable: '预设列表暂时读取不到;创建后将使用部署默认预设。',\n createMembers: '初始成员',\n createMembersHint: '创建者自动成为第一个成员;其余成员可在建群后邀请。',\n createPolicy: '谁能邀请成员',\n createVisibility: '谁能在群里发现这个房间',\n createTrigger: '机器人何时回复',\n createNotice: '这些设置控制房间在界面上的可见性,不构成访问控制。',\n menuRename: '重命名',\n menuInvite: '邀请成员',\n menuSettings: '群聊设置',\n menuMembers: '成员管理',\n menuTransfer: '转让群主',\n menuTransferTitle: '转让群主',\n menuTransferHint: '选择一位成员成为新群主。转让后你会变成普通成员,那时就可以退出群聊。',\n menuTransferConfirm: '转让',\n menuTransferEmpty: '目前没有其他成员可以转让,请先邀请成员。',\n menuOwnerCannotLeave: '群主不能退出群聊。请先转让群主,或解散群聊。',\n menuDissolve: '解散群聊',\n menuDissolveTarget: '将要解散群聊:',\n menuDissolveConfirm: '解散这个群聊?所有成员都会失去它,群消息也会一并删除,且无法恢复。',\n menuLeave: '退出群聊',\n menuLeaveConfirm: '退出这个群聊?你自己的 agent 已记录的历史会保留。',\n renameTitle: '重命名群聊',\n renamePlaceholder: '新名称',\n membersTitle: '成员管理',\n membersRosterTitle: '成员',\n membersSearch: '搜索成员',\n membersSearchOpen: '搜索成员',\n membersSearchPlaceholder: '搜索成员…',\n membersSearchClear: '清除搜索',\n membersSearchEmpty: '没有匹配的成员',\n membersEmpty: '这个群还没有成员。',\n membersError: '成员列表读取失败,可从群聊菜单重试。',\n announcementTitle: '群公告',\n announcementEmpty: '暂无群公告',\n announcementAdd: '+ 添加公告',\n announcementEdit: '编辑',\n announcementPlaceholder: '写一条所有成员都能看到的公告',\n announcementUnavailable: '公告功能暂不可用',\n announcementSaving: '保存中…',\n announcementOwnerOnly: '只有群主可以编辑公告',\n announcementCleared: '公告已清空',\n announcementFailed: '公告保存失败,请重试。',\n membersSelf: '我',\n membersFilesTitle: '群文件',\n membersFilesEmpty: '还没有共享文件。',\n membersFilesRefresh: '刷新文件',\n membersFileClose: '关闭预览',\n membersFileUnreadable: '无法打开该文件。',\n membersFileTruncated: '仅显示开头部分。',\n membersFileBinary: '二进制文件,不提供文本预览。',\n membersSectionCollapsed: '已折叠',\n membersFileUpload: '上传文件',\n membersFileUploadFailed: '上传失败。',\n resizeRegion: '拖动调节群聊区域高度',\n reply: '引用',\n replyCancel: '取消引用',\n searchMessages: '搜索消息',\n searchMessagesPlaceholder: '搜索消息内容',\n searchMessagesEmpty: '没有匹配的消息',\n searching: '搜索中…',\n membersFilesRoot: '群文件',\n membersOwner: '群主',\n membersNotARoom: '该会话不是群聊,没有群成员。',\n membersGuideHint: '本群成员与群公告',\n inviteTitle: '邀请成员',\n invitePlaceholder: '按姓名或账号搜索 DataOps 用户',\n inviteHint: '只能加入已存在的 DataOps 账号。',\n inviteEmpty: '没有匹配的账号,可能已在群里。',\n inviteSearching: '搜索中…',\n inviteRemove: '移除',\n inviteSearchLabel: '搜索账号',\n inviteSelectedCount: '已选 {count} 人',\n inviteSubmitCount: '邀请 ({count})',\n optionEmpty: '没有可选项。',\n pickThisDirectory: '用这个目录',\n inviteSubmit: '邀请',\n settingsTitle: '群聊设置',\n removeMember: '移除',\n ownerBadge: '群主',\n you: '我',\n createSubmit: '创建',\n cancel: '取消',\n\n members: '成员',\n memberCount: '位成员',\n invite: '邀请',\n leave: '退出群聊',\n leaveConfirm: '退出这个群聊?你自己的 agent 会保留已记录的历史。',\n owner: '群主',\n\n sendPlaceholder: '发消息。@ 自己可以让你的 agent 应答。',\n send: '发送',\n sending: '发送中…',\n\n agentBadge: 'agent',\n triggeredHint: '你的 agent 正在应答。',\n\n notConfigured: '未配置群聊服务器,无法列出群聊。',\n unreachable: '群聊服务器没有响应。',\n credentialMissing: '没有可用的工作区凭证,无法读取群聊。',\n credentialRejected: '工作区凭证被拒绝。请重新登录 DataOps 后重试。',\n ownerCannotLeave: '群主不能退出群聊。请先转让群主,或解散群聊。',\n\n policy: '谁可以邀请',\n policyAnyone: '任何成员',\n policyAdmin: '仅群主',\n discoverability: '可发现性',\n discoverable: '可在搜索中列出',\n inviteOnly: '仅限邀请',\n\n trigger: '机器人何时回复',\n triggerMention: '仅被 @ 时回复',\n triggerAlways: '每条都回复',\n triggerNever: '从不回复(纯记录)',\n\n save: '保存',\n saved: '已保存',\n} satisfies Record<GroupChatKey, string>\n","/**\n * Group-chat Client plugin.\n *\n * Registers two official DSH surfaces:\n * - `sidebar.footer.action` renders the group-chat region at the sidebar foot, under the\n * workspace list and above Settings. The shell positions it inside `footerActions`\n * (full width, `flex: none`); this plugin draws the section header and the flat room\n * list. It is an ordinary list seat, so no other plugin's contribution is disturbed.\n * - The matching `main` key gives the room page the region's rows select, so a room opens\n * in the main column rather than replacing the navigation column.\n *\n * This half owns the room list so the two surfaces cannot disagree about which rooms\n * exist. Both read the one store below and both re-read through the same action.\n *\n * The page is a pure consumer of the Host plugin's `/dsh-group-chat/api` route, so every\n * room operation it performs is the same call the agent's own turn makes. The browser\n * never talks to DataOps directly and never handles a credential: the Host holds the\n * workspace identity, and this half only renders what that identity can already see.\n *\n * @module @sparkelf/dsh-group-chat/client\n */\n\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'\n// Brings in the official `sidebarRight` / `sidebarRightTabs` members this plugin consumes, the\n// right-sidebar slot names it registers into, and the merge map its page parameters are typed by.\n// The augmentation lives in the package's `/client` entry, so importing it is what makes those\n// names real in this compilation.\nimport type {} from '@deepseek-ai/dsh-client-ui-sidebar-right/client'\nimport type { SessionId } from '@deepseek-ai/dsh-session/types'\nimport type { MainPanelId } from '@deepseek-ai/dsh-client-ui-layout/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type { StoredEntry } from '@deepseek-ai/dsh-client-ui-slots'\n// The slot this plugin shadows needs a local declaration; see the module doc comment.\nimport './contract.ts'\nimport { SidebarRooms, type SidebarRoomsInjected } from './SidebarRooms.tsx'\nimport { MessageSide } from './MessageSide.tsx'\nimport {\n MembersPanel, MEMBERS_TAB_KIND, MembersTabTitle,\n type MembersPanelInjected,\n} from './MembersPanel.tsx'\nimport { MembersGuideEntry, type MembersGuideEntryProps } from './GuideEntry.tsx'\nimport { createRoomStore, type RoomStore } from './store.ts'\nimport { createQuoteStore } from './quote.ts'\nimport { PendingQuoteBar } from './QuoteComposer.tsx'\nimport { registerMessageSource, type InputTriggersLike } from './messageReference.ts'\nimport { PRESET_LOCALE_NS } from './rows.ts'\nimport { roomSessionId } from '../session-id.ts'\nimport { readRoomState } from './api.ts'\nimport { en, zh, type GroupChatKey } from './locales.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n 'settings.groupChat': GroupChatKey\n }\n}\n\ndeclare module '@deepseek-ai/dsh-client-ui-sidebar-right/client' {\n /**\n * The navigation parameters this page type accepts.\n *\n * The official service types `openTab`'s `params` by this map, and a kind that declares none\n * accepts `undefined` only. Declaring the room here keeps the opener typed against the body's\n * real contract instead of casting the service to a looser shape.\n */\n interface SidebarRightTabParamsMap {\n groupChatMembers: { readonly roomId: string }\n }\n}\n\nconst NS = 'settings.groupChat'\n\n/**\n * The `user` cell this plugin shadows, and the one the official Chat renderer occupies.\n *\n * Named rather than inlined because shadowing is a two-sided fact: winning the cell means\n * reading past the winner to find what would have rendered.\n */\nconst CHAT_NODE_SLOT = 'conversation.chat.node'\n\n/**\n * Every `user`-shaped cell of the chat-node seat.\n *\n * `user` and `steering` are separate cells that render the *same* official component: a room\n * message is `steering` once a turn has claimed it and `user` otherwise, so shadowing only\n * `user` would leave exactly the messages that arrived while the agent was working on the\n * right. Both are cell-local shadows, so neither reaches an ordinary Session: the side is\n * decided from the message's own room source, and an ordinary message has none.\n */\nconst CHAT_NODE_KEYS = ['user', 'steering'] as const\n\n/** Priority this plugin claims its chat-node cells at; the official entries sit at `0`. */\nconst SHADOW_PRIORITY = -1\n\n/**\n * This plugin's own chat-node renderer, so the lookup below cannot mistake it for the\n * official one. Compared by identity against the registered component.\n */\nconst OUR_VIEW = MessageSide\n\n/**\n * The entry the shell would render for one cell without this plugin.\n *\n * The registry projects shadowing winners through `entriesOfSlot`, which is exactly the\n * wrong question here — this plugin is the winner. `entries` keeps the whole ledger, so the\n * official entry is still visible, and picking the lowest-priority live one other than this\n * plugin's own reproduces the pre-plugin choice.\n *\n * @param ledger - every registered entry for the chat-node slot.\n * @param key - the cell being claimed.\n * @param ours - this plugin's own component, excluded from the candidates.\n * @returns the official component, or `undefined` when nothing else is registered.\n */\nconst officialViewFor = (\n ledger: readonly StoredEntry[],\n key: string,\n ours: unknown,\n): StoredEntry['component'] | undefined =>\n ledger\n .filter((entry) => entry.options.key === key && entry.component !== ours)\n .reduce<StoredEntry | undefined>(\n (lowest, entry) =>\n lowest === undefined || (entry.options.priority ?? 0) < (lowest.options.priority ?? 0)\n ? entry\n : lowest,\n undefined,\n )?.component\n\n/**\n * Report why one chat-node cell could not be claimed.\n *\n * A rejected claim is not fatal — the official renderer still draws that cell — but it does\n * mean the side never flips, which is invisible in the UI. The console is the only channel\n * this half has, and the message has to name the cell so the missing side can be traced to it.\n */\nconst logClaimFailure = (key: string, error: unknown): void => {\n console.warn(`[group-chat] could not claim the \"${key}\" chat-node cell; messages of this kind stay on the official side`, error)\n}\n\n/** Main-panel key the sidebar rows select. */\nconst PANEL_ID = 'group-chat' as MainPanelId\n\n/**\n * The members tab type.\n *\n * `MEMBERS_TAB_ID` keys the type's slot entries; `MEMBERS_TAB_KIND` is what `openTab` resolves.\n * They are separate values because the sidebar stores a placed tab by kind while the slot ledger\n * is keyed by id — the same split the official schedule plugin makes.\n */\nconst MEMBERS_TAB_ID = '@sparkelf/dsh-group-chat/members'\n\n/** Id of the pending-reply bar in the composer's dock. */\nconst QUOTE_DOCK_ID = '@sparkelf/dsh-group-chat/quote'\n\n/**\n * The guide entry's own identity, and where it sorts on the start page.\n *\n * An id rather than the tab's: the guide lists one box per entry, and the id is what the\n * entry-level slot replacement below is keyed by. The order lands the box among the other\n * `extension` types instead of at either end.\n */\nconst MEMBERS_GUIDE_ID = '@sparkelf/dsh-group-chat/members-guide'\nconst MEMBERS_GUIDE_ORDER = 60\n/** Client services required for these contributions. */\n/**\n * The Workspace navigation service, declared structurally.\n *\n * `@deepseek-ai/dsh-client-ui-workspace` is not a dependency of this package and declaring it as\n * one would tie the plugin to the plugin that happens to own the service today. Only the one\n * method used here is named, which is also what keeps this honest: the room opens the way a\n * session row opens, and nothing else about that plugin is assumed.\n */\ninterface WorkspaceNavigation {\n /**\n * Select a Session and show its Conversation.\n * @param sessionId - the room's Session, which `roomSessionId` derives from the room.\n */\n openSession(sessionId: string): void\n}\n\n/**\n * The slice of the Workspace controller this package needs.\n *\n * A dissolved room's Session outlives the room: it is a durable record, so the shell keeps\n * listing it under whatever grouping it landed in. Archiving is the documented way to take a\n * Session off those surfaces without destroying it, and it is exactly what the shell's own\n * \"archive session\" action does.\n */\ninterface WorkspaceArchive {\n archiveSession(sessionId: string, options?: { readonly stopActivity?: boolean }): Promise<void>\n /** Remove a Workspace registration; its Sessions and files are kept. */\n delete(workspaceId: string): Promise<void>\n /** Host-authoritative Workspace rows, including each one's owning Session ids. */\n readonly list: { getSnapshot(): WorkspaceListSnapshot | undefined }\n}\n\n/** The fields of the Workspace snapshot this package reads. */\ninterface WorkspaceListSnapshot {\n readonly phase?: string\n readonly items: readonly {\n readonly workspaceId: string\n readonly path: string\n readonly sessionIds: readonly string[]\n }[]\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n /** Workspace navigation, contributed by the workspace picker plugin. */\n readonly uiWorkspace: WorkspaceNavigation\n /** Workspace state and commands, contributed by the workspace controller plugin. */\n readonly workspaces: WorkspaceArchive\n }\n}\n\nexport const inject = ['slots', 'locale', 'uiWorkspace', 'workspaces', 'sidebarRightTabs', 'sidebarRight']\n\n/**\n * Register the sidebar region and the room page.\n * @param ctx - DSH client context with locale and slot registries.\n */\nexport function apply(ctx: ClientContext): void {\n const context = ctx as unknown as {\n sidebarRight?: { openTab(kind: string, options: { params: { url: string } }): void }\n get?: (name: string) => unknown\n }\n const sidebarRight = (context.sidebarRight ?? context.get?.('sidebarRight')) as\n { openResource?(address: string): void } | undefined\n const openRoomFile = (attachment: { attachmentId: string; name: string; bytes: number }, roomId: string): void => {\n const query = 'attachmentId=' + encodeURIComponent(attachment.attachmentId)\n + '&name=' + encodeURIComponent(attachment.name)\n + '&bytes=' + encodeURIComponent(String(attachment.bytes))\n void (async () => {\n const materialized = await fetch('/dsh-group-chat/api/attachment-materialize?' + query)\n if (!materialized.ok) throw new Error('附件预览准备失败')\n const target = await materialized.json() as { path?: string }\n if (typeof target.path !== 'string') throw new Error('附件预览路径缺失')\n const sessionId = 'group-room-' + roomId\n const address = 'dsh-resource://file/session/' + encodeURIComponent(sessionId)\n + '/' + target.path.split('/').map((part) => encodeURIComponent(part)).join('/')\n if (sidebarRight !== undefined && sidebarRight.openResource !== undefined) {\n sidebarRight.openResource(address)\n return\n }\n const fallback = new URL('/dsh-group-chat/api/attachment-preview?' + query, window.location.origin).toString()\n window.open(fallback, '_blank', 'noopener,noreferrer')\n })().catch((error) => {\n console.error('[group-chat] file preview failed', error)\n })\n }\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'group-chat: copy')\n const t = ctx.locale.bind(NS) as (key: GroupChatKey) => string\n // A second binding rather than a merged dictionary: the preset copy belongs to the platform's\n // `settings.agentPreset` namespace, and a preset name this plugin translated itself would\n // drift from the hero chip that shows the same preset under the same id.\n const tPreset = ctx.locale.bind(PRESET_LOCALE_NS) as (key: string) => string\n\n // One store shared by both surfaces, created here because the sidebar must show the\n // same list the page is operating on.\n const base = createRoomStore()\n // One store for the pending reply, shared by the message that starts it and the composer that\n // shows it. Created here because both surfaces are registered from this entry.\n const quotes = createQuoteStore()\n /**\n * Drops the pending reply once the composer submits.\n *\n * The composer belongs to the official chat surface, so there is no submit callback to hook.\n * What is stable is how a submission looks: Enter inside the editor, or a click on the send\n * button. Both are observed at the document in the capture phase — before the official handler\n * runs — and this side's pending reply is dropped for the open room. Only the local copy goes:\n * the host still needs its own to attach the quote to the message being published, and deleting\n * it here would send the reply bare.\n */\n const dropQuoteOnSubmit = (): (() => void) => {\n const clearActive = (): void => {\n const roomId = store.getState().activeRoomId\n if (roomId !== undefined) quotes.clearLocal(roomId)\n }\n const onSubmitKey = (event: KeyboardEvent): void => {\n if (event.key !== 'Enter' || event.shiftKey || event.isComposing) return\n const target = event.target\n if (target instanceof Element && target.closest('[contenteditable=\"true\"]') !== null) clearActive()\n }\n const onSubmitClick = (event: MouseEvent): void => {\n const target = event.target\n if (!(target instanceof Element)) return\n const button = target.closest('button[aria-label]')\n const label = button?.getAttribute('aria-label') ?? ''\n // Matches the shipped labels without pinning one language: both the zh and en strings\n // describe sending a message.\n if (/发送|send/i.test(label)) clearActive()\n }\n document.addEventListener('keydown', onSubmitKey, true)\n document.addEventListener('click', onSubmitClick, true)\n return () => {\n document.removeEventListener('keydown', onSubmitKey, true)\n document.removeEventListener('click', onSubmitClick, true)\n }\n }\n ctx.effect(dropQuoteOnSubmit)\n // The one action that means \"show me this room\" is opening its Session, so every caller —\n // the row today, a keyboard shortcut or a deep link later — goes through it and no caller has\n // to remember a second step.\n const store: RoomStore = {\n ...base,\n open: (roomId) => {\n base.open(roomId)\n // Wait for the host to have the room's Session before asking the shell to open it.\n //\n // The host creates room Sessions on its own startup poll; a member who clicks a room in\n // the first seconds after the plugin comes up otherwise retains an id that does not exist\n // yet. That failure is quiet — the history still draws — but nothing written afterwards\n // lands, because the composer is bound to a session scope with no session behind it.\n void (async () => {\n for (let attempt = 0; attempt < 25; attempt += 1) {\n try {\n if ((await readRoomState(roomId)).open) break\n } catch {\n // A read that fails says nothing about readiness; the open below is still the best\n // thing to try, and the shell reports its own failure if the session is missing.\n break\n }\n await new Promise((resolve) => { setTimeout(resolve, 300) })\n }\n ctx.uiWorkspace.openSession(roomSessionId(roomId))\n })()\n // Opening a room is the operator saying \"show me this conversation\", and the roster is the\n // other half of it: who is in here, and what the room has announced. The tab is revealed\n // rather than forced — `openTab` focuses an already-placed tab of this type instead of\n // stacking a second one, so re-entering a room keeps whatever the operator had open beside\n // it, and a reader who closed the roster stays closed because nothing re-opens it later.\n // Reached through the store's own action so the timing rule lives in one place: the room has\n // to be the mounted Session before a right-sidebar tab can be placed beside it.\n queueMicrotask(() => { store.openMembers?.(roomId) })\n },\n /**\n * Dissolve a room, then take its Session off the shell's grouping surfaces.\n *\n * The room row disappears because the server deleted it, but the Session is a durable\n * record that outlives the room: `dissolveRoom` cannot reach it, so without this the\n * room's leftover workspace (from before rooms stopped registering one) and its Session\n * entry would stay listed for a conversation that no longer exists. Archiving is the\n * documented, reversible way to remove a Session from those surfaces — the same action\n * the shell's own \"archive session\" uses.\n *\n * Runs after the delete so a refused dissolve leaves the Session alone.\n */\n dissolve: async (roomId) => {\n await base.dissolve(roomId)\n try {\n await ctx.workspaces.archiveSession(roomSessionId(roomId), { stopActivity: true })\n // A room opened before rooms stopped registering one still owns a Workspace entry,\n // and dissolving it has to take that entry with it. Matched by the Session it owns\n // rather than by path: the path is the room's directory, and a room may have been\n // pointed at a directory a real workspace also uses — deleting by path would remove\n // that one.\n const sessionId = roomSessionId(roomId)\n const snapshot = ctx.workspaces.list.getSnapshot()\n for (const workspace of snapshot?.items ?? []) {\n if (!workspace.sessionIds.includes(sessionId)) continue\n await ctx.workspaces.delete(workspace.workspaceId)\n }\n } catch {\n // The room is gone either way; a Session that could not be archived is a leftover\n // row, not a failed dissolve. Saying so would make the delete look unsuccessful.\n }\n },\n /**\n * Open the members panel for a room.\n *\n * Two steps, in this order and not the other way round: the room has to be the *mounted*\n * Session before a right-sidebar tab can be placed beside it, because `openTab` acts on the\n * mounted seat and throws when there is none. `openSession` is synchronous in what it records\n * but the seat re-binds on the next frame, so the tab is opened on a zero-delay task rather\n * than inline — inline would aim at the session the operator is leaving.\n */\n openMembers: (roomId) => {\n // `sidebarRight` is injected, so it is live for this whole turn. Reading it through\n // `ctx.get` was the one-shot lookup that cannot see a service another plugin provides.\n const open = (): void => {\n // A page type that is not `multiple` is recorded under one address, and the official\n // service reveals the tab already at that address instead of stacking a second copy — so\n // re-entering a room focuses the page the reader already had, with no probing here.\n ctx.sidebarRight.openTab(MEMBERS_TAB_KIND, { params: { roomId } })\n }\n if (base.getState().activeRoomId === roomId) open()\n else setTimeout(open, 0)\n },\n }\n\n ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({\n name: 'sidebar.footer.action',\n id: PANEL_ID,\n order: 20,\n inject: (): SidebarRoomsInjected => ({ t, tPreset, store }),\n }, SidebarRooms))\n\n /**\n * The members panel, as a tab in the official right sidebar.\n *\n * Three registrations, because the sidebar keeps three separate facts about a tab: the type it\n * belongs to (so navigation can resolve and persist it), the body to draw, and the chip that\n * labels it. `id` identifies the type's slot entries; `kind` is what `openTab` takes. They are\n * not the same value — the official schedule plugin uses\n * `\"@deepseek-ai/dsh-client-ui-schedule/task\"` and `\"scheduleTask\"` respectively — and\n * collapsing them would make `openTab` throw where nobody can see it.\n *\n * The panel is registered for every profile and decides at render time whether the Session it\n * was opened beside is a room. Registering conditionally instead would lose a placed tab on the\n * next reload: a tab whose type is gone cannot be restored.\n */\n // Registered through `ctx.inject`, not a one-shot `ctx.get`: the official right sidebar is a\n // separate plugin, so this one may load before the registry it contributes to exists. `inject`\n // runs the body when the service is present and re-runs it if the provider is replaced, which\n // is also what keeps the panel alive across an HMR of either side. A profile without the right\n // sidebar simply never runs this body, so the room list still comes up.\n //\n // The guide box is registered in the same body. That is deliberate: both entries are faces of\n // one page type, and a type that appears on the start page must be openable from it.\n /**\n * Open this plugin's page type from the start guide.\n *\n * The guide's shipped box opens through the guide's own `openTab`; a replacement box is not\n * handed that callback, so it goes through the same service the plugin already holds.\n */\n const openTabFromGuide = (kind: string): void => {\n // The shipped box opens through the guide's own `tab.actions.openTab`, which a replacement\n // box is not handed. This reaches the same service directly, so a click behaves exactly like\n // one on a shipped box: the type opens beside the guide the reader is looking at.\n // A click that cannot reach the sidebar is a wiring fault, not a reader's mistake: surface it\n // rather than leaving a dead box behind.\n try {\n ctx.sidebarRight.openTab(kind)\n } catch (error) {\n console.error('[group-chat] opening the members page failed', error)\n }\n }\n\n // The page type, its body, its title, and its start-guide box. `sidebarRightTabs` and\n // `sidebarRight` are injected (see the module's `inject`), so by the time `apply` runs they\n // are live: the official right sidebar always loads before the types that contribute to it.\n // A profile without that sidebar does not load this plugin at all, which is the intended\n // outcome — there would be nowhere to put the page.\n ctx.effect(() => ctx.sidebarRightTabs.register({\n id: MEMBERS_TAB_ID,\n kind: MEMBERS_TAB_KIND,\n // `extension` is the band for a type from outside the product; the default already means\n // exactly that, and naming it keeps the choice visible next to `builtin` types.\n priority: 'extension',\n title: () => t('membersTitle'),\n // The guide entry is what puts this type on the right sidebar's start page, beside Files,\n // Database and the rest. Omitting `guide` is the documented way to stay off that page.\n guide: [{\n id: MEMBERS_GUIDE_ID,\n order: MEMBERS_GUIDE_ORDER,\n title: () => t('membersTitle'),\n description: () => t('membersGuideHint'),\n }],\n }), 'group-chat: members tab type')\n\n ctx.effect(() => ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({\n name: 'sidebar.right.pane.tab',\n key: MEMBERS_TAB_ID,\n locale: NS,\n // The seat hands the inject factory the Session id and its bound actions, not a props\n // object; the panel needs the id to know which room it belongs to.\n inject: ((sessionId: string): MembersPanelInjected => ({ sessionId, t, store })) as never,\n }, MembersPanel)), 'group-chat: members tab body')\n\n ctx.effect(() => ctx.slots.inject('sidebar.right.pane.tab.title', () => ctx.slots.register({\n name: 'sidebar.right.pane.tab.title',\n key: MEMBERS_TAB_ID,\n locale: NS,\n inject: () => ({ t }),\n }, MembersTabTitle)), 'group-chat: members tab title')\n\n // Replace this type's own box on the start guide. The seat is keyed by the contributing\n // type's id, and the replacement decides per Session whether the box is drawn: the guide's\n // own list is global, so this is the only place the decision can be made.\n ctx.effect(() => ctx.slots.inject('sidebar.right.tab.guide.entry', () => ctx.slots.register({\n name: 'sidebar.right.tab.guide.entry',\n key: MEMBERS_TAB_ID,\n locale: NS,\n // The seat calls an inject factory with the Session id; the box's own copy arrives as owner\n // props at render time, so this returns only what the factory itself must supply.\n inject: ((sessionId: string): Partial<MembersGuideEntryProps> => ({\n sessionId,\n onPick: () => { openTabFromGuide(MEMBERS_TAB_KIND) },\n })) as never,\n }, MembersGuideEntry)), 'group-chat: members guide box')\n\n // A file card lives inside the official Chat renderer, including occurrences that are\n // emitted before the room wrapper claims a cell. Delegate from the document so every file\n // card in a room opens the same right-Sidebar preview, regardless of which cell drew it.\n ctx.effect(() => {\n const onFileClick = (event: MouseEvent): void => {\n const target = event.target\n if (!(target instanceof Element)) return\n const card = target.closest('[data-message-attachments] > *')\n const roomId = store.getState().activeRoomId\n if (!(card instanceof HTMLElement) || roomId === undefined) return\n const name = (card.getAttribute('title') ?? card.innerText.split('\\n')[0] ?? '').trim()\n if (name === '') return\n void (async () => {\n try {\n let cursor: string | null = null\n let found: { attachmentId: string; name: string; bytes: number } | undefined\n for (let page = 0; page < 20 && found === undefined; page += 1) {\n const suffix = cursor === null ? '' : '?cursor=' + encodeURIComponent(cursor)\n const response = await fetch('/dsh-group-chat/api/rooms/' + encodeURIComponent(roomId) + '/events' + suffix)\n if (!response.ok) return\n const payload = await response.json() as { events?: Array<{ event?: Record<string, unknown> }>; nextCursor?: string | null; hasMore?: boolean }\n for (const record of payload.events ?? []) {\n const data = record.event?.data as Record<string, unknown> | undefined\n const content = data?.content\n if (!Array.isArray(content)) continue\n for (const block of content) {\n if (block === null || typeof block !== 'object') continue\n const item = block as Record<string, unknown>\n const attachment = item.attachment as Record<string, unknown> | undefined\n if (item.type === 'file' && attachment?.name === name\n && typeof attachment.attachmentId === 'string' && typeof attachment.bytes === 'number') {\n found = { attachmentId: attachment.attachmentId, name, bytes: attachment.bytes }\n break\n }\n }\n if (found !== undefined) break\n }\n if (payload.hasMore !== true || payload.nextCursor === null || payload.nextCursor === undefined) break\n cursor = payload.nextCursor\n }\n if (found !== undefined) openRoomFile(found, roomId)\n } catch (error) {\n console.error('[group-chat] file preview lookup failed', error)\n }\n })()\n }\n document.addEventListener('click', onFileClick, true)\n return () => { document.removeEventListener('click', onFileClick, true) }\n }, 'group-chat: file preview delegation')\n\n // A room is a Session. Its messages are ordinary `user/message` events on that Session's log,\n // so the shell's own Conversation already renders them — bubbles, markdown, tool calls,\n // streaming, scrollback. Opening the room is therefore one navigation action, not a panel to\n // draw: `openSession` is the same call the workspace list makes for its own rows.\n //\n // Anything the plugin drew here instead would be a second renderer that has to keep pace with\n // the first, and would silently drop every feature the Conversation gains.\n //\n // One thing the official renderer cannot know is which side a message belongs on: in an\n // ordinary Session the only author is the operator, so its user row is unconditionally\n // right-aligned. A room has several authors. Shadowing the `user`-shaped cells at a\n // negative priority is how those cells are reached — the renderer picks the lowest live\n // entry, and the official one sits at `0`. The shadowing entries render the official\n // component read back out of this same registry, so the bubble itself is not\n // reimplemented; only its placement is decided here.\n //\n // This waits for the official entries rather than reading the ledger once. `slots.inject`\n // re-runs on a *declaration* change, and the Chat node slot is declared by the shell's\n // root before any of its renderers register — so at this point the ledger can hold a\n // different cell's entry and nothing else. Reading it once would find no official `user`\n // view, and registering nothing is invisible: the cells would simply keep their official\n // alignment, with no error anywhere to say why. Subscribing to the slot is what makes the\n // claim happen whenever the official renderers actually arrive.\n ctx.slots.inject('conversation.chat.node', () => {\n let claims: (() => void)[] = []\n const release = (): void => {\n for (const dispose of claims) dispose()\n claims = []\n }\n const reconcile = (): void => {\n const ledger = ctx.slots.entries('conversation.chat.node')\n // Cells already claimed are left alone: re-registering at the same priority throws, and\n // the throw would take the whole contribution down. Occupancy is read from key and\n // priority rather than from component identity, because the client bundle can be\n // evaluated more than once in a page (a reload, or the HMR receiver re-running the\n // factory): each evaluation gets its own `OUR_VIEW` object, so an identity check misses\n // the previous evaluation's entry and the re-registration throws. `SHADOW_PRIORITY` is\n // ours alone — the official entries sit at the default `0` — so a `-1` occupant of this\n // cell is always a previous evaluation of this plugin, never something to displace.\n const claimed = new Set(\n ledger\n .filter((entry) => (entry.options.priority ?? 0) === SHADOW_PRIORITY)\n .map((entry) => entry.options.key),\n )\n for (const key of CHAT_NODE_KEYS) {\n if (claimed.has(key)) continue\n const official = officialViewFor(ledger, key, OUR_VIEW)\n // Without the official view there is nothing to place: registering a wrapper that\n // renders nothing would blank every message of that cell in every Session, which is\n // far worse than leaving the bubble on its official (right) side. The subscription is\n // what gives this another chance once the renderer does arrive.\n if (official === undefined) continue\n // Wrapped because a lost race must not take the plugin down with it. The guard above\n // covers the ordinary case, but the slot core can also reject a registration this\n // plugin cannot observe first (another evaluation mid-flight). Throwing here would\n // abort the whole `inject` callback — losing the room list's own renderer wiring — so a\n // rejected claim is skipped, leaving that cell with the official placement.\n let dispose: (() => void) | undefined\n try {\n dispose = ctx.slots.register({\n name: 'conversation.chat.node',\n key,\n priority: SHADOW_PRIORITY,\n // Required, not cosmetic: the seat synthesizes the `t` prop from the *registering*\n // entry's namespace, so this entry must name the official Chat namespace. Declaring\n // our own here would leave the official bubble's copy looking up keys that namespace\n // does not hold. `chat` is already registered by the shell, so naming it adds no\n // dictionary and cannot collide with the shell's own registration.\n locale: 'chat',\n }, (props: Record<string, unknown>) =>\n MessageSide({\n ...props,\n Official: official,\n // Read as a snapshot rather than captured: identity arrives after the first paint,\n // and a value captured when this renderer was registered would stay `undefined` for\n // the life of the page, leaving every message on the unknown-author side forever.\n getSelfId: store.selfId,\n subscribeSelf: store.subscribe,\n // Reads through the live store rather than a captured value: the roster arrives after\n // the first paint, and a snapshot taken here would stay `undefined` for the life of\n // the page, leaving every other author's bubble unlabelled.\n getRoster: (roomId: string) => store.getState().members[roomId],\n sided: props['node'] !== undefined,\n quotes,\n // Our own copy, kept apart from the seat's `t`: that one is bound to the official\n // `chat` namespace the bubble renders against.\n tOwn: t,\n }))\n } catch (error) {\n logClaimFailure(key, error)\n }\n if (dispose !== undefined) claims.push(dispose)\n }\n }\n // Registration mutates the ledger, so the notification that a claim triggers would\n // re-enter this. `reconcile` is idempotent through the `claimed` set, which makes the\n // re-entry a no-op rather than a loop.\n const unsubscribe = ctx.slots.subscribe('conversation.chat.node', reconcile)\n reconcile()\n return () => { unsubscribe(); release() }\n })\n\n // The pending-quote bar above the composer. `conversation.input.dock` is a list seat, so this\n // adds a row beside the official queue/todo docks instead of replacing one of them. The seat\n // is session-scoped and hands the factory a session id; a non-room session draws nothing.\n // Room messages join the official `@` menu. This is the only route to a real inline\n // reference: the pipeline owns chip insertion, so a plugin-provided pick is what makes a\n // quote look and behave like a session reference instead of a second, parallel widget.\n const inputTriggers = ctx.get('inputTriggers') as InputTriggersLike | undefined\n if (inputTriggers !== undefined) {\n ctx.effect(() => registerMessageSource(inputTriggers, store))\n }\n\n ctx.effect(() => ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({\n name: 'conversation.input.dock',\n id: QUOTE_DOCK_ID,\n locale: NS,\n inject: (() => ({\n t,\n quotes,\n store,\n })) as never,\n }, PendingQuoteBar)), 'group-chat: pending quote bar')\n\n void PANEL_ID\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["styles","useState","useRef","useCallback","styles","Input","useState","styles","Menu","useState","Modal","styles","Button","Input","Switch","useState","useRef","Modal","invite","styles","Button","Input","IconLoadingOutlineRegular","IconCheckOutlineRegular","styles","Tooltip","IconQueueOutlineRegular","useSyncExternalStore","useState","useRef","useCallback","IconSearchOutlineRegular","IconCloseFillRegular","IconProjectAddOutlineRegular","createPortal","Modal","Button","IconSlidersTwoOutlineRegular","IconClockOutlineRegular","IconChevronsUpDownOutlineRegular","styles","useSyncExternalStore","createElement","useMemo","styles","useState","useCallback","Button","useRef","Input","styles","useSyncExternalStore","styles","SOURCE_NAME","MAX_CANDIDATES"],"sources":["../src/client/api.ts","../src/client/rows.ts","../src/client/Avatar.tsx","../src/client/Picker.tsx","../src/client/PresetSelect.tsx","../src/client/store.ts","../src/client/CreateRoomDialog.tsx","../src/client/RoomDialogs.tsx","../src/client/SidebarRooms.tsx","../src/client/quote.ts","../src/client/MessageSide.tsx","../src/session-id.ts","../src/client/MembersPanel.tsx","../src/client/GuideEntry.tsx","../src/client/QuoteComposer.tsx","../src/client/messageReference.ts","../src/reference-ids.ts","../src/client/mentionSource.ts","../src/client/memberReference.ts","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["/**\n * Room-page transport.\n *\n * The browser talks only to the Host plugin's own route. The Host holds the\n * workspace credential and forwards to the server under the workspace identity, so\n * this module never sees a token and cannot disagree with the agent about who the\n * caller is.\n *\n * @module @sparkelf/dsh-group-chat/client/api\n */\n\n/** The Host route registered by this plugin's own Host half. */\nexport const GC_API = '/dsh-group-chat/api'\n\n/**\n * One event as the server stores it: the DSH envelope verbatim, with no\n * plugin-specific fields added.\n */\nexport interface RoomEvent {\n readonly id: string\n readonly event: {\n readonly type: string\n readonly seq?: number\n readonly time?: number\n readonly data?: Record<string, unknown>\n }\n}\n\nexport interface RoomSummary {\n readonly id: string\n readonly name: string\n readonly ownerUserId: string\n readonly invitePolicy: 'anyone' | 'admin'\n readonly discoverability: 'discoverable' | 'invite_only'\n /** The room's reply policy, which decides whether a message starts a turn. */\n readonly agentTrigger: 'mention' | 'always' | 'never'\n /** The room's agent working directory, or null for the workspace root. */\n readonly cwd: string | null\n /** The room's agent preset, or null for the default. */\n readonly agentPreset: string | null\n readonly memberCount: number\n readonly joined: boolean\n /**\n * Messages from others since this account last read the room.\n *\n * Absent on a deployment that predates the field: the sidebar then shows no badge rather\n * than claiming \"0 unread\", which would be indistinguishable from a room nobody wrote in.\n */\n readonly unreadCount?: number\n /** The subset of {@link unreadCount} that mentions this account. */\n readonly mentionCount?: number\n readonly createdAt: string\n readonly updatedAt: string\n /**\n * The room announcement, when this deployment stores one.\n *\n * `undefined` is a third state on purpose: it means the server answering this read has no\n * announcement feature at all (an older deployment, or one rolled back after the plugin\n * shipped), which the panel reports as unavailable. `null` is a feature that is present\n * and has nothing written in it — the distinction is what keeps an empty banner from\n * claiming the feature is broken.\n */\n readonly announcement?: string | null\n}\n\nexport interface RoomMember {\n readonly userId: string\n readonly displayName: string\n readonly owner: boolean\n readonly joinedAt: string\n /** The member's picture, when DataOps has one. Absent means \"draw initials instead\". */\n readonly avatarUrl?: string | null\n /**\n * The member's role, when the server reports one.\n *\n * Absent on a server that predates roles; `owner` above already carries the same fact, so\n * the panel derives the role from it rather than showing every member as ordinary.\n */\n readonly role?: 'owner' | 'member'\n}\n\n/**\n * One account that can still be added to a room.\n *\n * Separate from {@link RoomMember} because it describes a person who is *not* in the room:\n * a candidate carries the handle and picture the picker searches and draws, and no join\n * date or ownership, because neither exists before the invite is accepted.\n */\nexport interface InviteCandidate {\n readonly userId: string\n readonly displayName: string\n readonly username: string\n readonly avatarUrl?: string | null\n}\n\n/** Directory names the room picker can browse, relative to the browsing root. */\nexport interface DirectoryListing {\n readonly path: string\n readonly parent: string | null\n readonly entries: readonly { readonly name: string; readonly path: string }[]\n}\n\n/**\n * One agent preset the deployment offers.\n *\n * `id` is the registry's own identifier — the `config.id` a preset declares, not the filename\n * of the patch that declares it. The two happen to match for every shipped preset, which is\n * exactly why the distinction has to be carried by the type: a deployment that renames a file\n * must not silently offer a name the registry will reject at room creation.\n */\nexport interface PresetOption {\n readonly id: string\n /** Present only for a preset that names itself; a shipped preset's copy is a locale key. */\n readonly name?: string\n readonly description?: string\n readonly order?: number\n /** Whether this is the deployment's default preset. */\n readonly isDefault?: boolean\n /** Non-empty when the preset is declared but could not be activated. */\n readonly broken?: string\n}\n\n/** Preset roster and the directories the create dialog needs. */\nexport interface RoomOptions {\n readonly presets: readonly PresetOption[]\n}\n\nexport interface EventPage {\n readonly events: readonly RoomEvent[]\n readonly nextCursor: string | null\n readonly hasMore: boolean\n}\n\n/** A failure the page renders as copy rather than as an exception. */\nexport class RoomError extends Error {\n constructor(message: string, readonly status: number) {\n super(message)\n this.name = 'RoomError'\n }\n}\n\nconst call = async <T>(path: string, init?: RequestInit): Promise<T> => {\n let response: Response\n try {\n response = await fetch(`${GC_API}${path}`, {\n ...init,\n headers: {\n ...(init?.body === undefined ? {} : { 'content-type': 'application/json' }),\n ...init?.headers,\n },\n // Same-origin: the Host route is served from this page's own origin.\n credentials: 'same-origin',\n })\n } catch (cause) {\n throw new RoomError('unreachable', 0)\n }\n if (!response.ok) {\n // The Host relays the server's own status, so each failure keeps its cause instead of\n // collapsing into one generic sentence. 401 and 403 are distinct from 503: the plugin\n // did resolve a credential and the server refused it, which is what a logged-out or\n // expired DataOps session looks like — a reader who saw \"unreachable\" would go looking\n // at the network instead of signing in again.\n const status = response.status\n if (status === 503) throw new RoomError('credentialMissing', status)\n if (status === 401 || status === 403) throw new RoomError('credentialRejected', status)\n if (status === 404) throw new RoomError('notConfigured', status)\n // 409 is the server refusing a state change it will not make — the owner trying to leave\n // is the one this client can reach, and collapsing it into \"unreachable\" would send the\n // reader to inspect the network instead of transferring ownership.\n if (status === 409) throw new RoomError('ownerCannotLeave', status)\n throw new RoomError('unreachable', status)\n }\n if (response.status === 204) return undefined as T\n return await response.json() as T\n}\n\n/**\n * The account this workspace acts as.\n *\n * The message renderer needs it to tell the operator's own messages from another member's,\n * and the browser holds no credential of its own to ask DataOps directly — the Host resolves\n * it from the same identity that decides message routing, so the two cannot disagree.\n *\n * @returns the account id, or `undefined` when the Host cannot identify this workspace.\n */\nexport const readSelf = async (): Promise<{ id: string; administrator: boolean } | undefined> => {\n try {\n // The host answers `roleCodes`, not the raw DataOps role objects: the client half never\n // talks to DataOps itself, so the host is what carries the account's roles across.\n const me = await call<{ id?: unknown; roleCodes?: unknown }>('/me')\n if (typeof me?.id !== 'string' || me.id.length === 0) return undefined\n // The room server owns the authoritative check; this flag only decides whether to *offer*\n // dissolving a room the reader does not own. These two role names are the ones the\n // deployment grants `system:admin:manage` to by default, so offering the entry matches what\n // the server will accept.\n const administrator = Array.isArray(me.roleCodes)\n && me.roleCodes.some((code) => code === '管理员' || code === '超级管理员')\n return { id: me.id, administrator }\n } catch {\n // An unidentified workspace still shows its rooms; its own messages simply stay where\n // the official renderer puts them rather than being claimed as the operator's.\n return undefined\n }\n}\n\n/** Rooms this workspace has joined. */\nexport const listRooms = (): Promise<readonly RoomSummary[]> => call('/rooms')\n\n/**\n * Rooms this account could join, as opposed to the ones it is already in.\n *\n * Kept separate from {@link listRooms} because the two answer different questions: the sidebar\n * shows what you belong to, while this is consulted only when the operator is looking for\n * something to join. Merging them would make every list read ask for rooms the account has no\n * relationship with.\n *\n * @param query - optional narrowing keyword, applied by the server.\n * @returns matching rooms, each carrying `joined` so the caller can tell them apart.\n */\nexport const listDiscoverableRooms = (query?: string): Promise<readonly RoomSummary[]> => {\n const suffix = query === undefined || query.trim().length === 0\n ? ''\n : `?query=${encodeURIComponent(query.trim())}`\n return call(`/discover${suffix}`)\n}\n\n/**\n * Take a seat in a room the server allows this account to join.\n *\n * The decision to allow it is the server's: it re-reads discoverability, invite policy and\n * membership at the moment of the press, so a room offered a minute ago may still be refused.\n * That refusal arrives as a `RoomError` and is shown as-is rather than being pre-empted here.\n *\n * @param roomId - the room to join.\n * @returns the server's answer, including whether the account was already a member.\n */\nexport const joinRoom = (roomId: string): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/join`, { method: 'POST' })\n\nexport const listMembers = (roomId: string): Promise<readonly RoomMember[]> =>\n call(`/rooms/${encodeURIComponent(roomId)}/members`)\n\n/**\n * Accounts that match `query` and are not yet in the room.\n *\n * The search runs on the Host, which holds the credential: the browser has no DataOps token\n * of its own, so a client-side filter over a full directory is not an option here. An empty\n * query is valid and returns a browsable first page rather than nothing, which is what makes\n * the picker usable before the operator has typed anything.\n */\nexport const searchInviteCandidates = (\n roomId: string,\n query: string,\n): Promise<readonly InviteCandidate[]> => {\n const suffix = query.length === 0 ? '' : `?q=${encodeURIComponent(query)}`\n return call(`/rooms/${encodeURIComponent(roomId)}/invite-candidates${suffix}`)\n}\n\n/** Preset names this deployment offers. An empty list means the picker falls back to typing. */\nexport const readOptions = (): Promise<RoomOptions> => call('/options')\n\n/** Child directories of `path`, for the working-directory picker. */\nexport const listDirectories = (path?: string): Promise<DirectoryListing> => {\n const suffix = path === undefined || path.length === 0 ? '' : `?path=${encodeURIComponent(path)}`\n return call(`/directories${suffix}`)\n}\n\n/**\n * Whether the host has this room's session up.\n *\n * The host creates a room's session on its own poll, which starts when the plugin does. A\n * member who opens a room before that poll reaches it makes the shell retain an id the host\n * has not created yet — the open then fails with `sessions.retain: unknown session`, and the\n * room shows its history against a session scope nothing can write into.\n */\nexport interface RoomState {\n readonly roomId: string\n readonly open: boolean\n readonly memberCount: number\n}\n\nexport const readRoomState = (roomId: string): Promise<RoomState> =>\n call<RoomState>(`/rooms/${encodeURIComponent(roomId)}/state`)\n\nexport const listEvents = (roomId: string, cursor?: string): Promise<EventPage> => {\n const suffix = cursor === undefined ? '' : `?cursor=${encodeURIComponent(cursor)}`\n return call(`/rooms/${encodeURIComponent(roomId)}/events${suffix}`)\n}\n\n/**\n * Create a room.\n *\n * `cwd` and the preset are sent at creation because they decide the scope every member\n * will share; the server does not accept them afterwards.\n */\nexport const createRoom = (\n name: string,\n execution?: { cwd?: string; agentPreset?: string },\n): Promise<RoomSummary> =>\n call('/rooms', { method: 'POST', body: JSON.stringify({ name, ...execution }) })\n\n/**\n * Set or clear a room's announcement.\n *\n * Only the room owner may write it; the server decides that, so a refusal is surfaced as-is\n * rather than pre-empted here. An empty string clears the announcement — the server stores\n * that as no announcement, which is why the panel treats `''` and `null` alike when reading.\n *\n * @param roomId - the room whose announcement to write.\n * @param announcement - the new text, or an empty string to clear it.\n */\nexport const setAnnouncement = (roomId: string, announcement: string): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/announcement`, {\n method: 'POST',\n body: JSON.stringify({ announcement }),\n })\n\nexport const updateRoom = (\n roomId: string,\n patch: { name?: string; invitePolicy?: string; discoverability?: string; agentTrigger?: string },\n): Promise<RoomSummary> =>\n call(`/rooms/${encodeURIComponent(roomId)}/update`, { method: 'POST', body: JSON.stringify(patch) })\n\n/**\n * Hand ownership of a room to another member.\n *\n * The reason this exists at all is that a room's owner cannot leave it: leaving would strand the\n * room with nobody able to change its settings. Transferring is the way out, so the server owns\n * the rule (owner only, target must be a member) and this only carries the request.\n */\nexport const transferOwnership = (roomId: string, userId: string): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/transfer`, {\n method: 'POST',\n body: JSON.stringify({ userId }),\n })\n\nexport const inviteMembers = (roomId: string, userIds: readonly string[]): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/members`, {\n method: 'POST',\n body: JSON.stringify({ userIds }),\n })\n\nexport const leaveRoom = (roomId: string): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/members/me`, { method: 'DELETE' })\n\n/** One file in a room's shared folder. */\nexport interface SharedFile {\n readonly path: string\n readonly name: string\n readonly size: number\n readonly directory: boolean\n}\n\n/** The shared folder of one room. */\n/**\n * The group-chat UI preferences this workspace remembers.\n *\n * Read from and written to a file in this workspace's own volume, so they follow the user\n * across re-logins and browsers rather than living in one browser's storage.\n */\nexport interface GroupChatPreferences {\n /** Height of the sidebar's group-chat region, in pixels. */\n readonly sidebarHeightPx?: number\n}\n\n/** Reads the stored preferences. Never fails: an unreadable file reads as \"nothing saved\". */\nexport const readPreferences = async (): Promise<GroupChatPreferences> => {\n try {\n return await call<GroupChatPreferences>('/preferences')\n } catch {\n // The panel's height is not worth surfacing an error for; the default is a fine answer.\n return {}\n }\n}\n\n/** Stores the preferences and returns what the host settled on, clamped. */\nexport const savePreferences = (patch: GroupChatPreferences): Promise<GroupChatPreferences> =>\n call<GroupChatPreferences>('/preferences', {\n method: 'PATCH',\n body: JSON.stringify(patch),\n })\n\nexport interface RoomFiles {\n readonly path: string | null\n /** Where this listing is, relative to the room folder. Empty at the top. */\n readonly relativePath?: string\n /** One entry per level from the room folder down to here. */\n readonly crumbs?: readonly { readonly name: string; readonly path: string }[]\n readonly files: readonly SharedFile[]\n}\n\n/**\n * List a room's shared folder.\n *\n * An empty `path` means this deployment has no shared tree, which the panel renders as\n * \"not available\" rather than as an empty folder — the two read differently to a member.\n */\nexport const listRoomFiles = (roomId: string, path?: string): Promise<RoomFiles> =>\n call(`/rooms/${encodeURIComponent(roomId)}/files${\n path === undefined || path.length === 0 ? '' : `?path=${encodeURIComponent(path)}`\n }`)\n\n/** One shared file's text, as the preview shows it. */\nexport interface SharedFileText {\n readonly path: string\n readonly name: string\n readonly text: string\n readonly truncated: boolean\n readonly textLike: boolean\n /** Data URL when the file is an image the panel can draw inline. */\n readonly image?: string\n}\n\n/** Read one file out of a room's shared folder. */\nexport const getRoomFile = (roomId: string, path: string): Promise<SharedFileText> =>\n call(`/rooms/${encodeURIComponent(roomId)}/file-content?path=${encodeURIComponent(path)}`)\n\n/** Where an upload landed. */\nexport interface SharedFileWrite {\n readonly path: string\n readonly name: string\n readonly size: number\n}\n\n/**\n * Upload one file into a room's shared folder.\n *\n * Base64 in JSON rather than multipart: the plugin's request path reads one JSON envelope for\n * every operation, and a second body format would need its own parser, limits, and error\n * mapping for no gain.\n *\n * @param roomId - the room whose folder receives the file.\n * @param file - the browser's File object.\n * @returns the written file.\n */\nexport const uploadRoomFile = async (roomId: string, file: File): Promise<SharedFileWrite> => {\n const buffer = new Uint8Array(await file.arrayBuffer())\n // Chunked: `String.fromCharCode(...bytes)` overflows the argument limit on a file of a few\n // hundred kilobytes, and the spread is what makes the one-liner tempting.\n let binary = ''\n for (let index = 0; index < buffer.length; index += 1) {\n binary += String.fromCharCode(buffer[index] as number)\n }\n return call(`/rooms/${encodeURIComponent(roomId)}/files`, {\n method: 'POST',\n body: JSON.stringify({ name: file.name, data: btoa(binary) }),\n })\n}\n\n/**\n * Remove one member from a room.\n *\n * The reserved word `me` keeps its meaning: removal always names another account, and the\n * server rejects the owner as a target. Leaving therefore never goes through this call.\n */\nexport const removeMember = (roomId: string, userId: string): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/members/${encodeURIComponent(userId)}`, { method: 'DELETE' })\n\n/**\n * Dissolve a room outright.\n *\n * Distinct from {@link leaveRoom}: leaving removes one membership and the room keeps existing\n * for everyone else, while dissolving ends the room for every member. The server decides who\n * may do it (the owner, or an administrator), so a refusal surfaces as copy rather than being\n * pre-empted here.\n */\nexport const dissolveRoom = (roomId: string): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}`, { method: 'DELETE' })\n\n/**\n * Send one message.\n *\n * `rpcId` is the idempotency key: the server keys a retry on it, so a send that\n * succeeded but whose response was lost does not post twice.\n */\n/** One content block, as the room server stores it. */\nexport interface ContentBlock {\n readonly type: string\n readonly [key: string]: unknown\n}\n\n/**\n * Sends a message that carries more than text.\n *\n * The server's block type is open (`{ type: string; ... }`), so a quote rides through as its own\n * block instead of being flattened into the text — flattening would lose the message id the\n * renderer needs to draw the bar, and would make the quote look like something the author typed.\n */\n/** One message the room search matched, with enough context to open it. */\nexport interface MessageHit {\n readonly eventId: string\n readonly roomId: string\n readonly roomName: string\n readonly authorName: string | null\n readonly authorKind: 'human' | 'agent' | null\n readonly text: string\n readonly createdAt: string\n}\n\n/**\n * Searches message text across the rooms this account can read.\n *\n * The room server owns the visibility rule; this only passes the words. An empty query is not\n * sent at all — every message would match it.\n */\nexport const searchMessages = (query: string): Promise<MessageHit[]> => {\n const trimmed = query.trim()\n if (trimmed.length === 0) return Promise.resolve([])\n return call<MessageHit[]>(`/rooms/messages/search?query=${encodeURIComponent(trimmed)}`)\n}\n\n/**\n * Registers a pending reply with the host.\n *\n * The message itself is submitted by the official composer, which this plugin neither wraps nor\n * intercepts, so the quote travels beside it rather than inside it: the host attaches it to the\n * next message this member sends in the room and then forgets it.\n */\nexport const registerQuote = (\n roomId: string,\n quote: { readonly messageId: string; readonly preview: string; readonly authorName?: string },\n): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/quote`, {\n method: 'POST',\n body: JSON.stringify(quote),\n })\n\n/** Drops a pending reply that will not be sent. */\nexport const clearQuote = (roomId: string): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/quote`, { method: 'DELETE' })\n\nexport const sendBlocks = (\n roomId: string,\n content: readonly ContentBlock[],\n rpcId: string,\n mentions?: readonly string[],\n): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/messages`, {\n method: 'POST',\n body: JSON.stringify({\n content,\n rpcId,\n ...mentions === undefined || mentions.length === 0 ? {} : { mentions },\n }),\n })\n\nexport const sendMessage = (roomId: string, content: string, rpcId: string): Promise<unknown> =>\n call(`/rooms/${encodeURIComponent(roomId)}/messages`, {\n method: 'POST',\n body: JSON.stringify({ content, rpcId }),\n })\n\n/** Text carried by an event, ignoring any non-text block. */\nexport const eventText = (record: RoomEvent): string => {\n const content = (record.event.data as { content?: unknown } | undefined)?.content\n if (!Array.isArray(content)) return ''\n return content\n .filter((block): block is { type: string; text?: string } =>\n typeof block === 'object' && block !== null && (block as { type?: string }).type === 'text')\n .map((block) => block.text ?? '')\n .join('')\n}\n\n/** Who wrote an event, and whether an agent wrote it. */\nexport const eventAuthor = (record: RoomEvent): { name: string; isAgent: boolean; isSelf: boolean } => {\n const source = (record.event.data as { source?: Record<string, unknown> } | undefined)?.source ?? {}\n return {\n name: String(source.memberName ?? source.agentOwnerId ?? '—'),\n isAgent: source.authorKind === 'agent',\n isSelf: source.isSelf === true,\n }\n}\n","/**\n * The decisions the plugin's components make without a browser.\n *\n * They live outside their components on purpose. The components import the official\n * primitives, which bring CSS modules that this repository's test runner cannot transform,\n * so anything worth asserting has to be reachable without mounting a component. That\n * constraint turns out to match the risk: what an operator sees — which rows survive a\n * query, what a picture falls back to, who carries the owner tag, whether an empty\n * announcement area means \"write one\" or \"this deployment cannot store one\" — is exactly\n * what goes wrong quietly, and it is all decidable from plain values.\n *\n * @module @sparkelf/dsh-group-chat/client/rows\n */\n\nimport {\n RoomError, type InviteCandidate, type PresetOption, type RoomMember, type RoomSummary,\n} from './api.ts'\n\n/** One row a picker can draw. */\nexport interface PickerOption {\n readonly id: string\n readonly label: string\n /** What the operator searches by when the label is a display name. */\n readonly secondary?: string\n readonly avatarUrl?: string | null\n /** Initials and tint are keyed on this when it differs from the row's result id. */\n readonly avatarId?: string\n /**\n * Whether the row is shown but cannot be taken.\n *\n * Shown, not hidden: a row that is absent leaves the operator unable to tell a deployment\n * that never declared it from one that declared it unusable.\n */\n readonly disabled?: boolean\n}\n\n/**\n * The locale namespace the shipped presets publish their names in.\n *\n * Read as a foreign namespace rather than one this plugin registers: `standard` and the other\n * three are declared by the platform, and their copy is owned there. Reprinting that copy here\n * would freeze today's wording and drift from the hero chip the moment the platform changes it.\n */\nexport const PRESET_LOCALE_NS = 'settings.agentPreset'\n\n/** Locale keys the shipped presets use, keyed by the id the registry publishes. */\nconst BUILT_IN_PRESET_KEYS: Readonly<Record<string, { name: string; description: string }>> = {\n standard: { name: 'presetStandardName', description: 'presetStandardDescription' },\n ptc: { name: 'presetPtcName', description: 'presetPtcDescription' },\n minimal: { name: 'presetMinimalName', description: 'presetMinimalDescription' },\n cordis: { name: 'presetCordisName', description: 'presetCordisDescription' },\n}\n\n/**\n * A preset's display copy, following the platform's own rule.\n *\n * A shipped preset publishes no `name`, so its copy is a locale lookup; a preset that names\n * itself in YAML owns its own words and is never translated. The split matters because the two\n * arrive as the same `{ id, name? }` shape — treating a published name as a locale key would\n * render the key itself, and treating a shipped id as self-named would render `standard`.\n *\n * @param preset - one roster row.\n * @param t - lookup for the preset locale namespace; a missing key resolves to the key, so the\n * fallback below is what keeps a renamed platform key from reaching the operator.\n * @returns the name and optional description to draw.\n */\nexport const presetDisplay = (\n preset: PresetOption,\n t: (key: string) => string,\n): { readonly name: string; readonly description?: string } => {\n const keys = preset.name === undefined ? BUILT_IN_PRESET_KEYS[preset.id] : undefined\n if (keys === undefined) {\n return {\n name: preset.name ?? preset.id,\n ...preset.description === undefined ? {} : { description: preset.description },\n }\n }\n // An unresolved key comes back as itself; showing `presetStandardName` would be worse than\n // showing the id, so the id wins whenever the lookup did not actually resolve.\n const name = t(keys.name)\n const description = t(keys.description)\n return {\n name: name === keys.name ? preset.id : name,\n ...description === keys.description ? {} : { description },\n }\n}\n\n/**\n * Roster rows as picker rows, optional-broken preset included.\n *\n * A broken preset stays visible and disabled rather than disappearing: it is declared in the\n * deployment, so an operator who cannot see it has no way to learn why the room they are\n * about to create will not start. Selection is expressed through `primary`, which the caller\n * uses to keep the broken row out of reach.\n *\n * @param presets - roster rows in the order the Host reported them.\n * @param t - lookup for the preset locale namespace.\n * @returns picker rows, one per preset.\n */\nexport const presetOptions = (\n presets: readonly PresetOption[],\n t: (key: string) => string,\n): readonly PickerOption[] =>\n presets.map((preset) => {\n const { name, description } = presetDisplay(preset, t)\n return {\n id: preset.id,\n label: name,\n ...description === undefined ? {} : { secondary: description },\n ...preset.broken === undefined ? {} : { disabled: true },\n }\n })\n\n/**\n * Filter rows by what the operator typed.\n *\n * Matching happens here rather than only at the source because the source's answer arrives a\n * debounce behind the keystroke; re-filtering the rows already held narrows the list on the\n * same frame instead of one request later. `secondary` participates because a member's handle\n * is often what the operator knows them by.\n *\n * @param options - the rows currently held.\n * @param query - the raw input.\n * @returns the rows to draw, in their original order.\n */\nexport const filterOptions = (\n options: readonly PickerOption[],\n query: string,\n): readonly PickerOption[] => {\n const needle = query.trim().toLowerCase()\n if (needle.length === 0) return options\n return options.filter((option) =>\n option.label.toLowerCase().includes(needle)\n || (option.secondary ?? '').toLowerCase().includes(needle))\n}\n\n/**\n * Add or remove one person from the invite selection.\n *\n * The selection is kept apart from the search result rows because the two answer different\n * questions: the rows are \"what the current query found\", while the selection is \"who will be\n * invited\". A narrowed query legitimately hides a person already picked, and deriving the\n * selection from the visible rows would drop them the moment the operator typed another name.\n *\n * The row is stored whole, not by id, so a chip can still draw the name and picture after the\n * search that produced it has been replaced.\n *\n * @param chosen - the current selection, in pick order.\n * @param candidate - the row the operator pressed.\n * @returns the new selection; a second press on the same id removes it, and identity is stable\n * when the press changed nothing.\n */\nexport const toggleChosen = <T extends { readonly userId: string }>(\n chosen: readonly T[],\n candidate: T,\n): readonly T[] =>\n chosen.some((member) => member.userId === candidate.userId)\n ? chosen.filter((member) => member.userId !== candidate.userId)\n : [...chosen, candidate]\n\n/**\n * Narrow the invite candidates to what the operator typed.\n *\n * The same rule the server applies, applied again to the rows already held, for one reason:\n * the server's answer arrives a debounce behind the keystroke, and the list would otherwise\n * sit still while the operator types. Re-filtering locally makes the narrowing immediate; the\n * server request still happens, and its answer replaces these rows a moment later.\n *\n * Both the display name and the handle participate, because the handle is often what a\n * colleague is known by — the server's search matches the same pair.\n *\n * @param candidates - the rows currently held, from the last answer.\n * @param query - the raw input.\n * @returns the rows to draw, in their original order.\n */\nexport const filterCandidates = (\n candidates: readonly InviteCandidate[],\n query: string,\n): readonly InviteCandidate[] => {\n const needle = query.trim().toLowerCase()\n if (needle.length === 0) return candidates\n return candidates.filter((candidate) =>\n candidate.displayName.toLowerCase().includes(needle)\n || candidate.username.toLowerCase().includes(needle))\n}\n\n/**\n * Whether one candidate row is part of the selection.\n *\n * @param chosen - the current selection.\n * @param userId - the row's account id.\n * @returns whether the row draws as selected.\n */\nexport const isChosen = (\n chosen: readonly { readonly userId: string }[],\n userId: string,\n): boolean => chosen.some((member) => member.userId === userId)\n\n/**\n * Up to two initials from a display name.\n *\n * Names arriving here are DataOps display names, which are as often Chinese as Latin. A CJK\n * name has no spaces to split on, so the first two characters are taken as-is; a Latin name\n * takes the first letter of its first two words. Both paths return at most two glyphs, which\n * is what keeps the circle from overflowing at either size.\n *\n * @param name - the display name, possibly empty.\n * @returns one or two characters, or `'?'` when the name carries nothing to draw.\n */\nexport const initialsOf = (name: string): string => {\n const trimmed = name.trim()\n if (trimmed.length === 0) return '?'\n // CJK ranges: a leading character here means the name is written without word spaces.\n if (/^[\\u3400-\\u9fff\\uf900-\\ufaff]/u.test(trimmed)) return [...trimmed].slice(0, 2).join('')\n const words = trimmed.split(/\\s+/u).filter((word) => word.length > 0)\n if (words.length === 1) return [...words[0]!].slice(0, 2).join('').toUpperCase()\n return (words[0]![0]! + words[1]![0]!).toUpperCase()\n}\n\n\n/**\n * The rooms worth offering as joinable.\n *\n * Two filters, for two different failures. `joined` is belt-and-braces: the Host already strips\n * joined rooms from discovery, but this list arrives from a request the sidebar did not wait\n * for, so a room the operator joined a moment ago can still be in the response.\n *\n * The member-id check covers the other window: the discovery read and the sidebar's own list\n * come from separate requests, so a room can be in both for a moment. Without it the same room\n * would appear twice, and the copy under the discovery heading would offer a Join button that\n * can only fail.\n *\n * @param discoverable - what the discovery read returned.\n * @param memberIds - ids of the rooms the sidebar is already showing.\n * @returns the rooms to render under the discovery heading.\n */\nexport const joinableRooms = (\n discoverable: readonly RoomSummary[],\n memberIds: ReadonlySet<string>,\n): readonly RoomSummary[] =>\n discoverable.filter((room) => !room.joined && !memberIds.has(room.id))\n\n/**\n * The members panel's decisions, made away from the component.\n *\n * Same reason as the pickers above: the panel imports primitives whose CSS modules this\n * repository's test runner cannot transform, so everything worth asserting — who is pinned\n * to the top, who carries a role tag, what a search removes, which of the announcement's\n * three states a room is in — has to be decidable from plain values.\n */\n\n/** One row the members panel draws. */\nexport interface MemberRow {\n readonly userId: string\n readonly name: string\n readonly avatarUrl?: string | null\n /** Whether the row carries the owner tag; plain members are deliberately unmarked. */\n readonly owner: boolean\n /** Whether this row is the operator, marked so they can find themselves in a long roster. */\n readonly self: boolean\n}\n\n/**\n * Whether a member owns the room.\n *\n * `owner` is the field every server has ever sent, `role` is the one the announcement work\n * added. Preferring `role` and falling back to `owner` keeps the tag correct against either\n * server, and costs nothing: the two carry the same fact.\n *\n * @param member - one roster row.\n * @returns whether the row is the room's owner.\n */\nexport const memberIsOwner = (member: RoomMember): boolean =>\n member.role === undefined ? member.owner === true : member.role === 'owner'\n\n/**\n * Roster rows for the panel, owner pinned first.\n *\n * Only the owner is moved. Everyone else keeps the order the server sent, which is join\n * order — a roster re-sorted alphabetically would scramble the one fact a reader can\n * actually see in it (who has been here longest), and would do so differently for a CJK\n * name than for a Latin one.\n *\n * @param members - the roster as the store holds it.\n * @param selfId - this workspace's account id, `undefined` while identity is still unknown.\n * @returns rows to draw.\n */\nexport const memberRows = (\n members: readonly RoomMember[],\n selfId: string | undefined,\n): readonly MemberRow[] => {\n const rows = members.map((member): MemberRow => ({\n userId: member.userId,\n name: member.displayName.length > 0 ? member.displayName : member.userId,\n avatarUrl: member.avatarUrl,\n owner: memberIsOwner(member),\n self: selfId !== undefined && member.userId === selfId,\n }))\n // `Array.prototype.sort` is stable, so the non-owners keep the server's order.\n return rows.slice().sort((left, right) => Number(right.owner) - Number(left.owner))\n}\n\n/**\n * Narrow the roster to what the operator typed.\n *\n * The account id participates because it is what another member is often known by — the\n * invite picker searches the handle for the same reason.\n *\n * @param rows - the roster rows currently held.\n * @param query - the raw input.\n * @returns rows to draw, in their order.\n */\nexport const filterMemberRows = (\n rows: readonly MemberRow[],\n query: string,\n): readonly MemberRow[] => {\n const needle = query.trim().toLowerCase()\n if (needle.length === 0) return rows\n return rows.filter((row) =>\n row.name.toLowerCase().includes(needle) || row.userId.toLowerCase().includes(needle))\n}\n\n/**\n * What the announcement area has to draw, as one of exactly three states.\n *\n * The middle state matters: a feature that is present and empty must offer to add an\n * announcement, while a deployment that never shipped the field must not pretend the\n * operator can write one. `undefined` is the absence of the field, `null` or a blank string\n * is the empty one — so the distinction has to be made here, once, rather than by every\n * caller.\n */\nexport type AnnouncementView =\n | { readonly state: 'unavailable' }\n | { readonly state: 'empty' }\n | { readonly state: 'text'; readonly text: string }\n\n/**\n * Read one room's announcement into the state its area draws.\n *\n * @param announcement - the room summary's field.\n * @returns the state to render.\n */\nexport const announcementView = (announcement: string | null | undefined): AnnouncementView => {\n if (announcement === undefined) return { state: 'unavailable' }\n // `null` and a blank string are the same fact to the reader: the feature is here and\n // nothing is written in it. The server reads a cleared announcement back as `null`.\n const text = (announcement ?? '').trim()\n return text.length === 0 ? { state: 'empty' } : { state: 'text', text }\n}\n\n/**\n * Whether a failed write means this deployment has no announcement feature.\n *\n * A rolled-back deployment answers the route with 404 (no route) or 501/405 (a route that\n * refuses the method). Those are the only failures that should switch the area to\n * \"unavailable\": a 401 or a 5xx is this request's problem, and reporting it as a missing\n * feature would send the operator looking at the wrong thing.\n *\n * @param error - whatever the write threw.\n * @returns whether the feature should be regarded as absent.\n */\nexport const announcementMissing = (error: unknown): boolean =>\n error instanceof RoomError\n && (error.status === 404 || error.status === 405 || error.status === 501)\n\n/**\n * The announcement text a save should send.\n *\n * Trimming here rather than in the component keeps \"clearing is allowed\" a stated rule: the\n * server reads an empty string as \"remove the announcement\", and a field holding only spaces\n * must mean the same thing rather than storing invisible text.\n *\n * @param draft - what the editor holds.\n * @returns the body to send, empty when the announcement should be cleared.\n */\nexport const announcementDraft = (draft: string): string => draft.trim()\n","/**\n * A member's picture, with a stable stand-in when there is none.\n *\n * The official component set ships no avatar, so the two places a person is drawn — the\n * member roster and the invite picker — would otherwise each invent their own. Keeping one\n * component means the fallback, the size and the accessibility name cannot drift apart\n * between them.\n *\n * The initials are computed in `rows.ts`, which is what lets them be tested without mounting\n * this component.\n *\n * The fallback is deliberately not a silhouette: DataOps has pictures for only some accounts, so\n * most rows land on initials, and a generic icon repeated down a list reads as \"broken image\"\n * rather than as a person. The circle is the portal's own blue with white initials, so a member\n * looks the same here as beside their name on the DataOps pages.\n *\n * @module @sparkelf/dsh-group-chat/client/Avatar\n */\n\nimport type { ReactNode } from 'react'\nimport { initialsOf } from './rows.ts'\nimport styles from './Avatar.module.css'\n\n/** Sizes the callers use; the class is chosen here so the CSS owns the dimensions. */\nexport type AvatarSize = 'sm' | 'md'\n\nexport interface AvatarProps {\n /** The name to draw initials from and to describe the picture with. */\n readonly name: string\n /** The picture's URL, when DataOps has one. */\n readonly src?: string | null\n readonly size?: AvatarSize\n}\n\n/** One member's picture, or their initials when there is no picture to show. */\nexport const Avatar = (props: AvatarProps): ReactNode => {\n const { name, src, size = 'md' } = props\n const className = `${styles['avatar']} ${size === 'sm' ? styles['sm'] : styles['md']}`\n const fallback = (\n <span className={className} role=\"img\" aria-label={name}>\n {initialsOf(name)}\n </span>\n )\n if (typeof src !== 'string' || src.length === 0) return fallback\n // The initials are always rendered underneath and the picture is layered on top, so a\n // picture that fails after the list has painted — an expired remote URL is the common\n // case — uncovers the initials rather than leaving an empty circle.\n return (\n <span className={styles['stack']}>\n {fallback}\n <img\n className={`${styles['avatar']} ${size === 'sm' ? styles['sm'] : styles['md']} ${styles['over']}`}\n src={src}\n alt=\"\"\n aria-hidden=\"true\"\n onError={(event) => { event.currentTarget.style.display = 'none' }}\n />\n </span>\n )\n}\n","/**\n * A searchable list of choices, for the fields that used to be free text.\n *\n * Three of the room dialogs ask for values the deployment already knows — who can be\n * invited, which preset to run, which directory to work in — and each used to be an empty\n * box that only accepted the answer if it was spelled exactly right. A wrong userId failed\n * only after the invite was submitted, and a wrong directory silently became a room the\n * agent could not work in. This component is the shared answer so all three behave alike\n * rather than each growing its own behaviour.\n *\n * It is deliberately not a `<datalist>`: the options come from an async source, the row\n * needs a picture and a secondary line, and a datalist cannot group or report why it is\n * empty. It is also not built on the official primitives because none of them is a\n * combobox — the package ships Button, Input, Menu and friends, but no searchable select.\n *\n * @module @sparkelf/dsh-group-chat/client/Picker\n */\n\nimport { useCallback, useEffect, useRef, useState, type KeyboardEvent, type ReactNode } from 'react'\nimport { Input } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { Avatar } from './Avatar.tsx'\nimport { filterOptions, type PickerOption } from './rows.ts'\nimport styles from './Picker.module.css'\n\n/**\n * Sentinel id for the \"use what I typed\" row.\n *\n * A real id cannot collide with it because account ids, preset names and paths are all\n * printable, so a NUL is outside every id space this component is handed.\n */\nconst TYPED_ROW_ID = '\\u0000typed'\n\nexport interface PickerProps {\n readonly options: readonly PickerOption[]\n readonly placeholder: string\n /** Shown under the input when the source answered with nothing. */\n readonly emptyHint?: string\n /** Shown instead of the list while the source is in flight. */\n readonly loadingHint?: string\n readonly loading?: boolean\n /** Called on every keystroke; the caller debounces and owns the query. */\n readonly onQueryChange?: (query: string) => void\n /**\n * Lets the field's own text be committed as a value.\n *\n * Two fields this serves — the working directory and the preset — accept any string\n * server-side, so a picker that could only choose from a list would make valid values\n * unreachable. With this set, a non-empty query offers itself as the first row and Enter\n * commits the typed text instead of falling through to the dialog's submit.\n */\n readonly onTypedCommit?: (text: string) => void\n /** Copy for that first row, given the text it would commit. */\n readonly typedCommitLabel?: (text: string) => string\n /** Enter with no highlighted row, and the click on a row, both land here. */\n readonly onPick: (option: PickerOption) => void\n /** Cleared after each pick, so a picker can be used repeatedly without reselecting. */\n readonly value?: string\n readonly autoFocus?: boolean\n}\n\n/**\n * A text field with a filtered list beneath it.\n *\n * Keyboard support is the reason this is not a plain list of buttons: ↑/↓ move the highlight\n * without leaving the field, Enter takes the highlighted row, and Escape first closes the\n * list and only then reaches the dialog, so a stray Escape while searching does not discard\n * the whole dialog.\n */\nexport const Picker = (props: PickerProps): ReactNode => {\n const {\n options, placeholder, emptyHint, loadingHint, loading = false,\n onQueryChange, onTypedCommit, typedCommitLabel, onPick, value, autoFocus = false,\n } = props\n const [query, setQuery] = useState(value ?? '')\n // A picker that opens empty hides the very choices it exists to offer, so an auto-focused\n // field starts expanded: the operator is looking straight at the list they came for.\n const [open, setOpen] = useState(autoFocus)\n const [active, setActive] = useState(0)\n const boxRef = useRef<HTMLDivElement | null>(null)\n\n // The caller can clear the field (after a pick) without this component losing its own\n // typing state, which is what lets one picker invite several people in a row.\n useEffect(() => { setQuery(value ?? '') }, [value])\n\n // The typed value is offered as a row of its own, ahead of the filtered ones, so a value\n // the deployment does not list stays reachable by the same Enter key as a listed one.\n const typed: PickerOption[] = onTypedCommit !== undefined && query.trim().length > 0\n ? [{ id: TYPED_ROW_ID, label: typedCommitLabel?.(query.trim()) ?? query.trim() }]\n : []\n const visible = [...typed, ...filterOptions(options, query)]\n\n // A shrinking list must not keep a highlight that now points past its end.\n useEffect(() => { setActive(0) }, [query])\n\n const pick = useCallback((option: PickerOption | undefined): void => {\n if (option === undefined) return\n if (option.id === TYPED_ROW_ID) {\n onTypedCommit?.(query.trim())\n return\n }\n onPick(option)\n }, [onPick, onTypedCommit, query])\n\n // Clicking a row must not blur-close the list before the click lands, so the close is\n // driven by an outside pointerdown rather than by the field's blur.\n useEffect(() => {\n if (!open) return\n const onPointerDown = (event: PointerEvent): void => {\n if (boxRef.current !== null && !boxRef.current.contains(event.target as Node)) setOpen(false)\n }\n document.addEventListener('pointerdown', onPointerDown)\n return () => { document.removeEventListener('pointerdown', onPointerDown) }\n }, [open])\n\n const onKeyDown = (event: KeyboardEvent<HTMLInputElement>): void => {\n if (event.key === 'ArrowDown') {\n event.preventDefault()\n setOpen(true)\n setActive((current) => (visible.length === 0 ? 0 : (current + 1) % visible.length))\n return\n }\n if (event.key === 'ArrowUp') {\n event.preventDefault()\n setActive((current) => (visible.length === 0 ? 0 : (current - 1 + visible.length) % visible.length))\n return\n }\n if (event.key === 'Enter') {\n if (open && visible.length > 0) {\n // Enter belongs to the highlighted row while the list is open; the dialog's own\n // submit only runs when there is nothing to pick.\n event.preventDefault()\n pick(visible[active])\n }\n return\n }\n if (event.key === 'Escape' && open) {\n // Consumed here so Escape closes the list first and the dialog second.\n event.preventDefault()\n event.stopPropagation()\n setOpen(false)\n }\n }\n\n return (\n <div className={styles['box']} ref={boxRef}>\n <Input\n value={query}\n autoFocus={autoFocus}\n placeholder={placeholder}\n role=\"combobox\"\n aria-expanded={open}\n aria-autocomplete=\"list\"\n onFocus={() => { setOpen(true) }}\n onChange={(event) => {\n setQuery(event.target.value)\n setOpen(true)\n onQueryChange?.(event.target.value)\n }}\n onKeyDown={onKeyDown}\n />\n {open && (\n <div className={styles['list']} role=\"listbox\">\n {loading && <p className={styles['note']}>{loadingHint ?? ''}</p>}\n {!loading && visible.length === 0 && (\n <p className={styles['note']}>{emptyHint ?? ''}</p>\n )}\n {!loading && visible.map((option, index) => (\n <button\n key={option.id}\n type=\"button\"\n role=\"option\"\n aria-selected={index === active}\n className={index === active ? styles['rowActive'] : styles['row']}\n onMouseEnter={() => { setActive(index) }}\n onClick={() => { pick(option) }}\n >\n {option.avatarId !== undefined || option.avatarUrl !== undefined\n ? <Avatar name={option.label} src={option.avatarUrl} size=\"sm\" />\n : null}\n <span className={styles['label']}>{option.label}</span>\n {option.secondary !== undefined && option.secondary.length > 0 && (\n <span className={styles['secondary']}>{option.secondary}</span>\n )}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n}\n","/**\n * The agent-preset field, built on the platform's own menu.\n *\n * This is the same control the new-session screen shows beside the workspace picker: the\n * platform paints that one with `Menu` and draws each row as a name over its description.\n * Reproducing the choice as a text box — or as a differently-shaped list — would make the same\n * concept look like two things depending on where the operator met it, so the dialog borrows\n * the platform's materials instead:\n *\n * - `Menu` for the dropdown, so the keyboard walk, outside-click dismissal and the portaled\n * list behave exactly as they do on the hero chip.\n * - The platform's `settings.agentPreset` locale namespace for the shipped presets' names and\n * descriptions. That copy is owned by the platform; reprinting it here would freeze today's\n * wording and drift the moment the platform rewords it.\n *\n * What this deliberately does not do is import the platform's preset component: its props are\n * bound to the `conversation.hero.agentPreset` slot and carry a session plus a staged-selection\n * store this dialog has neither of. The roster comes from the Host instead, which reads the\n * same registry that slot renders.\n *\n * @module @sparkelf/dsh-group-chat/client/PresetSelect\n */\n\nimport { useState, type ReactNode } from 'react'\nimport { Menu, type MenuEntry } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { PresetOption } from './api.ts'\nimport { presetDisplay } from './rows.ts'\nimport styles from './PresetSelect.module.css'\n\nexport interface PresetSelectProps {\n /** Roster rows as the Host reported them, in its order. */\n readonly presets: readonly PresetOption[]\n /** The chosen preset id, or `''` for \"the deployment's default\". */\n readonly value: string\n /** Lookup for the platform's `settings.agentPreset` namespace. */\n readonly t: (key: string) => string\n /** Copy for the row that clears the choice back to the deployment default. */\n readonly defaultLabel: string\n readonly onChange: (id: string) => void\n}\n\n/**\n * Render the preset trigger and its menu.\n * @param props - roster, current choice, and the platform copy lookup.\n * @returns the anchored dropdown.\n */\nexport const PresetSelect = (props: PresetSelectProps): ReactNode => {\n const { presets, value, t, defaultLabel, onChange } = props\n const [open, setOpen] = useState(false)\n\n const chosen = presets.find((preset) => preset.id === value)\n const trigger = chosen === undefined ? defaultLabel : presetDisplay(chosen, t).name\n\n // A row's label is the name over its description, which the platform's menu styles as one\n // cell. `MenuEntry.label` is ReactNode, so the two lines travel together inside it rather\n // than through a second field the menu does not have.\n const entryOf = (preset: PresetOption): MenuEntry => {\n const { name, description } = presetDisplay(preset, t)\n return {\n id: preset.id,\n // A broken preset stays listed and disabled: hiding it would leave the operator unable to\n // tell a preset the deployment never declared from one it declared and could not activate.\n disabled: preset.broken !== undefined,\n label: (\n <span className={styles['row']}>\n <span className={styles['name']}>{name}</span>\n {description !== undefined && (\n <span className={styles['description']}>{description}</span>\n )}\n </span>\n ),\n }\n }\n // Shipped presets publish no name, so the split is the one the platform draws: the rows whose\n // copy it owns come first, and rows that name themselves in YAML follow.\n const items: MenuEntry[] = [\n ...presets.filter((preset) => preset.name === undefined).map(entryOf),\n ...presets.filter((preset) => preset.name !== undefined).map(entryOf),\n ]\n\n return (\n <Menu\n open={open}\n // Portaled: the dialog body scrolls and clips, and an in-place list would be cut off at\n // the field's edge once the roster grows past a couple of rows.\n portal\n side=\"bottom\"\n align=\"start\"\n items={items}\n selectedId={value.length === 0 ? undefined : value}\n onSelect={(id) => { onChange(id); setOpen(false) }}\n onClose={() => { setOpen(false) }}\n // The empty choice stays a menu row rather than a separate control: a room that omits the\n // preset inherits whatever the deployment defaults to, which is a different decision from\n // pinning today's default into the room's own record. Its id is the empty string the\n // dialog already uses for \"not set\", and no preset can shadow it — the registry rejects an\n // id that is empty.\n footer={[{ id: '', label: defaultLabel }]}\n anchor={(\n <button\n type=\"button\"\n className={styles['trigger']}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n onClick={() => { setOpen((current) => !current) }}\n >\n <span className={styles['triggerLabel']}>{trigger}</span>\n </button>\n )}\n />\n )\n}\n","/**\n * The room list both client surfaces read.\n *\n * It lives here rather than inside either component because the sidebar region and the\n * room page must never show different lists. It is a plain observable, not a framework\n * store: both consumers are components this plugin already owns.\n *\n * The slot caches the value its `inject` factory returns, so a snapshot taken at inject\n * time would freeze at the first read. Consumers therefore subscribe through\n * `useSyncExternalStore` and re-read on every change.\n *\n * @module @sparkelf/dsh-group-chat/client/store\n */\n\nimport {\n dissolveRoom, joinRoom, leaveRoom, removeMember, transferOwnership, listDiscoverableRooms, listMembers, listRooms, readSelf,\n type RoomMember, type RoomSummary, updateRoom,\n} from './api.ts'\nimport type { GroupChatKey } from './locales.ts'\n\n/** The three distinguishable failures the surfaces render as copy. */\nexport type RoomFailure = GroupChatKey | undefined\n\nexport interface RoomState {\n /** `undefined` until the first read completes, so the UI can tell loading from empty. */\n readonly rooms: readonly RoomSummary[] | undefined\n /** The room the main panel is showing, if any. */\n readonly activeRoomId: string | undefined\n readonly failure: RoomFailure\n /**\n * The sidebar's search box contents.\n *\n * Held here rather than in the region because the header collapses while a query is\n * present, and the region must survive that collapse with its text intact.\n */\n readonly searchQuery: string\n /**\n * This workspace's own account id, once the Host reports it.\n *\n * Held in the store because the message renderer needs it while rendering, and that\n * renderer is not a component that can wait on an effect: it decides a message's side\n * during the render in which the message appears. Undefined until the first read\n * completes, and the renderer treats \"unknown\" as \"not mine\" rather than guessing.\n */\n readonly selfId: string | undefined\n /**\n * Whether the deployment reports this reader as an administrator.\n *\n * Only used to decide whether to offer dissolving a room they do not own; the room server\n * decides on the request itself, so a stale `true` costs a refusal, not a wrong delete.\n */\n readonly administrator: boolean\n /**\n * Room members by room id, so a message can be drawn with its author's name and picture.\n *\n * A message event carries only the author's id. The name and the picture live on the roster,\n * which is a different request, so the renderer needs this lookup to avoid drawing a bare id\n * or initials derived from one.\n *\n * Kept per room rather than as one flat list because the same account can be a member of\n * several rooms and can leave some of them: a flat map would keep drawing a person in a room\n * they are no longer in.\n */\n readonly members: Readonly<Record<string, readonly RoomMember[]>>\n /**\n * Whether the create-room dialog is open.\n *\n * The sidebar's + button sets it and the page half renders the dialog: the sidebar seat\n * is a thin band, and a modal belongs to the surface that owns the window.\n */\n readonly pendingCreate: boolean\n /**\n * Rooms the operator could join, or `undefined` before the first discovery read.\n *\n * `undefined` rather than `[]` on purpose: an empty array means \"the search answered, and\n * nothing is joinable\", while `undefined` means \"nobody has asked yet\". Collapsing the two\n * would show an empty discovery section before the operator ever searched.\n */\n readonly discoverable: readonly RoomSummary[] | undefined\n /** The room dialog the row menu opened, if any. Rendered by the page half. */\n readonly dialog: RoomDialog | undefined\n}\n\n/**\n * A room dialog opened from a row's context menu.\n *\n * The menu lives in the sidebar and the dialogs live on the page, so the request travels\n * through the store rather than through component nesting.\n */\nexport type RoomDialog =\n | { readonly kind: 'invite'; readonly roomId: string }\n | { readonly kind: 'settings'; readonly roomId: string }\n | { readonly kind: 'members'; readonly roomId: string }\n /**\n * Hand the room over. Only reachable from the owner's own menu, because the action that\n * makes leaving possible for an owner is exactly this one.\n */\n | { readonly kind: 'transfer'; readonly roomId: string }\n\nexport interface RoomStore {\n getState: () => RoomState\n subscribe: (listener: () => void) => () => void\n /** Re-read the room list from the Host route. */\n refresh: () => Promise<void>\n /** Select a room; the main panel follows this. */\n open: (roomId: string) => void\n /** This workspace's own account id, or `undefined` while it is still unknown. */\n selfId: () => string | undefined\n administrator: () => boolean\n setFailure: (failure: RoomFailure) => void\n setSearch: (query: string) => void\n /**\n * Read the rooms this account could join.\n *\n * Separate from `refresh` because it is asked only when the operator is looking for\n * something to join: folding it into the sidebar's periodic read would put a second\n * cross-account query on every poll.\n *\n * @param query - optional narrowing keyword, applied by the server.\n */\n discover: (query?: string) => Promise<void>\n /**\n * Read one room's roster, so its messages can be drawn with their authors' names and pictures.\n *\n * Called when a room is opened rather than on every refresh: a roster changes rarely, and the\n * message stream does not require it to be current to be readable. A failed read leaves the\n * previous roster in place instead of clearing it, because an empty roster would redraw every\n * message in the room as an unknown author.\n *\n * @param roomId - the room whose roster to read.\n */\n loadMembers: (roomId: string) => Promise<void>\n /**\n * Join a room, then re-read both lists.\n *\n * The room moves from the discovery list to the sidebar in one step, so a successful join\n * cannot leave the same room visible in both places.\n *\n * @param roomId - the room to join.\n */\n join: (roomId: string) => Promise<void>\n /** Apply one policy to an existing room, then re-read so both surfaces agree. */\n setPolicy: (\n roomId: string,\n patch: { invitePolicy?: string; discoverability?: string; agentTrigger?: string },\n ) => Promise<void>\n /** Open the create-room dialog. */\n openCreate: () => void\n closeCreate: () => void\n /** Open one room dialog; `undefined` closes whatever is open. */\n setDialog: (dialog: RoomDialog | undefined) => void\n /**\n * Show the members panel for one room in the right sidebar.\n *\n * Supplied by the plugin's apply step rather than implemented here: opening a sidebar tab is a\n * shell concern, and this store owns room data only. Leaving it optional keeps the store\n * testable without a live sidebar and lets a profile without one keep the room list working.\n */\n openMembers: ((roomId: string) => void) | undefined\n /** Rename a room, then re-read so both surfaces show the new name. */\n rename: (roomId: string, name: string) => Promise<void>\n /** Leave a room. */\n leave: (roomId: string) => Promise<void>\n /**\n * Remove another member.\n *\n * Distinct from `leave`: this acts on someone else's membership, so the server restricts it\n * to the owner and refuses the owner as a target. Refreshing the list afterwards is what\n * updates every surface's roster.\n */\n removeMember: (roomId: string, userId: string) => Promise<void>\n /**\n * End a room for every member.\n *\n * Separate from `leave` on purpose: the two have different audiences and different\n * consequences, and a surface that offers one must not be mistaken for the other.\n */\n dissolve: (roomId: string) => Promise<void>\n /**\n * Hand ownership to another member.\n *\n * A room's owner cannot leave it, so this is the action that makes leaving possible for them.\n * The server decides both eligibility questions (is the caller the owner, is the target a\n * member), so the client only carries the choice.\n */\n transfer: (roomId: string, userId: string) => Promise<void>\n}\n\n/**\n * Create the shared store.\n * @returns a store whose `getState` returns a stable reference between changes, which is\n * what `useSyncExternalStore` requires to avoid a render loop.\n */\nexport function createRoomStore(): RoomStore {\n let state: RoomState = {\n rooms: undefined,\n activeRoomId: undefined,\n failure: undefined,\n searchQuery: '',\n selfId: undefined,\n administrator: false,\n members: {},\n pendingCreate: false,\n // Undefined, not empty: nobody has searched yet, which is a different fact from\n // \"searched and found nothing joinable\".\n discoverable: undefined,\n dialog: undefined,\n }\n const listeners = new Set<() => void>()\n\n const set = (patch: Partial<RoomState>): void => {\n // A fresh object per change, so `getState` is referentially stable between changes.\n state = { ...state, ...patch }\n for (const listener of listeners) listener()\n }\n\n /**\n * Read one room's roster into the store.\n *\n * A failure leaves the previous roster in place rather than clearing it: the room is still\n * readable without names (the renderer falls back to an unlabelled bubble), and a stale name is\n * still the right person far more often than no name at all.\n *\n * @param roomId - the room whose roster to read.\n */\n const loadMembersInto = async (roomId: string): Promise<void> => {\n try {\n const roster = await listMembers(roomId)\n set({ members: { ...state.members, [roomId]: roster } })\n } catch {\n // Deliberately silent: a roster that could not be read is not a room failure, and the\n // message list keeps working with what it already has.\n }\n }\n\n return {\n getState: () => state,\n subscribe: (listener) => {\n listeners.add(listener)\n return () => { listeners.delete(listener) }\n },\n refresh: async () => {\n // Resolved alongside the room list rather than in its own pass: identity is what the\n // message renderer needs, and a list that arrived while identity was still unknown\n // would draw the operator's own first messages on the wrong side.\n void readSelf().then((self) => {\n if (self !== undefined) set({ selfId: self.id, administrator: self.administrator })\n })\n try {\n const rooms = await listRooms()\n set({ rooms, failure: undefined })\n // Read every joined room's roster, not only the open one. A reload lands directly on a\n // room's Session without going through `open`, and a message must be attributable the\n // first time it paints — waiting for an interaction would leave the first screen of\n // every room unlabelled. Only rooms without a roster are read, so the periodic refresh\n // does not repeat this for data that rarely changes.\n for (const room of rooms) {\n if (!room.joined || state.members[room.id] !== undefined) continue\n void loadMembersInto(room.id)\n }\n } catch (error) {\n // The sidebar may load before the server is reachable; the page shows the\n // reason, and the next refresh clears it.\n set({ failure: failureOf(error) })\n }\n },\n open: (roomId) => {\n // Opening a room is the reader's acknowledgement: clear the mention/unread badge\n // immediately rather than waiting for the next room-list refresh, which is also what\n // makes the badge disappear the moment the conversation is on screen.\n set({\n activeRoomId: roomId,\n ...(state.rooms === undefined\n ? {}\n : { rooms: state.rooms.map(room => room.id === roomId ? { ...room, unreadCount: 0, mentionCount: 0 } : room) }),\n })\n // Read on open rather than on every side-bar refresh: the roster changes rarely, and the\n // side bar refreshes every few seconds — fetching it there would be a steady stream of\n // requests for data that almost never differs.\n void loadMembersInto(roomId)\n },\n discover: async (query) => {\n try {\n set({ discoverable: await listDiscoverableRooms(query) })\n } catch (error) {\n // An unreadable discovery list is not a broken sidebar: the joined rooms are still\n // there and still usable, so the failure is reported without clearing the list.\n set({ discoverable: [], failure: failureOf(error) })\n }\n },\n join: async (roomId) => {\n await joinRoom(roomId)\n // Re-read both lists: the room is now a member room, so leaving it in `discoverable`\n // would let the operator press Join again on a room they are already in.\n set({ rooms: await listRooms(), discoverable: await listDiscoverableRooms() })\n },\n selfId: () => state.selfId,\n administrator: () => state.administrator,\n setFailure: (failure) => set({ failure }),\n setSearch: (searchQuery) => set({ searchQuery }),\n loadMembers: loadMembersInto,\n setPolicy: async (roomId, patch) => {\n await updateRoom(roomId, patch)\n set({ rooms: await listRooms() })\n },\n openCreate: () => set({ pendingCreate: true }),\n closeCreate: () => set({ pendingCreate: false }),\n setDialog: (dialog) => set({ dialog }),\n openMembers: undefined,\n rename: async (roomId, name) => {\n await updateRoom(roomId, { name })\n set({ rooms: await listRooms() })\n },\n leave: async (roomId) => {\n await leaveRoom(roomId)\n // The room stays listed under the \"show left rooms\" filter, so the selection is\n // cleared by refreshing rather than by forgetting the room.\n set({ rooms: await listRooms(), activeRoomId: undefined })\n },\n removeMember: async (roomId, userId) => {\n await removeMember(roomId, userId)\n // The roster is the only thing that changed; the room list itself keeps the same\n // rooms, so a full re-read is enough and refreshes the member count too.\n set({ rooms: await listRooms() })\n },\n dissolve: async (roomId) => {\n await dissolveRoom(roomId)\n // Unlike leaving, the room is gone for everyone: there is no \"left rooms\" entry to\n // refresh into, so the selection is cleared and the list re-read without it.\n set({ rooms: await listRooms(), activeRoomId: undefined })\n },\n transfer: async (roomId, userId) => {\n await transferOwnership(roomId, userId)\n // Ownership is a field on the room, so the refreshed list is what updates everyone's\n // notion of who runs it — including this reader's, who is now an ordinary member.\n set({ rooms: await listRooms() })\n },\n }\n}\n\n/** Map a transport failure onto the copy key the surfaces render. */\nexport const failureOf = (error: unknown): GroupChatKey => {\n const message = error instanceof Error ? error.message : ''\n if (message === 'credentialMissing') return 'credentialMissing'\n if (message === 'credentialRejected') return 'credentialRejected'\n if (message === 'notConfigured') return 'notConfigured'\n if (message === 'ownerCannotLeave') return 'ownerCannotLeave'\n return 'unreachable'\n}\n","/**\n * The create-room dialog.\n *\n * Three fields are required because each one decides something the room cannot change\n * later — the name, the working directory, and the agent preset. `cwd` is the real\n * security boundary: every member of the room can ask the agent to modify anything under\n * it, so the field carries a visible warning rather than a hint.\n *\n * The three policy switches are the room's settings, offered at creation so a room is not\n * born with defaults the creator would have chosen differently. The notice at the foot is\n * required wording: these settings control where a room appears, not who may reach it.\n *\n * @module @sparkelf/dsh-group-chat/client/CreateRoomDialog\n */\n\nimport { useEffect, useState, type ReactNode } from 'react'\nimport { Button, Input, Modal, Switch } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { createRoom, listDirectories, readOptions, type PresetOption } from './api.ts'\nimport { Picker } from './Picker.tsx'\nimport { PresetSelect } from './PresetSelect.tsx'\nimport { type PickerOption } from './rows.ts'\nimport { failureOf, type RoomStore } from './store.ts'\nimport type { GroupChatKey } from './locales.ts'\nimport styles from './CreateRoomDialog.module.css'\n\n/** Props the page half passes. */\nexport interface CreateRoomDialogProps {\n readonly t: (key: GroupChatKey) => string\n /**\n * Lookup for the platform's own preset copy.\n *\n * Separate from `t` because it is a different namespace: `t` resolves this plugin's keys,\n * while this one resolves `settings.agentPreset`, which the platform registers. Keeping them\n * apart is what lets a shipped preset show the platform's wording without this plugin\n * reprinting it.\n */\n readonly tPreset: (key: string) => string\n readonly store: RoomStore\n}\n\n/** Invite policy values the backend accepts. */\ntype InvitePolicy = 'anyone' | 'admin'\n/** Discoverability values the backend accepts. */\ntype Discoverability = 'discoverable' | 'invite_only'\n/** Agent trigger values the backend accepts. */\ntype AgentTrigger = 'mention' | 'always' | 'never'\n\n/**\n * Render the create-room dialog.\n * @param props - the copy resolver and shared store whose `pendingCreate` opens this.\n * @returns the dialog, or null while closed.\n */\nexport function CreateRoomDialog(props: CreateRoomDialogProps) {\n const { t, tPreset, store } = props\n const open = store.getState().pendingCreate\n\n const [name, setName] = useState('')\n const [cwd, setCwd] = useState('')\n const [preset, setPreset] = useState('')\n const [invitePolicy, setInvitePolicy] = useState<InvitePolicy>('anyone')\n const [discoverability, setDiscoverability] = useState<Discoverability>('discoverable')\n const [agentTrigger, setAgentTrigger] = useState<AgentTrigger>('mention')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<GroupChatKey | undefined>(undefined)\n // Preset names the deployment offers. Empty means the picker could not be filled, and the\n // field then falls back to typing — the dialog must not become unusable because a listing\n // failed.\n const [presets, setPresets] = useState<readonly PresetOption[]>([])\n const [directories, setDirectories] = useState<readonly PickerOption[]>([])\n const [dirPath, setDirPath] = useState('')\n\n // Read once when the dialog opens rather than at module load: the deployment's presets and\n // its directories can both change while the page is up, and a failed read must be retried\n // by reopening instead of being cached for the life of the page.\n useEffect(() => {\n if (!open) return\n let cancelled = false\n void (async () => {\n try {\n const options = await readOptions()\n if (!cancelled) setPresets(options.presets)\n } catch {\n if (!cancelled) setPresets([])\n }\n })()\n return () => { cancelled = true }\n }, [open])\n\n // The directory list follows `cwd` only after a pick. Re-listing on every keystroke would\n // fight the operator: a half-typed path is usually an invalid one, and the list would flip\n // to empty between characters.\n useEffect(() => {\n if (!open) return\n let cancelled = false\n void (async () => {\n try {\n const listing = await listDirectories(dirPath.length === 0 ? undefined : dirPath)\n if (cancelled) return\n const entries = listing.entries.map((entry) => ({\n id: entry.path,\n label: entry.name,\n secondary: entry.path,\n }))\n // The current directory is offered as the first row: the common case is accepting the\n // root, and without it the list can only ever move deeper.\n setDirectories([\n { id: listing.path, label: listing.path, secondary: t('pickThisDirectory') },\n ...(listing.parent === null\n ? []\n : [{ id: listing.parent, label: '..', secondary: listing.parent }]),\n ...entries,\n ])\n } catch {\n if (!cancelled) setDirectories([])\n }\n })()\n return () => { cancelled = true }\n }, [open, dirPath, t])\n\n // A reopened dialog starts clean, so a cancelled attempt leaves nothing behind.\n useEffect(() => {\n if (!open) {\n setName(''); setBusy(false); setError(undefined)\n }\n }, [open])\n\n const close = (): void => store.closeCreate()\n\n const submit = async (): Promise<void> => {\n if (name.trim().length === 0 || busy) return\n setBusy(true)\n try {\n const room = await createRoom(name.trim(), {\n // Empty means \"use the server's default\", so it is omitted rather than sent blank.\n ...cwd.trim().length === 0 ? {} : { cwd: cwd.trim() },\n ...preset.trim().length === 0 ? {} : { agentPreset: preset.trim() },\n })\n // The policies travel as a follow-up update rather than create-time fields, so a\n // failure to apply them still leaves a usable room instead of none.\n await Promise.all([\n invitePolicy === 'anyone' ? Promise.resolve() : store.setPolicy(room.id, { invitePolicy }),\n discoverability === 'discoverable' ? Promise.resolve() : store.setPolicy(room.id, { discoverability }),\n agentTrigger === 'mention' ? Promise.resolve() : store.setPolicy(room.id, { agentTrigger }),\n ])\n await store.refresh()\n store.open(room.id)\n close()\n } catch (cause) {\n setError(failureOf(cause))\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <Modal\n open={open}\n onClose={close}\n title={t('createTitle')}\n closeLabel={t('cancel')}\n footer={(\n <div className={styles.footer}>\n <Button variant=\"ghost\" size=\"md\" onClick={close}>{t('cancel')}</Button>\n <Button\n variant=\"primary\"\n size=\"md\"\n disabled={busy || name.trim().length === 0}\n onClick={() => void submit()}\n >\n {t('createSubmit')}\n </Button>\n </div>\n )}\n >\n <div className={styles.body}>\n <label className={styles.field}>\n <span className={styles.label}>{t('createName')}</span>\n <Input\n value={name}\n autoFocus\n onChange={(event) => setName(event.target.value)}\n onKeyDown={(event) => { if (event.key === 'Enter') void submit() }}\n />\n </label>\n\n {/* Required and security-relevant: this field decides what the agent can touch on\n behalf of everyone in the room, so it is shown with its consequence. */}\n <div className={styles.field}>\n <span className={styles.label}>{t('createCwd')}</span>\n <Picker\n value={cwd}\n options={directories}\n placeholder=\"/workspace\"\n emptyHint={t('optionEmpty')}\n onQueryChange={(next) => { setCwd(next) }}\n onTypedCommit={(text) => { setCwd(text); setDirPath(text) }}\n typedCommitLabel={(text) => text}\n onPick={(option) => {\n // Browsing and choosing are the same gesture here: taking a directory both sets\n // the value and moves the list into it, so the field never shows a path whose\n // contents the operator cannot see.\n setCwd(option.id)\n setDirPath(option.id)\n }}\n />\n <span className={styles.warn}>{t('createCwdHint')}</span>\n </div>\n\n <div className={styles.field}>\n <span className={styles.label}>{t('createPreset')}</span>\n {presets.length === 0\n // The roster could not be read. Falling back to a text box would accept any string,\n // and a preset the registry does not hold fails only after the room is submitted —\n // so the field states the one thing it can promise instead: the deployment default.\n ? <span className={styles.hint}>{t('createPresetUnavailable')}</span>\n : (\n <PresetSelect\n presets={presets}\n value={preset}\n t={tPreset}\n defaultLabel={t('createPresetDefault')}\n onChange={setPreset}\n />\n )}\n <span className={styles.hint}>{t('createPresetHint')}</span>\n </div>\n\n <div className={styles.switches}>\n <Switch\n checked={invitePolicy === 'anyone'}\n onChange={(next) => setInvitePolicy(next ? 'anyone' : 'admin')}\n label={t('createPolicy')}\n />\n <Switch\n checked={discoverability === 'discoverable'}\n onChange={(next) => setDiscoverability(next ? 'discoverable' : 'invite_only')}\n label={t('createVisibility')}\n />\n <Switch\n checked={agentTrigger === 'mention'}\n onChange={(next) => setAgentTrigger(next ? 'mention' : 'always')}\n label={t('createTrigger')}\n />\n <div className={styles.triggerExtra}>\n {(['mention', 'always', 'never'] as const).map((mode) => (\n <button\n key={mode}\n type=\"button\"\n className={agentTrigger === mode ? styles.pillActive : styles.pill}\n onClick={() => setAgentTrigger(mode)}\n >\n {mode === 'mention' ? t('triggerMention')\n : mode === 'always' ? t('triggerAlways') : t('triggerNever')}\n </button>\n ))}\n </div>\n </div>\n\n {/* Required wording: these settings are about appearance, not authorization. */}\n <p className={styles.notice}>{t('createNotice')}</p>\n {error !== undefined && <p className={styles.error}>{t(error)}</p>}\n </div>\n </Modal>\n )\n}\n","/**\n * The three per-room dialogs: invite, members, and settings.\n *\n * They share one module because they are one interaction family — each is opened from a\n * room row's context menu, each operates on exactly one room, and each writes through the\n * same store. Splitting them would triple the plumbing without separating anything.\n *\n * The settings dialog carries the required notice: its three switches change where a room\n * appears, not who may reach it. The server enforces permissions; these are preferences.\n *\n * @module @sparkelf/dsh-group-chat/client/RoomDialogs\n */\n\nimport { useEffect, useRef, useState, type KeyboardEvent } from 'react'\nimport {\n Button,\n IconCheckOutlineRegular,\n IconLoadingOutlineRegular,\n Input,\n Modal,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport {\n inviteMembers,\n listMembers,\n searchInviteCandidates,\n updateRoom,\n type InviteCandidate,\n type RoomMember,\n} from './api.ts'\nimport { Avatar } from './Avatar.tsx'\nimport { filterCandidates, isChosen, toggleChosen } from './rows.ts'\nimport { failureOf, type RoomStore } from './store.ts'\nimport type { GroupChatKey } from './locales.ts'\nimport styles from './CreateRoomDialog.module.css'\nimport invite from './InviteDialog.module.css'\n\nexport interface RoomDialogsProps {\n readonly t: (key: GroupChatKey) => string\n readonly store: RoomStore\n}\n\n/**\n * Render whichever room dialog is open.\n * @param props - the copy resolver and the store holding the open dialog.\n * @returns the dialog, or null when none is open.\n */\nexport function RoomDialogs(props: RoomDialogsProps) {\n const { t, store } = props\n const dialog = store.getState().dialog\n if (dialog === undefined) return null\n const close = (): void => store.setDialog(undefined)\n if (dialog.kind === 'invite') {\n return <InviteDialog t={t} store={store} roomId={dialog.roomId} onClose={close} />\n }\n if (dialog.kind === 'settings') {\n return <SettingsDialog t={t} store={store} roomId={dialog.roomId} onClose={close} />\n }\n if (dialog.kind === 'transfer') {\n return <TransferDialog t={t} store={store} roomId={dialog.roomId} onClose={close} />\n }\n return <MembersDialog t={t} roomId={dialog.roomId} onClose={close} />\n}\n\n/**\n * Add existing DataOps accounts to a room.\n *\n * The list is multi-select rather than one-pick-at-a-time: a room is usually started with\n * several colleagues, and a dialog that closed after each person made that a repeated\n * errand. Rows are native buttons with `aria-pressed`, so Tab, Enter and Space all work and\n * a screen reader hears the selected state; ↑/↓ move between rows without leaving the list.\n *\n * What is selected lives in `chosen` and not in the search result rows. A query can hide an\n * already-picked person — the server search returns only what matches — and keeping the two\n * separate is what stops the selection from silently emptying as the operator narrows the\n * list.\n */\nfunction InviteDialog(props: {\n t: (key: GroupChatKey) => string\n store: RoomStore\n roomId: string\n onClose: () => void\n}) {\n const { t, store, roomId, onClose } = props\n const [candidates, setCandidates] = useState<readonly InviteCandidate[]>([])\n const [query, setQuery] = useState('')\n const [chosen, setChosen] = useState<readonly InviteCandidate[]>([])\n const [searching, setSearching] = useState(true)\n const [busy, setBusy] = useState(false)\n const [searchError, setSearchError] = useState<GroupChatKey | undefined>(undefined)\n const [submitError, setSubmitError] = useState<GroupChatKey | undefined>(undefined)\n const [done, setDone] = useState(false)\n // Bumped by Retry. The query itself may not change, so the effect needs a dependency that\n // still re-runs the same read.\n const [attempt, setAttempt] = useState(0)\n const listRef = useRef<HTMLUListElement | null>(null)\n\n // Typing re-runs the search on a pause, so a name typed at speed is one request rather\n // than one per letter. An empty query is a real query — it is what fills the list before\n // anything has been typed.\n useEffect(() => {\n let cancelled = false\n setSearching(true)\n const timer = setTimeout(() => {\n void (async () => {\n try {\n const next = await searchInviteCandidates(roomId, query)\n if (!cancelled) {\n setCandidates(next)\n setSearchError(undefined)\n }\n } catch (cause) {\n if (!cancelled) setSearchError(failureOf(cause))\n } finally {\n if (!cancelled) setSearching(false)\n }\n })()\n }, 220)\n return () => { cancelled = true; clearTimeout(timer) }\n }, [roomId, query, attempt])\n\n const toggle = (candidate: InviteCandidate): void => {\n setDone(false)\n setSubmitError(undefined)\n setChosen((current) => toggleChosen(current, candidate))\n }\n\n const submit = async (): Promise<void> => {\n if (chosen.length === 0 || busy) return\n setBusy(true)\n setSubmitError(undefined)\n try {\n await inviteMembers(roomId, chosen.map((member) => member.userId))\n await store.refresh()\n setChosen([])\n setDone(true)\n // The people just invited are members now and must drop out of the candidates. Clearing\n // the query re-runs the read; when it was already empty, the attempt counter does.\n if (query.length === 0) setAttempt((current) => current + 1)\n else setQuery('')\n } catch (cause) {\n setSubmitError(failureOf(cause))\n } finally {\n setBusy(false)\n }\n }\n\n // Arrow keys walk the rows the way a listbox does; without this, reaching the tenth\n // candidate means ten Tabs. Enter and Space already toggle, because each row is a button.\n const onListKeyDown = (event: KeyboardEvent<HTMLUListElement>): void => {\n if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return\n const rows = [...(listRef.current?.querySelectorAll<HTMLButtonElement>('button[data-candidate]') ?? [])]\n if (rows.length === 0) return\n event.preventDefault()\n const index = rows.indexOf(document.activeElement as HTMLButtonElement)\n const step = event.key === 'ArrowDown' ? 1 : -1\n const next = index < 0 ? 0 : (index + step + rows.length) % rows.length\n rows[next]?.focus()\n }\n\n const moveToList = (): void => {\n listRef.current?.querySelector<HTMLButtonElement>('button[data-candidate]')?.focus()\n }\n\n // The narrowing is applied here as well as at the server, so the list answers a keystroke\n // on the same frame rather than one debounce later. The server's answer replaces these\n // rows when it arrives.\n const visible = filterCandidates(candidates, query)\n\n return (\n <Modal\n open\n onClose={onClose}\n title={t('inviteTitle')}\n closeLabel={t('cancel')}\n className={invite.dialog}\n contentClassName={invite.content}\n footer={(\n <div className={styles.footer}>\n <Button variant=\"ghost\" size=\"md\" onClick={onClose}>{t('cancel')}</Button>\n <Button variant=\"primary\" size=\"md\" disabled={busy || chosen.length === 0}\n onClick={() => void submit()}>\n {chosen.length === 0\n ? t('inviteSubmit')\n : t('inviteSubmitCount').replace('{count}', String(chosen.length))}\n </Button>\n </div>\n )}\n >\n <div className={styles.body}>\n {/* Search and its helper text are one field, the same grouping the create dialog\n uses for the fields it shares this stylesheet with. */}\n <div className={invite.field}>\n <div className={invite.searchRow}>\n <Input\n className={invite.search}\n value={query}\n data-modal-autofocus\n placeholder={t('invitePlaceholder')}\n aria-label={t('inviteSearchLabel')}\n onChange={(event) => { setQuery(event.target.value) }}\n onKeyDown={(event) => {\n if (event.key !== 'ArrowDown') return\n event.preventDefault()\n moveToList()\n }}\n />\n {searching && <IconLoadingOutlineRegular className={invite.spinner} size={14} />}\n </div>\n <span className={styles.hint}>{t('inviteHint')}</span>\n </div>\n\n {/* A live region mounted from the first render: a status element that appears\n together with its text is not reliably announced, and the count changing is\n exactly what a screen-reader user needs to hear after each toggle. */}\n <span className={invite.visuallyHidden} role=\"status\">\n {chosen.length === 0\n ? ''\n : t('inviteSelectedCount').replace('{count}', String(chosen.length))}\n </span>\n\n {/* The chips are the durable answer to \"who am I about to invite\": they survive a\n narrowed list and name everyone, rather than leaving that to a count alone. */}\n {chosen.length > 0 && (\n <div className={styles.field}>\n <span className={styles.label}>\n {t('inviteSelectedCount').replace('{count}', String(chosen.length))}\n </span>\n <ul className={invite.chips}>\n {chosen.map((member) => (\n <li key={member.userId} className={invite.chip}>\n {/* The chip already says the name, so the picture's own label would\n repeat it in the row's accessible name. */}\n <span aria-hidden=\"true\">\n <Avatar name={member.displayName || member.username}\n src={member.avatarUrl} size=\"sm\" />\n </span>\n <span className={invite.chipName}>{member.displayName || member.username}</span>\n <button\n type=\"button\"\n className={invite.chipRemove}\n aria-label={`${t('inviteRemove')} ${member.displayName || member.username}`}\n onClick={() => {\n setChosen((current) => current.filter((row) => row.userId !== member.userId))\n }}\n >×</button>\n </li>\n ))}\n </ul>\n </div>\n )}\n\n {/* One region, four states: a failed read with its retry, a read in flight, an answer\n with rows, and an answer with none. The panel keeps its place across all four so\n the dialog does not change height as the operator types. */}\n {searchError !== undefined && !searching ? (\n <div className={invite.errorRow}>\n <span className={invite.error}>{t(searchError)}</span>\n <Button size=\"sm\" variant=\"ghost\" onClick={() => { setAttempt((current) => current + 1) }}>\n {t('retry')}\n </Button>\n </div>\n ) : searching && visible.length === 0 ? (\n <p className={invite.note}>{t('inviteSearching')}</p>\n ) : visible.length === 0 ? (\n <p className={invite.note}>{t('inviteEmpty')}</p>\n ) : (\n <ul ref={listRef} className={invite.list} onKeyDown={onListKeyDown}>\n {visible.map((candidate) => {\n const selected = isChosen(chosen, candidate.userId)\n const name = candidate.displayName.length > 0 ? candidate.displayName : candidate.username\n return (\n <li key={candidate.userId}>\n <button\n type=\"button\"\n data-candidate\n className={selected ? invite.rowSelected : invite.row}\n aria-pressed={selected}\n onClick={() => { toggle(candidate) }}\n >\n {/* The row's name is already its text; hiding the picture's label\n keeps the button's accessible name from saying it twice. */}\n <span aria-hidden=\"true\">\n <Avatar name={name} src={candidate.avatarUrl} size=\"sm\" />\n </span>\n <span className={invite.rowName}>{name}</span>\n <span className={invite.rowHandle}>{candidate.username}</span>\n {/* Held in the layout even when unselected, so a pick does not shift\n the name towards the handle. */}\n <span className={selected ? invite.check : invite.checkHidden} aria-hidden=\"true\">\n <IconCheckOutlineRegular size={14} />\n </span>\n </button>\n </li>\n )\n })}\n </ul>\n )}\n\n {done && <p className={styles.hint}>{t('saved')}</p>}\n {submitError !== undefined && <p className={invite.error}>{t(submitError)}</p>}\n </div>\n </Modal>\n )\n}\n\n/** List a room's members. */\nfunction MembersDialog(props: {\n t: (key: GroupChatKey) => string\n roomId: string\n onClose: () => void\n}) {\n const { t, roomId, onClose } = props\n const [members, setMembers] = useState<readonly RoomMember[] | undefined>(undefined)\n const [error, setError] = useState<GroupChatKey | undefined>(undefined)\n\n useEffect(() => {\n let cancelled = false\n void (async () => {\n try {\n const next = await listMembers(roomId)\n if (!cancelled) setMembers(next)\n } catch (cause) {\n if (!cancelled) setError(failureOf(cause))\n }\n })()\n return () => { cancelled = true }\n }, [roomId])\n\n return (\n <Modal open onClose={onClose} title={t('membersTitle')} closeLabel={t('cancel')}\n footer={<div className={styles.footer}><Button variant=\"ghost\" size=\"md\" onClick={onClose}>{t('cancel')}</Button></div>}>\n <div className={styles.body}>\n {error !== undefined && <p className={styles.error}>{t(error)}</p>}\n {error === undefined && members === undefined && <p className={styles.hint}>{t('loading')}</p>}\n {members !== undefined && (\n <ul className={styles.memberList}>\n {members.map((member) => (\n <li key={member.userId} className={styles.memberRow}>\n <Avatar name={member.displayName} src={member.avatarUrl} />\n <span className={styles.memberName}>{member.displayName}</span>\n {member.owner && <span className={styles.memberBadge}>{t('ownerBadge')}</span>}\n </li>\n ))}\n </ul>\n )}\n </div>\n </Modal>\n )\n}\n\n/** The room's three policy dimensions. */\n/**\n * Hand ownership of this room to another member.\n *\n * Exists because an owner cannot leave: a room without an owner has nobody who may change its\n * settings, so the server refuses the leave. Transferring is that way out, and this dialog is\n * where the owner names the successor.\n *\n * Only current members are offered, and the owner is excluded — the server rejects both a\n * non-member and a self-transfer, so listing them would only invite a refusal.\n */\nfunction TransferDialog(props: {\n t: (key: GroupChatKey) => string\n store: RoomStore\n roomId: string\n onClose: () => void\n}) {\n const { t, store, roomId, onClose } = props\n const room = store.getState().rooms?.find((candidate) => candidate.id === roomId)\n const [members, setMembers] = useState<readonly RoomMember[] | undefined>(undefined)\n const [chosen, setChosen] = useState<string | undefined>(undefined)\n const [busy, setBusy] = useState(false)\n const [failure, setFailure] = useState<GroupChatKey | undefined>(undefined)\n\n useEffect(() => {\n let cancelled = false\n void listMembers(roomId)\n .then((rows) => { if (!cancelled) setMembers(rows) })\n .catch(() => { if (!cancelled) setMembers([]) })\n return () => { cancelled = true }\n }, [roomId])\n\n const candidates = (members ?? []).filter((member) => member.userId !== store.selfId())\n\n const submit = async (): Promise<void> => {\n if (busy || chosen === undefined) return\n setBusy(true)\n setFailure(undefined)\n try {\n await store.transfer(roomId, chosen)\n onClose()\n } catch (cause) {\n setFailure(failureOf(cause))\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <Modal\n open\n onClose={busy ? () => {} : onClose}\n title={t('menuTransferTitle')}\n closeLabel={t('cancel')}\n footer={(\n <div className={styles.footer}>\n <Button variant=\"ghost\" size=\"md\" disabled={busy} onClick={onClose}>\n {t('cancel')}\n </Button>\n <Button variant=\"primary\" size=\"md\" disabled={busy || chosen === undefined}\n onClick={() => void submit()}>\n {t('menuTransferConfirm')}\n </Button>\n </div>\n )}\n >\n <div className={invite.content}>\n <p className={invite.note}>{t('menuTransferHint')}</p>\n {room !== undefined && <p className={invite.note}>{room.name}</p>}\n {members === undefined && <p className={invite.note}>…</p>}\n {members !== undefined && candidates.length === 0 && (\n <p className={invite.note}>{t('menuTransferEmpty')}</p>\n )}\n {candidates.length > 0 && (\n <ul className={invite.list} role=\"listbox\" aria-label={t('menuTransferTitle')}>\n {candidates.map((member) => (\n <li key={member.userId}>\n <button type=\"button\" role=\"option\"\n aria-selected={chosen === member.userId}\n className={chosen === member.userId ? invite.rowSelected : invite.row}\n onClick={() => setChosen(member.userId)}>\n <Avatar size=\"sm\" name={member.displayName} src={member.avatarUrl ?? undefined} />\n <span className={invite.rowName}>{member.displayName}</span>\n </button>\n </li>\n ))}\n </ul>\n )}\n {failure !== undefined && <p className={invite.error} role=\"alert\">{t(failure)}</p>}\n </div>\n </Modal>\n )\n}\n\nfunction SettingsDialog(props: {\n t: (key: GroupChatKey) => string\n store: RoomStore\n roomId: string\n onClose: () => void\n}) {\n const { t, store, roomId, onClose } = props\n const room = store.getState().rooms?.find((candidate) => candidate.id === roomId)\n const [invitePolicy, setInvitePolicy] = useState(room?.invitePolicy ?? 'anyone')\n const [discoverability, setDiscoverability] = useState(room?.discoverability ?? 'discoverable')\n const [agentTrigger, setAgentTrigger] = useState(room?.agentTrigger ?? 'mention')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<GroupChatKey | undefined>(undefined)\n\n const save = async (): Promise<void> => {\n setBusy(true)\n try {\n await updateRoom(roomId, { invitePolicy, discoverability, agentTrigger })\n await store.refresh()\n onClose()\n } catch (cause) {\n setError(failureOf(cause))\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <Modal\n open\n onClose={onClose}\n title={t('settingsTitle')}\n closeLabel={t('cancel')}\n footer={(\n <div className={styles.footer}>\n <Button variant=\"ghost\" size=\"md\" onClick={onClose}>{t('cancel')}</Button>\n <Button variant=\"primary\" size=\"md\" disabled={busy} onClick={() => void save()}>\n {t('save')}\n </Button>\n </div>\n )}\n >\n <div className={styles.body}>\n {/* Three switches, each a distinct control point: who may invite, who can find the\n room, and whether the agent answers. */}\n <div className={styles.switches}>\n <PolicyRow t={t} label={t('createPolicy')}\n value={invitePolicy === 'anyone'} onChange={(next) => setInvitePolicy(next ? 'anyone' : 'admin')}\n onLabel={t('policyAnyone')} offLabel={t('policyAdmin')} />\n <PolicyRow t={t} label={t('createVisibility')}\n value={discoverability === 'discoverable'} onChange={(next) => setDiscoverability(next ? 'discoverable' : 'invite_only')}\n onLabel={t('discoverable')} offLabel={t('inviteOnly')} />\n </div>\n <div className={styles.switches} style={{ borderTop: 'none', paddingTop: 0 }}>\n <span className={styles.label}>{t('createTrigger')}</span>\n <div className={styles.triggerExtra}>\n {(['mention', 'always', 'never'] as const).map((mode) => (\n <button\n key={mode}\n type=\"button\"\n className={agentTrigger === mode ? styles.pillActive : styles.pill}\n onClick={() => setAgentTrigger(mode)}\n >\n {mode === 'mention' ? t('triggerMention') : mode === 'always' ? t('triggerAlways') : t('triggerNever')}\n </button>\n ))}\n </div>\n </div>\n <p className={styles.notice}>{t('createNotice')}</p>\n {error !== undefined && <p className={styles.error}>{t(error)}</p>}\n </div>\n </Modal>\n )\n}\n\n/** One two-state policy row, labelled with what each side means. */\nfunction PolicyRow(props: {\n t: (key: GroupChatKey) => string\n label: string\n value: boolean\n onChange: (next: boolean) => void\n onLabel: string\n offLabel: string\n}) {\n const { label, value, onChange, onLabel, offLabel } = props\n return (\n <div className={styles.policyRow}>\n <span className={styles.label}>{label}</span>\n <div className={styles.triggerExtra}>\n <button type=\"button\" className={value ? styles.pillActive : styles.pill}\n onClick={() => onChange(true)}>{onLabel}</button>\n <button type=\"button\" className={!value ? styles.pillActive : styles.pill}\n onClick={() => onChange(false)}>{offLabel}</button>\n </div>\n </div>\n )\n}\n","/**\n * The group-chat region of the left sidebar.\n *\n * Structured after `ui-workspace`'s browsing region so the two read as the same design\n * system rather than a lookalike: a 36px header holding a 14px section label, a\n * collapsible search slot, and the trailing icon actions (view options, new room), then\n * a flat list of rows. Sizes, tokens, and icon set are the ones that package uses — the\n * group-chat region is the workspace region with the grouping removed, because a room is\n * 1:1 with its session while a workspace is 1:N.\n *\n * Registered into `sidebar.footer.action`, the official list seat at the sidebar foot. The\n * shell renders it inside `footerActions` (full width, `flex: none`), under\n * `sidebar.workspaces` and above Settings.\n *\n * @module @sparkelf/dsh-group-chat/client/SidebarRooms\n */\n\nimport { useCallback, useEffect, useRef, useState, useSyncExternalStore, type ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport {\n IconChevronsUpDownOutlineRegular,\n IconQueueOutlineRegular,\n IconClockOutlineRegular,\n IconCloseFillRegular,\n IconProjectAddOutlineRegular,\n IconSearchOutlineRegular,\n IconSlidersTwoOutlineRegular,\n Button,\n Modal,\n Tooltip,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport {\n createRoom,\n readPreferences,\n savePreferences,\n searchMessages,\n updateRoom,\n type MessageHit,\n type RoomSummary,\n} from './api.ts'\nimport { CreateRoomDialog } from './CreateRoomDialog.tsx'\nimport { RoomDialogs } from './RoomDialogs.tsx'\nimport { joinableRooms } from './rows.ts'\nimport { failureOf, type RoomStore } from './store.ts'\nimport type { GroupChatKey } from './locales.ts'\nimport styles from './SidebarRooms.module.css'\n\n/** Props the `sidebar.footer.action` seat injects. */\nexport interface SidebarRoomsInjected {\n /** Namespace-bound copy resolver, supplied by the Client entry. */\n readonly t: (key: GroupChatKey) => string\n /** Lookup for the platform's `settings.agentPreset` namespace, bound by the Client entry. */\n readonly tPreset: (key: string) => string\n /** The list both sidebar and page read, owned by the Client entry. */\n readonly store: RoomStore\n /**\n * The seat passes the shell's own `wide` flag: false when the sidebar is\n * collapsed to the icon rail.\n *\n * Collapsed shows the LIST's entry as one compact icon, NOT the room rows: the rail has\n * room for an icon, not for a room name, and a clipped name (\"文融合分析\" cut mid-glyph)\n * reads as a rendering fault rather than as a collapsed list. The icon is what the\n * reader clicks to get the rooms back, so a collapsed rail still advertises that this\n * region exists -- rendering nothing made the feature disappear entirely.\n *\n * Optional because a host that does not supply the flag keeps the previous\n * always-render behaviour.\n */\n readonly wide?: boolean\n}\n\n/**\n * Height bounds for the region, in pixels.\n *\n * The floor is one room row plus the header; the ceiling keeps the region from crowding\n * out the workspace list above it, which is the same reason the stylesheet capped it at\n * `46vh` before this was adjustable. The host clamps to the same pair, so a stored value\n * cannot arrive outside the range the region can draw.\n */\nconst HEIGHT_MIN = 80\nconst HEIGHT_MAX = 1200\n\n/** Clamps a requested height into the range the region can draw. */\nconst clampHeight = (value: number): number =>\n Math.min(HEIGHT_MAX, Math.max(HEIGHT_MIN, Math.round(value)))\n\n/** How often the list re-reads, so a peer's new room appears without a reload. */\nconst REFRESH_MS = 10_000\n\n/** Sort orders the view menu offers, mirroring the workspace menu's updated/manual pair. */\ntype OrderBy = 'updated' | 'manual'\n\n/** Whether rooms the operator left are listed. */\ntype ArchivedFilter = 'default' | 'all'\n\n/**\n * Render the sidebar's group-chat region.\n * @param props - the injected copy resolver and the shared room store.\n * @returns the section header plus the flat room list.\n */\n/**\n * The hit text with the matched words marked.\n *\n * Splitting on the query gives the reader the one thing a result row has to offer: where in a\n * long message the words are. Case comes from the source text; only the comparison folds case.\n */\nconst markQuery = (text: string, query: string): ReactNode => {\n const needle = query.trim()\n if (needle.length === 0) return text\n const parts: ReactNode[] = []\n const lower = text.toLowerCase()\n const target = needle.toLowerCase()\n let from = 0\n let at = lower.indexOf(target)\n let key = 0\n while (at !== -1) {\n if (at > from) parts.push(text.slice(from, at))\n parts.push(<mark key={key} className={styles['mark']}>{text.slice(at, at + needle.length)}</mark>)\n key += 1\n from = at + needle.length\n at = lower.indexOf(target, from)\n }\n if (from < text.length) parts.push(text.slice(from))\n return parts\n}\n\n/**\n * The collapsed rail's stand-in for this region: one icon, no rows.\n *\n * `onSelect` is the same \"open the group chat\" action the room list header offers, so the\n * collapsed rail is not a dead end — clicking it gets the reader to the rooms.\n * @param props - the realm's injected copy resolver and the open action.\n * @returns The compact control.\n */\nfunction SidebarRoomsCollapsed(props: {\n t: (key: GroupChatKey) => string\n /** What clicking the rail icon does: open the rooms (see the caller). */\n onOpen: () => void\n}): ReactNode {\n return (\n <Tooltip label={props.t('title')} side=\"right\" delayMs={400}>\n <button\n type=\"button\"\n className={styles['railButton']}\n aria-label={props.t('title')}\n onClick={props.onOpen}\n >\n <IconQueueOutlineRegular size={18} />\n </button>\n </Tooltip>\n )\n}\n\nexport function SidebarRooms(props: SidebarRoomsInjected) {\n const { t, tPreset, store } = props\n if (props.wide === false) {\n // Collapsed: the icon is the whole region. Clicking it opens the active room — the same\n // destination the row would have taken the reader to, so the rail is not a dead end.\n // `openCreate` is deliberately NOT used: a reader clicking the rail wants their\n // conversations, not a new-room form.\n const firstRoom = store.getState().rooms?.[0]\n return (\n <SidebarRoomsCollapsed\n t={t}\n onOpen={() => {\n if (firstRoom !== undefined) void store.open(firstRoom.id)\n }}\n />\n )\n }\n const state = useSyncExternalStore(store.subscribe, store.getState, store.getState)\n const { rooms, activeRoomId, failure, searchQuery, discoverable } = state\n\n const [searchExpanded, setSearchExpanded] = useState(false)\n const [viewOpen, setViewOpen] = useState(false)\n const [orderBy, setOrderBy] = useState<OrderBy>('updated')\n const [archivedFilter, setArchivedFilter] = useState<ArchivedFilter>('default')\n const [joining, setJoining] = useState<string | undefined>(undefined)\n const searchInput = useRef<HTMLInputElement | null>(null)\n /**\n * Opens the room a hit belongs to and scrolls to that message.\n *\n * The conversation is still loading when this fires, so the row is not in the DOM yet: poll\n * for the anchor instead of guessing one delay, and give up quietly rather than scrolling to\n * a stranger's message.\n */\n const openHit = (hit: MessageHit): void => {\n void store.open(hit.roomId)\n store.setSearch('')\n setSearchExpanded(false)\n if (typeof document === 'undefined') return\n let attempts = 0\n const look = (): void => {\n const node = document.querySelector(`[data-group-chat-message=\"${hit.eventId}\"]`)\n if (node !== null) {\n node.scrollIntoView({ block: 'center', behavior: 'smooth' })\n return\n }\n attempts += 1\n if (attempts < 40) window.setTimeout(look, 250)\n }\n window.setTimeout(look, 400)\n }\n /**\n * Message hits for the same box the room rows are filtered by.\n *\n * One field, two questions: the rows below answer \"which room is this\" and this list answers\n * \"where is this said\". Two separate boxes made the reader choose which kind of search they\n * wanted before they knew what they were looking for.\n */\n const [hits, setHits] = useState<readonly MessageHit[]>([])\n const [searching, setSearching] = useState(false)\n /** Issue counter so a slow query cannot overwrite the results of a later one. */\n const searchTicket = useRef(0)\n /**\n * Height of this region, in pixels, once the user has dragged it.\n *\n * `undefined` until either the saved value arrives or a drag happens: the region then keeps\n * the stylesheet's `max-height` and shrinks to its content, which is what every workspace\n * looked like before this existed. Storing a number here switches it to a fixed height.\n */\n const [heightPx, setHeightPx] = useState<number | undefined>(undefined)\n /** The height being dragged to; `undefined` when no drag is in progress. */\n const [draggingPx, setDraggingPx] = useState<number | undefined>(undefined)\n const dragStart = useRef<{ y: number; height: number } | null>(null)\n\n useEffect(() => { void store.refresh() }, [store])\n\n // The saved height belongs to the user, not to this browser: it lives in the workspace's own\n // volume so signing in from another machine restores the same layout.\n useEffect(() => {\n let live = true\n void readPreferences().then((saved) => {\n if (!live || saved.sidebarHeightPx === undefined) return\n setHeightPx(saved.sidebarHeightPx)\n })\n return () => { live = false }\n }, [])\n useEffect(() => {\n const timer = setInterval(() => { void store.refresh() }, REFRESH_MS)\n return () => clearInterval(timer)\n }, [store])\n\n /**\n * Start a height drag from the grip on the region's top edge.\n *\n * The delta is inverted because the grip sits above the region: dragging up (a smaller `y`)\n * makes the region taller, which is the same direction the right sidebar's grip works in.\n * Listeners go on the document so the pointer may leave the 4px grip — and the window —\n * without dropping the drag halfway.\n */\n const beginResize = (event: React.PointerEvent<HTMLDivElement>): void => {\n event.preventDefault()\n const section = event.currentTarget.parentElement\n if (section === null) return\n dragStart.current = { y: event.clientY, height: section.getBoundingClientRect().height }\n setDraggingPx(dragStart.current.height)\n\n const onMove = (move: PointerEvent): void => {\n const start = dragStart.current\n if (start === null) return\n setDraggingPx(clampHeight(start.height + (start.y - move.clientY)))\n }\n const onUp = (): void => {\n document.removeEventListener('pointermove', onMove)\n document.removeEventListener('pointerup', onUp)\n document.removeEventListener('pointercancel', onUp)\n dragStart.current = null\n setDraggingPx((settled) => {\n if (settled !== undefined) {\n setHeightPx(settled)\n // Best-effort: a failed save leaves the layout correct for this session and the\n // stored value unchanged, which is a better outcome than blocking the drop.\n void savePreferences({ sidebarHeightPx: settled }).catch(() => undefined)\n }\n return undefined\n })\n }\n document.addEventListener('pointermove', onMove)\n document.addEventListener('pointerup', onUp)\n document.addEventListener('pointercancel', onUp)\n }\n\n /**\n * Message search, driven by the header box.\n *\n * Debounced and latest-wins: typing issues several queries, and an early one resolving late\n * would replace the results of a later, more specific one.\n */\n useEffect(() => {\n if (!searchExpanded) return\n const trimmed = searchQuery.trim()\n if (trimmed.length === 0) {\n setHits([])\n setSearching(false)\n return\n }\n const ticket = searchTicket.current + 1\n searchTicket.current = ticket\n setSearching(true)\n const timer = setTimeout(() => {\n void searchMessages(trimmed)\n .then((found) => { if (searchTicket.current === ticket) setHits(found) })\n .catch(() => { if (searchTicket.current === ticket) setHits([]) })\n .finally(() => { if (searchTicket.current === ticket) setSearching(false) })\n }, 250)\n return () => { clearTimeout(timer) }\n }, [searchQuery, searchExpanded])\n\n // The search box closes on Escape and on a click outside, the same way the workspace\n // one does, so the header returns to its resting width without a second control.\n useEffect(() => {\n if (!searchExpanded) return\n const onDown = (event: MouseEvent): void => {\n const target = event.target as HTMLElement | null\n if (target?.closest(`.${styles.search}`) === null) {\n if (searchQuery.length === 0) setSearchExpanded(false)\n }\n }\n document.addEventListener('mousedown', onDown)\n return () => document.removeEventListener('mousedown', onDown)\n }, [searchExpanded, searchQuery.length])\n\n const query = searchQuery.trim().toLowerCase()\n const visible = (rooms ?? [])\n // A room the operator left is hidden by default and reachable through the view menu,\n // which is the same shape the workspace list uses for archived sessions.\n .filter((room) => archivedFilter === 'all' || room.joined)\n .filter((room) => query.length === 0 || room.name.toLowerCase().includes(query))\n .slice()\n .sort((a, b) => orderBy === 'manual'\n ? 0\n : Date.parse(b.updatedAt || b.createdAt) - Date.parse(a.updatedAt || a.createdAt))\n\n const onPickOrder = useCallback(async (mode: OrderBy): Promise<void> => {\n setOrderBy(mode)\n setViewOpen(false)\n }, [])\n\n // Discovery runs only for a non-empty query, and debounced: every keystroke would otherwise\n // be a cross-account search, and the answer for \"a\" is never what the operator wanted.\n useEffect(() => {\n const keyword = searchQuery.trim()\n if (keyword.length === 0) return\n let cancelled = false\n const timer = setTimeout(() => {\n if (!cancelled) void store.discover(keyword)\n }, 300)\n return () => {\n cancelled = true\n clearTimeout(timer)\n }\n }, [searchQuery, store])\n\n // A room already in the sidebar must not also appear as joinable: the two lists answer\n // different questions, and a room in both would offer a Join button that cannot do anything.\n const joinable = joinableRooms(discoverable ?? [], new Set((rooms ?? []).map((room) => room.id)))\n\n const onJoin = async (roomId: string): Promise<void> => {\n setJoining(roomId)\n try {\n await store.join(roomId)\n // Opened on success: the operator pressed Join to get into the room, so landing in it is\n // the outcome they asked for rather than a second step they must discover.\n store.open(roomId)\n store.setSearch('')\n setSearchExpanded(false)\n } finally {\n setJoining(undefined)\n }\n }\n\n return (\n <section\n className={styles.root}\n aria-label={t('title')}\n // Always a fixed height. Without one the region shrink-wrapped its rows up to the\n // stylesheet's `46vh`, which on a tall window pushed the workspace list far up the\n // sidebar and made a two-room region look like the main surface. A reader who has never\n // dragged gets the floor: a handful of rows, and the sidebar keeps its proportions.\n style={{ height: `${String(draggingPx ?? heightPx ?? HEIGHT_MIN)}px`, maxHeight: 'none' }}\n >\n {/* The grip sits on the top edge, hidden until the region is hovered or focused: it is a\n pointer affordance, and a permanently visible bar would take a row of vertical space\n from the list it resizes. `onPointerDown` (not mouse) so a pen and a touch work too. */}\n <div\n className={styles.resizer}\n role=\"separator\"\n aria-orientation=\"horizontal\"\n aria-label={t('resizeRegion')}\n aria-valuenow={Math.round(draggingPx ?? heightPx ?? 0) || undefined}\n tabIndex={0}\n onPointerDown={beginResize}\n onKeyDown={(event) => {\n // Both pairs move towards the same physical edge: up/left grow the region.\n const step = event.shiftKey ? 40 : 10\n const grow = event.key === 'ArrowUp' || event.key === 'ArrowLeft'\n const shrink = event.key === 'ArrowDown' || event.key === 'ArrowRight'\n if (!grow && !shrink) return\n event.preventDefault()\n const section = event.currentTarget.parentElement\n const current = heightPx ?? HEIGHT_MIN\n const next = clampHeight(current + (grow ? step : -step))\n setHeightPx(next)\n void savePreferences({ sidebarHeightPx: next }).catch(() => undefined)\n }}\n />\n <div className={styles.sectionHeader}>\n {!searchExpanded && <span className={styles.sectionLabel}>{t('title')}</span>}\n\n <div className={searchExpanded ? styles.searchSlotExpanded : styles.searchSlot}>\n <div\n className={searchExpanded ? styles.searchExpanded : styles.search}\n onClick={() => { setSearchExpanded(true); searchInput.current?.focus() }}\n >\n <Tooltip label={t('search')} side=\"bottom\" delayMs={500} disabled={searchExpanded}>\n <button\n type=\"button\"\n className={styles.searchButton}\n aria-label={t('search')}\n aria-expanded={searchExpanded}\n onClick={(event) => {\n event.stopPropagation()\n setSearchExpanded((v) => !v)\n }}\n >\n <IconSearchOutlineRegular size={searchExpanded ? 11 : 14} />\n </button>\n </Tooltip>\n <input\n ref={searchInput}\n className={styles.searchInput}\n type=\"text\"\n placeholder={t('searchPlaceholder')}\n value={searchQuery}\n tabIndex={searchExpanded ? 0 : -1}\n onChange={(event) => store.setSearch(event.target.value)}\n onKeyDown={(event) => {\n if (event.key !== 'Escape') return\n store.setSearch('')\n setSearchExpanded(false)\n }}\n />\n {searchExpanded && (\n <button\n type=\"button\"\n className={styles.clearButton}\n aria-label={t('searchClear')}\n onClick={(event) => {\n event.stopPropagation()\n store.setSearch('')\n setSearchExpanded(false)\n }}\n >\n <IconCloseFillRegular />\n </button>\n )}\n </div>\n </div>\n\n {!searchExpanded && (\n <div className={styles.headerActions}>\n <ViewOptionsMenu\n t={t}\n open={viewOpen}\n orderBy={orderBy}\n archivedFilter={archivedFilter}\n onToggle={() => setViewOpen((v) => !v)}\n onPickOrder={(mode) => void onPickOrder(mode)}\n onPickArchived={(mode) => { setArchivedFilter(mode); setViewOpen(false) }}\n />\n <Tooltip label={t('createRoom')} side=\"bottom\" delayMs={500}>\n <button\n type=\"button\"\n className={styles.iconButton}\n aria-label={t('createRoom')}\n onClick={() => store.openCreate()}\n >\n <IconProjectAddOutlineRegular size={16} />\n </button>\n </Tooltip>\n </div>\n )}\n </div>\n\n <div className={styles.listArea}>\n\n {searchExpanded && query.length > 0 ? (\n <div className={styles.hitList} role=\"listbox\" aria-label={t('search')}>\n {searching && hits.length === 0 ? (\n <p className={styles.hitHint}>{t('searching')}</p>\n ) : hits.length === 0 ? null : (\n hits.map((hit) => (\n <button\n key={`${hit.roomId}:${hit.eventId}`}\n type=\"button\"\n role=\"option\"\n aria-selected={false}\n className={styles.hit}\n onClick={() => { openHit(hit) }}\n >\n <span className={styles.hitHead}>\n <span className={styles.hitRoom}>{hit.roomName}</span>\n {hit.authorName === null ? null : (\n <span className={styles.hitAuthor}>{hit.authorName}</span>\n )}\n </span>\n <span className={styles.hitText}>{markQuery(hit.text, searchQuery)}</span>\n </button>\n ))\n )}\n </div>\n ) : null}\n\n <div className={styles.list}>\n {failure !== undefined && <p className={styles.empty}>{t(failure)}</p>}\n {failure === undefined && rooms === undefined && <p className={styles.empty}>{t('loading')}</p>}\n {failure === undefined && rooms !== undefined && visible.length === 0 && (\n /* The same shape the workspace region uses when it has no sessions: a centred\n glyph above the copy and nothing else. The header already carries the way to\n create a room, so an action here would repeat it. */\n <div className={styles.emptyState}>\n <IconQueueOutlineRegular size={28} />\n <span>{query.length > 0 ? t('searchEmpty') : t('emptyRooms')}</span>\n </div>\n )}\n {visible.length > 0 && (\n <ul className={styles.flatList}>\n {visible.map((room) => (\n <li key={room.id}>\n <RoomRow\n room={room}\n t={t}\n store={store}\n selected={room.id === activeRoomId}\n onOpen={() => store.open(room.id)}\n />\n </li>\n ))}\n </ul>\n )}\n\n {/* Rooms this account could join. Shown only while searching, because that is the\n only moment the operator is looking for something they are not already in; a\n permanent section would put other people's rooms in a sidebar that otherwise\n answers \"what do I belong to\". */}\n {query.length > 0 && discoverable !== undefined && joinable.length > 0 && (\n <div className={styles.discoverSection}>\n <p className={styles.discoverHeading}>{t('discoverHeading')}</p>\n <ul className={styles.flatList}>\n {joinable.map((room) => (\n <li key={room.id}>\n <div className={styles.discoverRow}>\n <span className={styles.discoverName}>{room.name}</span>\n <span className={styles.discoverMeta}>\n {t('discoverMembers').replace('{count}', String(room.memberCount))}\n </span>\n <button\n type=\"button\"\n className={styles.joinButton}\n disabled={joining === room.id}\n onClick={() => { void onJoin(room.id) }}\n >\n {joining === room.id ? t('joining') : t('joinRoom')}\n </button>\n </div>\n </li>\n ))}\n </ul>\n </div>\n )}\n </div>\n {/* Matches the workspace list's bottom fade, so the list reads as scrollable\n without a visible scrollbar track. */}\n <div className={styles.fade} aria-hidden=\"true\" />\n </div>\n\n {/* The dialogs live here rather than on the room page. `Modal` portals itself to\n `document.body`, so it only needs to be rendered by a mounted component — and\n this region is always mounted, while the room page is not: pressing \"New room\"\n from the sidebar must work before any room has ever been opened. */}\n <CreateRoomDialog t={t} tPreset={tPreset} store={store} />\n <RoomDialogs t={t} store={store} />\n </section>\n )\n}\n\n/** One room row: glyph, name, and the member count where a session row shows its time. */\nfunction RoomRow(props: {\n room: RoomSummary\n t: (key: GroupChatKey) => string\n selected: boolean\n onOpen: () => void\n store: RoomStore\n}) {\n const { room, t, selected, onOpen, store } = props\n // The rect is captured when the menu opens, not read during render: the menu is\n // positioned against where the row was at that moment, so scrolling the list while the\n // menu is open does not drag it away from the row the operator aimed at.\n const [menuAnchor, setMenuAnchor] = useState<DOMRect | null>(null)\n // The leave confirmation lives on the row, not on the menu: choosing \"leave room\" closes\n // the menu, and a confirmation owned by the menu would unmount with it. The row itself\n // stays mounted while the room is still listed, so the dialog can stay open long enough to\n // report a refused leave.\n const [confirmingLeave, setConfirmingLeave] = useState(false)\n const [leaving, setLeaving] = useState(false)\n const [leaveFailure, setLeaveFailure] = useState<GroupChatKey | undefined>(undefined)\n // Dissolving gets its own dialog and its own failure slot: it ends the room for everyone,\n // unlike leaving, so sharing the leave state would print one act's refusal in the other's\n // dialog.\n const [confirmingDissolve, setConfirmingDissolve] = useState(false)\n const [dissolving, setDissolving] = useState(false)\n const [dissolveFailure, setDissolveFailure] = useState<GroupChatKey | undefined>(undefined)\n\n // A room this account has not joined has no reading position, so it never carries a badge:\n // the server reports 0 for it and the row keeps showing \"left\".\n const unread = room.joined ? room.unreadCount ?? 0 : 0\n const mentioned = unread > 0 && (room.mentionCount ?? 0) > 0\n\n const closeLeave = (): void => {\n setConfirmingLeave(false)\n setLeaveFailure(undefined)\n }\n\n const closeDissolve = (): void => {\n setConfirmingDissolve(false)\n setDissolveFailure(undefined)\n }\n\n const leave = async (): Promise<void> => {\n if (leaving) return\n setLeaving(true)\n setLeaveFailure(undefined)\n try {\n await store.leave(room.id)\n setConfirmingLeave(false)\n } catch (cause) {\n // The operator is still a member, so the dialog stays open and names the reason\n // instead of closing as if the leave had worked.\n setLeaveFailure(failureOf(cause))\n } finally {\n setLeaving(false)\n }\n }\n\n const dissolve = async (): Promise<void> => {\n if (dissolving) return\n setDissolving(true)\n setDissolveFailure(undefined)\n try {\n await store.dissolve(room.id)\n setConfirmingDissolve(false)\n } catch (cause) {\n // The room still exists, so the dialog stays open and names why the dissolve was\n // refused — closing here would read as success.\n setDissolveFailure(failureOf(cause))\n } finally {\n setDissolving(false)\n }\n }\n\n return (\n <div className={styles.rowWrap}>\n <button\n type=\"button\"\n className={selected ? styles.sessionRowSelected : styles.sessionRow}\n onClick={onOpen}\n onContextMenu={(event) => {\n event.preventDefault()\n setMenuAnchor(event.currentTarget.getBoundingClientRect())\n }}\n aria-current={selected}\n >\n <span className={styles.rowIcon} aria-hidden=\"true\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n strokeWidth=\"1.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M2.5 3.5A1.5 1.5 0 0 1 4 2h5a1.5 1.5 0 0 1 1.5 1.5v3A1.5 1.5 0 0 1 9 8H5.5L3 10V8h-.5A1.5 1.5 0 0 1 2.5 6.5z\" />\n <path d=\"M11 5.5h1A1.5 1.5 0 0 1 13.5 7v3.5A1.5 1.5 0 0 1 12 12h-.5v2l-2.5-2H7\" />\n </svg>\n </span>\n <span className={styles.title}>{room.name}</span>\n {unread > 0 ? (\n // The unread badge replaces the member count rather than sitting beside it: the row\n // has one trailing slot, and \"3 unread\" is what the reader needs from it. A mention\n // gets its own colour so \"someone wrote\" and \"someone wrote to me\" are distinguishable\n // at a glance without reading the number.\n <span\n className={mentioned ? styles.unreadMentioned : styles.unread}\n aria-label={mentioned\n ? t('unreadMentionAria').replace('{count}', String(unread))\n : t('unreadAria').replace('{count}', String(unread))}\n >\n {unread > 99 ? '99+' : String(unread)}\n </span>\n ) : (\n <span className={styles.time}>\n {room.joined ? t('memberCountShort').replace('{count}', String(room.memberCount)) : t('left')}\n </span>\n )}\n </button>\n {menuAnchor !== null && (\n <RoomMenu t={t} room={room} store={store} anchor={menuAnchor}\n onClose={() => setMenuAnchor(null)}\n onRequestLeave={() => { setMenuAnchor(null); setConfirmingLeave(true) }}\n onRequestDissolve={() => { setMenuAnchor(null); setConfirmingDissolve(true) }} />\n )}\n {confirmingLeave && (\n <LeaveRoomDialog t={t} room={room} busy={leaving} failure={leaveFailure}\n onCancel={closeLeave} onConfirm={() => void leave()} />\n )}\n {confirmingDissolve && (\n <DissolveRoomDialog t={t} room={room} busy={dissolving} failure={dissolveFailure}\n onCancel={closeDissolve} onConfirm={() => void dissolve()} />\n )}\n </div>\n )\n}\n\n/**\n * The row's context menu.\n *\n * Two official workspace entries are absent here rather than relabelled. Archiving would\n * duplicate \"leave room\", and forking copies history whose events carry no member\n * attribution — producing a room that looks continuous but whose earlier messages belong\n * to nobody. The remaining entries are the ones the design settled on, and each is shown\n * only when it applies to this room and this caller.\n */\nfunction RoomMenu(props: {\n t: (key: GroupChatKey) => string\n room: RoomSummary\n store: RoomStore\n onClose: () => void\n /** Ask the row to open the leave confirmation; the menu closes as it does. */\n onRequestLeave: () => void\n /** Ask the row to open the dissolve confirmation; the menu closes as it does. */\n onRequestDissolve: () => void\n /** Viewport rect of the row that opened the menu, which the portal positions against. */\n anchor: DOMRect\n}) {\n const { t, room, store, onClose, anchor, onRequestLeave, onRequestDissolve } = props\n const [renaming, setRenaming] = useState(false)\n const [draftName, setDraftName] = useState(room.name)\n const [busy, setBusy] = useState(false)\n\n const run = async (action: () => Promise<void>): Promise<void> => {\n setBusy(true)\n try {\n await action()\n onClose()\n } finally {\n setBusy(false)\n }\n }\n\n // Rendered into `document.body`, not beside the row. The room list scrolls\n // (`overflow-y: auto`), and a positioned menu inside a scrolling box is clipped at the\n // box's edge — the lower rooms' menus lost their bottom entries. The portal puts the menu\n // outside every clipping ancestor, and the anchor rect keeps it visually attached to the\n // row that opened it.\n //\n // The menu flips above the row when the row sits too low in the viewport to hold it:\n // pinning `top` unconditionally pushed the last rooms' menus past the bottom edge, which\n // clips exactly the entries the operator was reaching for. `bottom` is measured from the\n // viewport edge, so the flip needs no knowledge of the menu's own height.\n const below = window.innerHeight - anchor.bottom - 8\n const flip = below < 220 && anchor.top > below\n const placement = flip\n ? { bottom: window.innerHeight - anchor.top + 4 }\n : { top: anchor.bottom + 4 }\n const portal = (body: React.ReactNode): React.ReactPortal =>\n createPortal(\n <div className={styles.menuLayer} style={{ ...placement, left: anchor.left }}\n onClick={(event) => event.stopPropagation()}>\n {body}\n </div>,\n document.body,\n )\n\n if (renaming) {\n const commit = (): void => {\n void run(() => store.rename(room.id, draftName.trim()))\n }\n const cancel = (): void => {\n setRenaming(false)\n setDraftName(room.name)\n }\n return portal(\n // A wider panel than the menu rows need: the field and its two actions read as one\n // small form, and a 200px menu left the label, field and button stacked edge to edge.\n <div className={styles.menuRename} role=\"dialog\" aria-label={t('renameTitle')}>\n <span className={styles.menuHeading}>{t('renameTitle')}</span>\n <input\n className={styles.menuInput}\n value={draftName}\n autoFocus\n placeholder={t('renamePlaceholder')}\n onChange={(event) => setDraftName(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && draftName.trim().length > 0) commit()\n if (event.key === 'Escape') cancel()\n }}\n />\n <div className={styles.menuActions}>\n <button type=\"button\" className={styles.menuCancel} onClick={cancel}>\n {t('cancel')}\n </button>\n <button\n type=\"button\"\n className={styles.menuConfirm}\n disabled={busy || draftName.trim().length === 0}\n onClick={commit}\n >\n {t('save')}\n </button>\n </div>\n </div>,\n )\n }\n\n return portal(\n <div className={styles.menu} role=\"menu\">\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => setRenaming(true)}>\n {t('menuRename')}\n </button>\n {/* Inviting and leaving are member actions; the server decides whether this caller\n may invite, so the entry is offered to members and a refusal surfaces as copy. */}\n {room.joined && (\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => { store.setDialog({ kind: 'invite', roomId: room.id }); onClose() }}>\n {t('menuInvite')}\n </button>\n )}\n {/* Members are a side panel, not a dialog: a roster is consulted while reading, and a modal\n would cover the conversation it describes. `openMembers` is absent on a profile without\n the official right sidebar, where the entry would have nowhere to lead. */}\n {store.openMembers !== undefined && (\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => { store.openMembers?.(room.id); onClose() }}>\n {t('menuMembers')}\n </button>\n )}\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => { store.setDialog({ kind: 'settings', roomId: room.id }); onClose() }}>\n {t('menuSettings')}\n </button>\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => { void navigator.clipboard?.writeText(room.id); onClose() }}>\n {t('copyRoomId')}\n </button>\n {/* The owner is offered a transfer instead of a leave: leaving is refused by the server\n for them (a room without an owner has nobody able to change its settings), so showing\n \"leave room\" would only produce a refusal. Everyone else keeps the plain leave. */}\n {room.joined && store.selfId() === room.ownerUserId && (\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => { store.setDialog({ kind: 'transfer', roomId: room.id }); onClose() }}>\n {t('menuTransfer')}\n </button>\n )}\n {room.joined && store.selfId() !== room.ownerUserId && (\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={onRequestLeave}>\n {t('menuLeave')}\n </button>\n )}\n {/* Dissolving ends the room for everyone, so it is offered only to the people the\n server will accept it from: the owner, and an administrator. The server still\n decides on the request itself. */}\n {(store.selfId() === room.ownerUserId || store.administrator()) && (\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={onRequestDissolve}>\n {t('menuDissolve')}\n </button>\n )}\n </div>,\n )\n}\n\n /**\n * The dissolve-room confirmation.\n *\n * A separate dialog from leaving, not a variant of it: leaving removes one membership and the\n * room carries on for everyone else, while dissolving ends it for every member and takes the\n * messages with it. Sharing one dialog would let the far more destructive act read as the\n * mild one.\n *\n * Like the leave dialog, a refusal keeps this open and prints the mapped reason: the room is\n * still there, so closing would read as success.\n */\n function DissolveRoomDialog(props: {\n t: (key: GroupChatKey) => string\n room: RoomSummary\n busy: boolean\n failure: GroupChatKey | undefined\n onCancel: () => void\n onConfirm: () => void\n }) {\n const { t, room, busy, failure, onCancel, onConfirm } = props\n return (\n <Modal\n open\n onClose={onCancel}\n title={t('menuDissolve')}\n closeLabel={t('cancel')}\n footer={(\n <div className={styles.footer}>\n <Button data-modal-autofocus variant=\"ghost\" size=\"md\" disabled={busy} onClick={onCancel}>\n {t('cancel')}\n </Button>\n {/* Destructive, so it is red rather than the primary fill: the primitive has no\n danger variant, and colour is the only thing separating this from a harmless\n confirm at a glance. */}\n <Button variant=\"ghost\" size=\"md\" disabled={busy}\n className={styles.leaveConfirmDanger}\n onClick={onConfirm}>\n {t('menuDissolve')}\n </Button>\n </div>\n )}\n >\n <div className={styles.leaveBody}>\n <p className={styles.leaveCopy}>{t('menuDissolveConfirm')}</p>\n {/* A bare room name read as a stray label once the dialog opened from a menu that had\n already closed. Naming the target explicitly is what lets a reader confirm they are\n about to delete the room they meant to. */}\n <p className={styles.leaveRoomName}>\n {t('menuDissolveTarget')}<strong>{room.name}</strong>\n </p>\n {failure !== undefined && (\n <p className={styles.leaveError} role=\"alert\">{t(failure)}</p>\n )}\n </div>\n </Modal>\n )\n }\n\n\n/**\n * The leave-room confirmation.\n *\n * It exists because leaving is not undoable from this surface: the sidebar keeps the room\n * under the \"left rooms\" filter, but rejoining is a separate discovery step. The copy names\n * that the agent's recorded history stays, which is the part a reader cannot infer.\n *\n * A refused leave keeps this dialog open and prints the mapped failure. Closing on failure\n * was the old behaviour and read as success — the only sign it had gone wrong was the room\n * still being in the list.\n */\nfunction LeaveRoomDialog(props: {\n t: (key: GroupChatKey) => string\n room: RoomSummary\n busy: boolean\n failure: GroupChatKey | undefined\n onCancel: () => void\n onConfirm: () => void\n}) {\n const { t, room, busy, failure, onCancel, onConfirm } = props\n return (\n <Modal\n open\n onClose={onCancel}\n title={t('menuLeave')}\n closeLabel={t('cancel')}\n footer={(\n <div className={styles.footer}>\n {/* Cancel is the initial focus: the safe answer is the default for a destructive\n dialog, and Escape maps to the same command. */}\n <Button data-modal-autofocus variant=\"ghost\" size=\"md\" disabled={busy} onClick={onCancel}>\n {t('cancel')}\n </Button>\n <Button variant=\"primary\" size=\"md\" disabled={busy} onClick={onConfirm}>\n {t('menuLeave')}\n </Button>\n </div>\n )}\n >\n <div className={styles.leaveBody}>\n <p className={styles.leaveCopy}>{t('menuLeaveConfirm')}</p>\n <p className={styles.leaveRoomName}>{room.name}</p>\n {failure !== undefined && (\n <p className={styles.leaveError} role=\"alert\">{t(failure)}</p>\n )}\n </div>\n </Modal>\n )\n}\n\n/** The trailing view menu: ordering and whether rooms the operator left are listed. */\nfunction ViewOptionsMenu(props: {\n t: (key: GroupChatKey) => string\n open: boolean\n orderBy: OrderBy\n archivedFilter: ArchivedFilter\n onToggle: () => void\n onPickOrder: (mode: OrderBy) => void\n onPickArchived: (mode: ArchivedFilter) => void\n}) {\n const { t, open, orderBy, archivedFilter, onToggle, onPickOrder, onPickArchived } = props\n return (\n <div className={styles.viewWrap}>\n <Tooltip label={t('viewOptions')} side=\"bottom\" delayMs={500}>\n <button\n type=\"button\"\n className={styles.iconButton}\n aria-label={t('viewOptions')}\n aria-expanded={open}\n onClick={onToggle}\n >\n <IconSlidersTwoOutlineRegular size={16} />\n </button>\n </Tooltip>\n {open && (\n <div className={styles.menuInline} role=\"menu\">\n <span className={styles.menuHeading}>{t('orderBy')}</span>\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => onPickOrder('updated')}>\n <IconClockOutlineRegular size={14} />\n {t('orderByUpdated')}\n {orderBy === 'updated' && <span className={styles.check}>✓</span>}\n </button>\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => onPickOrder('manual')}>\n <IconChevronsUpDownOutlineRegular size={14} />\n {t('orderByManual')}\n {orderBy === 'manual' && <span className={styles.check}>✓</span>}\n </button>\n <span className={styles.menuHeading}>{t('archivedFilter')}</span>\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => onPickArchived('default')}>\n {t('archivedHide')}\n {archivedFilter === 'default' && <span className={styles.check}>✓</span>}\n </button>\n <button type=\"button\" role=\"menuitem\" className={styles.menuItem}\n onClick={() => onPickArchived('all')}>\n {t('archivedShow')}\n {archivedFilter === 'all' && <span className={styles.check}>✓</span>}\n </button>\n </div>\n )}\n </div>\n )\n}\n","import { clearQuote, registerQuote } from './api.ts'\n\n/**\n * The message a reply is being written against, shared by the three surfaces that need it.\n *\n * Replying spans two parts of the UI that do not otherwise talk to each other: the button on a\n * message that starts a reply, and the composer that has to show what is being answered. A store\n * this plugin already owns is the smallest thing that lets them agree, and it keeps the pending\n * reply in one place instead of in three components' props.\n *\n * The value is per room, not global: a member can be writing a reply in one room and switch to\n * another, and carrying the quote across that switch would attach it to a message the reader\n * never picked.\n *\n * @module @sparkelf/dsh-group-chat/client/quote\n */\n\n/**\n * The block type a reply uses to carry the message it answers.\n *\n * Declared in this shared module rather than beside the renderer that reads it: the same string\n * is written by the host when it publishes the reply, and a second literal there would let the\n * two drift into a quote nothing renders.\n */\nexport const QUOTE_BLOCK_TYPE = 'group-chat-quote'\n\n/**\n * The parts of a message row this module reads.\n *\n * A minimal structural shape rather than the official message type: the renderer receives the\n * official `UserMessage` and this module only ever looks at these fields, so naming the larger\n * type would tie it to a package this plugin does not depend on.\n */\nexport interface MessageLike {\n readonly source?: {\n /** Producer of the message; `group-chat-quote` marks model-only context. */\n readonly kind?: unknown\n readonly roomId?: unknown\n readonly memberId?: unknown\n readonly authorKind?: unknown\n readonly memberName?: unknown\n /** The room's log row this message was stored as; what a reply points at. */\n readonly messageId?: unknown\n } | null\n readonly content?: unknown\n readonly id?: unknown\n}\n\n/**\n * The message a reply points at, as the reply carries it.\n *\n * The quote rides inside the message's own `content` as a block, not as a top-level field: the\n * event envelope is the official `UserMessage` and anything outside `content`/`source` is\n * dropped on the way to the surface. A block survives, and the backend's block type is open\n * (`AiChatContentBlock` is `{ type: string; ... }`), so an unknown type passes through instead\n * of failing validation.\n */\nexport interface QuotedMessage {\n /** The id of the message being answered. */\n readonly messageId: string\n /** That message's author, as their name read when the reply was written. */\n readonly authorName?: string\n /** The first line of what they said, already trimmed for display. */\n readonly preview: string\n}\n\n/** How much of the quoted message to keep. One line, like the composer shows. */\nconst QUOTE_PREVIEW_CHARS = 120\n\n/**\n * Reads the quoted message out of a reply, if it has one.\n *\n * Returns `undefined` rather than a partial object: a quote with no preview has nothing to show,\n * and drawing an empty bar above the bubble would look like a rendering fault.\n *\n * @param message - the message data the node carries.\n * @returns the quote to draw, or `undefined` for an ordinary message.\n */\n/**\n * The text of a message, joined from its text blocks.\n *\n * Only `text` blocks contribute: an image or a file has no words to quote, and including a\n * placeholder for it would put text in the reply that the author never wrote.\n *\n * @param message - the message data the node carries.\n * @returns the collapsed text, or `''` when the message has none.\n */\nexport const previewOf = (message: MessageLike | undefined): string => {\n const content = message?.content\n // A text-only message carries its body as a plain string, not as a block list: that is the\n // shape the official composer submits, and reading only arrays left every ordinary message\n // with an empty preview — which silently removed the reply control from all of them.\n if (typeof content === 'string') return content.replace(/\\s+/g, ' ').trim()\n if (!Array.isArray(content)) return ''\n const parts: string[] = []\n for (const block of content) {\n if (typeof block !== 'object' || block === null) continue\n const record = block as Record<string, unknown>\n if (record['type'] !== 'text') continue\n const text = record['text']\n if (typeof text === 'string' && text.trim().length > 0) parts.push(text)\n }\n return parts.join(' ').replace(/\\s+/g, ' ').trim()\n}\n\nexport const quoteOf = (message: MessageLike | undefined): QuotedMessage | undefined => {\n const content = message?.content\n if (!Array.isArray(content)) return undefined\n for (const block of content) {\n if (typeof block !== 'object' || block === null) continue\n const record = block as Record<string, unknown>\n if (record['type'] !== QUOTE_BLOCK_TYPE) continue\n const messageId = record['messageId']\n const preview = record['preview']\n if (typeof messageId !== 'string' || messageId.length === 0) return undefined\n if (typeof preview !== 'string') return undefined\n const authorName = typeof record['authorName'] === 'string' && record['authorName'].trim().length > 0\n ? record['authorName'].trim()\n : undefined\n return { messageId, ...authorName === undefined ? {} : { authorName }, preview }\n }\n return undefined\n}\n\n/**\n * Builds the block a reply carries.\n *\n * Whitespace collapses to single spaces and the result is cut to one line: the quote is a\n * pointer, and a bar showing a paragraph would push the actual reply off the screen.\n *\n * @param messageId - the message being answered.\n * @param preview - what it said, taken from its text blocks.\n * @param authorName - who said it, when known.\n * @returns the block to append to the reply's content.\n */\nexport const quoteBlock = (\n messageId: string,\n preview: string,\n authorName?: string,\n): Record<string, unknown> => {\n const collapsed = preview.replace(/\\s+/g, ' ').trim()\n const trimmed = collapsed.length > QUOTE_PREVIEW_CHARS\n ? `${collapsed.slice(0, QUOTE_PREVIEW_CHARS - 1)}…`\n : collapsed\n return {\n type: QUOTE_BLOCK_TYPE,\n messageId,\n preview: trimmed,\n ...authorName === undefined ? {} : { authorName },\n }\n}\n\n\n/** What a pending reply points at, resolved to what the composer bar has to draw. */\nexport interface PendingQuote {\n /** The room the quoted message belongs to. */\n readonly roomId: string\n /** The id of the message being answered, as the room's event log knows it. */\n readonly messageId: string\n /** The author's display name, as the roster gave it when the reply was started. */\n readonly authorName?: string\n /** One line of what they said, already collapsed and trimmed for display. */\n readonly preview: string\n}\n\n/** The read side the components subscribe to, and the write side they call. */\nexport interface QuoteStore {\n /** The pending quote for one room, retold on every change. */\n readonly getQuote: (roomId: string) => PendingQuote | undefined\n /** Notified whenever any room's pending quote changes. */\n readonly subscribe: (listener: () => void) => () => void\n /** Starts a reply in one room, replacing whatever was pending there. */\n readonly start: (quote: PendingQuote) => void\n /** Drops the pending reply for one room, after it was sent or dismissed. */\n readonly clear: (roomId: string) => void\n /**\n * Drops the pending reply on this side only, leaving the host's copy for it to consume.\n *\n * Used when the composer submits: the host attaches the quote to the message it is about to\n * publish and then forgets it. Telling the host to delete at that moment would erase the quote\n * before the message arrives, and the reply would go out bare.\n */\n readonly clearLocal: (roomId: string) => void\n /** Reads the current quote for a room without subscribing, for one-shot decisions. */\n readonly peek: (roomId: string) => PendingQuote | undefined\n}\n\n/**\n * Creates the store.\n *\n * A plain observable rather than a framework store, for the same reason the room list is one:\n * every consumer is a component this plugin already owns, so the subscription contract is just\n * `useSyncExternalStore`.\n *\n * @returns the store the client entry hands to the components that need it.\n */\nexport const createQuoteStore = (): QuoteStore => {\n const quotes = new Map<string, PendingQuote>()\n const listeners = new Set<() => void>()\n\n const notify = (): void => {\n for (const listener of [...listeners]) listener()\n }\n\n return {\n getQuote: (roomId) => quotes.get(roomId),\n peek: (roomId) => quotes.get(roomId),\n subscribe: (listener) => {\n listeners.add(listener)\n return () => { listeners.delete(listener) }\n },\n start: (quote) => {\n quotes.set(quote.roomId, quote)\n notify()\n // The host has to know too: it is the side that attaches the quote, because the message\n // itself is submitted by the official composer and never passes through this plugin.\n // Fire-and-forget — the bar is already on screen, and a failed registration only means the\n // sent message arrives without the quote.\n void registerQuote(quote.roomId, quote).catch(() => undefined)\n },\n clear: (roomId) => {\n const had = quotes.delete(roomId)\n notify()\n if (had) void clearQuote(roomId).catch(() => undefined)\n },\n clearLocal: (roomId) => {\n if (!quotes.delete(roomId)) return\n notify()\n },\n }\n}\n","/**\n * Left/right placement for group-chat messages.\n *\n * The official Chat renderer draws every user message right-aligned — its own CSS is\n * unconditional (`align-items: flex-end`), with no self/other distinction, because in an\n * ordinary Session the only author is the operator. A room has several authors, so the\n * side has to carry a meaning the official renderer does not compute: this workspace's own\n * messages stay right, everyone else's move left.\n *\n * It is implemented by **wrapping the official renderer, not replacing it**. The official\n * component is read back out of the slot registry and rendered as an element, so markdown,\n * images, file and skill references, per-message actions and the streaming behaviour all\n * stay the official implementation's. Re-drawing the bubble here would have to track every\n * one of those forever.\n *\n * An ordinary Session is untouched: a message with no room source is rendered through the\n * official component with no wrapper at all, which is what keeps the two paths equal in\n * what they draw.\n *\n * Another author's message also carries their name and picture above the bubble. The official\n * renderer draws neither — in an ordinary Session every message is the operator's own, so there\n * is no author to attribute — and the event itself carries only the author's id. The name and\n * picture therefore come from the roster, looked up by that id. When the roster has not loaded,\n * or the author has since left, the message renders bare rather than labelled with an id: a\n * wrong name is worse than no name.\n *\n * @module @sparkelf/dsh-group-chat/client/MessageSide\n */\n\nimport { createElement, useSyncExternalStore, type MouseEvent, type ReactNode } from 'react'\nimport { Avatar } from './Avatar.tsx'\nimport type { RoomMember } from './api.ts'\nimport { previewOf, quoteOf, type MessageLike, type QuotedMessage, type QuoteStore } from './quote.ts'\nimport type { GroupChatKey } from './locales.ts'\nimport styles from './MessageSide.module.css'\n\n\n/**\n * Which side a message belongs on, or `undefined` when it is not a room message.\n *\n * `roomId` is the discriminator: the Host stamps it on every message it admits to a room,\n * and an ordinary Session's message carries `{kind, rpcId, clientTimeZone}` instead. The\n * remaining cases split the room's own authors — an agent is always someone else, and a\n * human is \"self\" only when the id matches this workspace's account. A message whose\n * author cannot be identified is placed left, because assuming \"self\" would silently\n * attribute another member's words to the operator.\n *\n * @param message - the message data the node carries.\n * @param selfId - this workspace's own account id, when known.\n * @returns `'self'`, `'other'`, or `undefined` for a non-room message.\n */\nexport const messageSide = (message: MessageLike | undefined, selfId: string | undefined): 'self' | 'other' | undefined => {\n const source = message?.source\n if (source === undefined || source === null) return undefined\n if (typeof source.roomId !== 'string' || source.roomId.length === 0) return undefined\n if (source.authorKind === 'agent') return 'other'\n return selfId !== undefined && source.memberId === selfId ? 'self' : 'other'\n}\n\n/**\n * The roster entry for one message's author, when the room knows it.\n *\n * The bubble needs the name and the picture, and neither is on the event: a message carries only\n * `source.memberId`. The roster is the only place those two facts live, so this is the lookup\n * that lets another author's message say who wrote it.\n *\n * @param message - the message data the node carries.\n * @param roster - that room's members, or `undefined` before the roster has been read.\n * @returns the matching member, or `undefined` when the author is unknown or has left.\n */\nexport const authorOf = (\n message: MessageLike | undefined,\n roster: readonly RoomMember[] | undefined,\n): RoomMember | undefined => {\n const memberId = message?.source?.memberId\n if (typeof memberId !== 'string' || memberId.length === 0) return undefined\n return roster?.find((member) => member.userId === memberId)\n}\n\n/**\n * The name to draw above another author's message.\n *\n * Falls back to the roster's display name only, never to the id: an unlabelled bubble is\n * readable, but a bubble labelled `u_7f3a91` is not, and it would also leak an internal id into\n * the conversation.\n *\n * @param member - the author's roster entry.\n * @returns the name, or `undefined` when the roster has no usable one.\n */\nexport const authorNameOf = (member: RoomMember | undefined): string | undefined => {\n const name = member?.displayName?.trim() ?? ''\n return name.length === 0 ? undefined : name\n}\n\n/** The official node view, as the registry hands it over. */\ntype OfficialView = (props: Record<string, unknown>) => ReactNode\n\n/** Props this wrapper adds to whatever the official node view receives. */\nexport interface MessageSideProps {\n readonly node?: { readonly data?: unknown }\n /** The official node view, read from the slot registry. */\n readonly Official: unknown\n /** This workspace's own account id, when known — read as a snapshot. */\n readonly getSelfId: () => string | undefined\n /** Notified when the account id arrives, so the side is re-decided. */\n readonly subscribeSelf: (listener: () => void) => () => void\n /** Whether this occurrence is the keyed `user` cell, which is the only one that has sides. */\n readonly sided: boolean\n /**\n * Read one room's roster as a snapshot, or `undefined` before it has been read.\n *\n * A function rather than the array itself, for the same reason [getSelfId] is: the roster is\n * read after the first paint, so a value captured when this renderer was registered would stay\n * `undefined` and every bubble would render unlabelled forever.\n */\n readonly getRoster: (roomId: string) => readonly RoomMember[] | undefined\n /**\n * Where a reply started on this message is recorded.\n *\n * The message is the only place that knows which row the reader pointed at, so the button has\n * to live here rather than on the composer: a composer-side control would have to ask \"which\n * message?\" and answer it from a hover that the composer never sees.\n */\n readonly quotes?: QuoteStore\n /**\n * Copy resolver for this plugin's own strings.\n *\n * Kept separate from the `t` the seat supplies: that one is bound to the official `chat`\n * namespace, because the official bubble looks its own copy up there and would print raw keys\n * if this plugin replaced it. Our keys live in this plugin's namespace, so they arrive under\n * their own name.\n */\n readonly tOwn?: (key: GroupChatKey) => string\n}\n\n/**\n * Render one node with its room side applied.\n *\n * Two details this has to get right, both of which fail silently otherwise:\n *\n * The official view is created as a React **element**, never called as a plain function. It\n * is a `React.memo` component, which compiles to a non-callable object, so calling it throws\n * `TypeError: … is not a function`; and even when a view is callable, calling it directly\n * would run its Hooks as this component's and drop its memoization.\n *\n * The account id is read through `useSyncExternalStore` rather than captured. It is fetched\n * after the first paint, and a value captured when this renderer was registered would stay\n * `undefined` for the life of the page — every message would keep the unknown-author\n * placement, and nothing would ever correct it. The store is the same observable the room\n * list uses, and the snapshot is a plain string, so an unrelated room-list change notifies\n * without re-rendering: React compares the snapshot by identity.\n *\n * @param props - the official props plus this wrapper's placement inputs.\n * @returns the official view, wrapped only when the message is another author's room message.\n */\n/**\n * The bar above a bubble showing which message it answers.\n *\n * Drawn by this plugin rather than by the official view: the quote travels as a content block\n * whose type the official renderer does not know, so it would otherwise be dropped. The bar sits\n * outside the bubble so the reply's own text keeps the official layout.\n *\n * @param props - the quote to show.\n * @returns the bar, or `null` when there is nothing worth quoting.\n */\nexport const QuoteBar = ({ quote }: { readonly quote: QuotedMessage }): ReactNode => {\n if (quote.preview.length === 0) return null\n return (\n <div className={styles['quoteBar']} data-group-chat-quote={quote.messageId}>\n <span className={styles['quoteMark']} aria-hidden=\"true\" />\n <span className={styles['quoteBody']}>\n {quote.authorName === undefined ? null : (\n <span className={styles['quoteAuthor']}>{quote.authorName}</span>\n )}\n <span className={styles['quoteText']}>{quote.preview}</span>\n </span>\n </div>\n )\n}\n\n/**\n * The control that starts a reply to one message.\n *\n * Rendered inside the wrapper this plugin already draws around another author's row, which is\n * the only place the message id is known. It reads its own hover state so the button appears\n * over the row it belongs to and nowhere else.\n *\n * @param props - the message it replies to, and the store the pick lands in.\n * @returns the button, or `null` when the message cannot be replied to.\n */\n/** Source kind of the model-only quoted-message context; it must never be drawn as a message. */\nconst QUOTE_CONTEXT_KIND = 'group-chat-quote'\n\nconst ReplyControl = (props: {\n readonly roomId: string\n readonly messageId: string\n readonly authorName?: string\n readonly preview: string\n readonly quotes: QuoteStore\n readonly t: (key: GroupChatKey) => string\n}): ReactNode => {\n const { roomId, messageId, authorName, preview, quotes, t } = props\n if (preview.length === 0) return null\n return (\n <button\n type=\"button\"\n className={styles['replyButton']}\n data-group-chat-reply=\"\"\n aria-label={t('reply')}\n title={t('reply')}\n onClick={() => {\n quotes.start({ roomId, messageId, preview, ...authorName === undefined ? {} : { authorName } })\n }}\n >\n {/* Inline rather than an icon from the primitives package: importing that package pulls its\n CSS into every module that renders a bubble, and the plain-function tests import this\n module without a bundler. The shape is a corner-turning arrow, the usual \"answers that\n message\" mark. */}\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M6 4 2.5 7.5 6 11\" />\n <path d=\"M2.5 7.5h7A3.5 3.5 0 0 1 13 11v1.5\" />\n </svg>\n </button>\n )\n}\n\nexport const MessageSide = (props: MessageSideProps): ReactNode => {\n const { node, Official, getSelfId, subscribeSelf, sided, getRoster, quotes } = props\n // One subscription drives both lookups: identity and roster arrive from separate requests, so\n // subscribing twice would re-render on the same change and could draw the author before the\n // name is known.\n const selfId = useSyncExternalStore(subscribeSelf, getSelfId, getSelfId)\n if (typeof Official !== 'function' && typeof Official !== 'object') return null\n // The official props ride through unchanged apart from this plugin's own additions, which a\n // React component ignores. `t` in particular must survive: the seat supplies it from the\n // namespace this plugin's registration declares.\n const view = createElement(Official as OfficialView, props as unknown as Record<string, unknown>)\n const message = node?.data as MessageLike | undefined\n // The quoted-message context the host injects before a model step is background for the\n // model, not part of the conversation. It carries its own source kind so it is recognizable\n // here; drawing it would put an untrusted-background snapshot in the room under the member's\n // name, and the message it belongs to already shows the quote as a pointer.\n if (message?.source?.kind === QUOTE_CONTEXT_KIND) return null\n const side = sided ? messageSide(message, selfId) : undefined\n const quote = quoteOf(message)\n const roomId = message?.source?.roomId\n const roomKnown = typeof roomId === 'string' && roomId.length > 0\n // The id the room's log knows this message by. `source.messageId` is that row; `id` is the\n // message's own uuid. A reply and a search jump both point at the row — it is what the room\n // stores and what a reply carries — so it is resolved once here, before any branch uses it.\n const sourceMessageId = message?.source?.messageId\n const rawId = message?.id\n const messageId = typeof sourceMessageId === 'string' && sourceMessageId.length > 0\n ? sourceMessageId\n : typeof rawId === 'string' && rawId.length > 0 ? rawId : undefined\n // A message outside a room has no roster to attribute it from and no room to reply into, so it\n // stays exactly as the official renderer drew it. A quote can still be present on a message\n // whose room id went missing; the bar is shown rather than dropped, because losing it would\n // make a reply look like it answered nothing.\n if (side === undefined || !roomKnown) {\n if (quote === undefined) return view\n return (\n <div\n className={styles['selfRow']}\n data-group-chat-side=\"self\"\n {...messageId === undefined ? {} : { 'data-group-chat-message': messageId }}\n >\n <QuoteBar quote={quote} />\n {view}\n </div>\n )\n }\n\n const member = authorOf(message, getRoster(roomId))\n const name = authorNameOf(member)\n // The avatar and the name are drawn only when the room knows this author. An unknown author\n // still moves left — the side is decided from a fact the message carries — but is left\n // unlabelled rather than attributed to a guessed name or an internal id.\n const head = name === undefined || member === undefined\n ? null\n : (\n <div className={styles['author']} data-group-chat-author={member.userId}>\n <Avatar name={name} src={member.avatarUrl} size=\"sm\" />\n <span className={styles['authorName']}>{name}</span>\n </div>\n )\n\n // The host prefixes what it publishes with the speaker's name so the model can tell who is\n // talking. That prefix is part of the stored text, so a quote taken from it would read\n // \"DataOps Admin:DataOps Admin:…\" once the bar draws the name itself.\n const rawPreview = previewOf(message)\n const preview = name === undefined\n ? rawPreview\n : rawPreview.replace(new RegExp(`^${name.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\\\s*[::]\\\\s*`), '')\n // Both halves are required: an id with no text has nothing to quote, and a message with no id\n // cannot be pointed at. Offering the control in either case would store a quote nobody can\n // resolve.\n const replying = quotes !== undefined && messageId !== undefined && preview.length > 0\n\n const actions = replying ? (\n <div className={side === 'self' ? styles['rowActionsSelf'] : styles['rowActions']}>\n <ReplyControl\n roomId={roomId}\n messageId={messageId}\n {...name === undefined ? {} : { authorName: name }}\n preview={preview}\n quotes={quotes}\n t={props.tOwn ?? ((key) => key)}\n />\n </div>\n ) : null\n\n // A message I wrote keeps the official right alignment; the reply control sits on its left,\n // where the bubble has room and where the pointer arrives from.\n if (side === 'self') {\n return (\n <div\n className={styles['selfRow']}\n data-group-chat-side=\"self\"\n {...messageId === undefined ? {} : { 'data-group-chat-message': messageId }}\n >\n {quote === undefined ? null : <QuoteBar quote={quote} />}\n <div className={styles['selfBody']}>\n {view}\n {actions}\n </div>\n </div>\n )\n }\n\n return (\n <div\n className={styles['otherRow']}\n data-group-chat-side=\"other\"\n {...messageId === undefined ? {} : { 'data-group-chat-message': messageId }}\n >\n {head}\n {quote === undefined ? null : <QuoteBar quote={quote} />}\n <div className={styles['rowBody']}>\n {view}\n {actions}\n </div>\n </div>\n )\n}\n","/**\n * The room/session binding, shared by both halves of the plugin.\n *\n * The browser half needs it to open a room's Session; the host half needs it to create and\n * recognize one. It lives in its own module because the two halves are built for different\n * runtimes: importing it from the host entry would drag the whole server side into the client\n * bundle, which the build's platform guard refuses — correctly, since none of it can run there.\n *\n * @module @sparkelf/dsh-group-chat/session-id\n */\n\n/**\n * Every room session id starts with this, so a room's session is recognizable after a restart\n * and an ordinary session never matches.\n *\n * The binding lives in the id rather than in `SessionHeader` because the header carries no\n * free-form metadata field.\n */\nexport const ROOM_SESSION_PREFIX = 'group-room-'\n\n/** Deterministic session id for a room. */\nexport const roomSessionId = (roomId: string): string => `${ROOM_SESSION_PREFIX}${roomId}`\n\n/**\n * Reads the room a session *id* is bound to, or `undefined` for an ordinary session.\n *\n * Separate from {@link roomIdOfSession} because the two call sites receive different things: a\n * seat hands the composer a bare id string, while a session event hands over the session object.\n * Passing a string to the object form silently returns `undefined` — it checks for an object\n * first — which is how a correctly registered composer seat ended up drawing nothing.\n */\nexport const roomIdOfSessionId = (sessionId: string): string | undefined => {\n if (!sessionId.startsWith(ROOM_SESSION_PREFIX)) return undefined\n const roomId = sessionId.slice(ROOM_SESSION_PREFIX.length)\n return roomId.length > 0 ? roomId : undefined\n}\n\n/** Reads the room a session is bound to, or `undefined` for an ordinary session. */\nexport const roomIdOfSession = (session: unknown): string | undefined => {\n if (typeof session !== 'object' || session === null) return undefined\n const id = (session as { id?: unknown }).id\n if (typeof id !== 'string' || !id.startsWith(ROOM_SESSION_PREFIX)) return undefined\n const roomId = id.slice(ROOM_SESSION_PREFIX.length)\n return roomId.length > 0 ? roomId : undefined\n}\n","/**\n * The members panel, shown in the right sidebar beside a room's conversation.\n *\n * A room is a Session, so the shell already draws its messages; what the shell has no reason to\n * know about is the roster behind them. This panel is that missing view: who is in the room, who\n * owns it, and the one message every member is meant to see.\n *\n * It is a **tab in the official right sidebar**, not a dialog. A roster is something a reader\n * consults while reading — opening it must not cover the conversation it describes — and the\n * shell already owns tab placement, width, persistence and the ability to put it side by side\n * with the document preview. Drawing our own overlay would have to track all of that.\n *\n * What the panel shows comes from the shared store rather than its own requests: the sidebar\n * already reads the roster, and a second reader would let the two disagree about who is present.\n * The one exception is writing the announcement, which no other surface can do.\n *\n * @module @sparkelf/dsh-group-chat/client/MembersPanel\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { Button, Input } from '@deepseek-ai/dsh-client-ui-primitives'\nimport {\n announcementMissing,\n announcementView,\n filterMemberRows,\n memberRows,\n type MemberRow,\n} from './rows.ts'\nimport { getRoomFile, listRoomFiles, setAnnouncement, uploadRoomFile, type RoomFiles, type SharedFileText } from './api.ts'\nimport { Avatar } from './Avatar.tsx'\nimport { roomIdOfSession } from '../session-id.ts'\nimport type { GroupChatKey } from './locales.ts'\nimport type { RoomStore } from './store.ts'\nimport styles from './MembersPanel.module.css'\n\n/** The tab type this panel is opened as. */\nexport const MEMBERS_TAB_KIND = 'groupChatMembers'\n\n/**\n * Props the panel reads.\n *\n * `sessionId` comes from the tab seat rather than from navigation parameters: the sidebar\n * persists a tab's place but not the parameters its opener passed, so a reload hands this\n * component a tab with no params at all. Deriving the room from the Session it belongs to is\n * what keeps the panel correct across a reload, and it also decides whether the panel belongs\n * here at all — an ordinary Session has no room, and therefore no roster.\n */\nexport interface MembersPanelInjected {\n /** The Session this tab was opened beside. */\n readonly sessionId: string\n /** Localized copy. */\n readonly t: (key: GroupChatKey) => string\n /** The shared room store, which already holds the roster. */\n readonly store: RoomStore\n}\n\n/**\n * Render the roster and announcement for the room this Session belongs to.\n *\n * @param props - the Session, copy, and shared store.\n * @returns the panel, or a short notice when this Session is not a room.\n */\nexport function MembersPanel(props: MembersPanelInjected): ReactNode {\n const { sessionId, t, store } = props\n const roomId = roomIdOfSession({ id: sessionId })\n const state = useRoomState(store)\n\n // Re-read the roster when the panel opens. The sidebar loads rosters for the rooms it lists,\n // but a reload that lands directly on a room's Session may paint this panel before that read\n // completes, and an empty roster would read as \"nobody is here\".\n useEffect(() => {\n if (roomId !== undefined) void store.loadMembers(roomId)\n }, [roomId, store])\n\n const members = roomId === undefined ? undefined : state.members[roomId]\n const rows = useMemo(\n () => (members === undefined ? undefined : memberRows(members, state.selfId)),\n [members, state.selfId],\n )\n const room = state.rooms?.find((candidate) => candidate.id === roomId)\n\n if (roomId === undefined) {\n return <p className={styles['notice']}>{t('membersNotARoom')}</p>\n }\n\n return (\n <div className={styles['root']}>\n <Announcement\n t={t}\n roomId={roomId}\n announcement={room?.announcement}\n canEdit={room !== undefined && room.ownerUserId === state.selfId}\n onSaved={() => { void store.refresh() }}\n />\n <SharedFiles t={t} roomId={roomId} />\n <Roster\n t={t}\n rows={rows}\n // Only the owner may remove another member: the server enforces the same rule, so\n // hiding the control elsewhere keeps the roster from offering an action that will be\n // refused. The owner's own row never shows it — they cannot remove themselves.\n canRemove={room !== undefined && room.ownerUserId === state.selfId}\n onRemove={(userId) => { void store.removeMember(roomId, userId) }}\n />\n </div>\n )\n}\n\n/**\n * Subscribe to the shared store.\n *\n * Written as a hook rather than read inline so a roster that arrives after the first paint\n * re-renders the panel: the store is a plain observable, not a framework store.\n *\n * @param store - the shared room store.\n * @returns its current state, re-read on every change.\n */\nfunction useRoomState(store: RoomStore): ReturnType<RoomStore['getState']> {\n const [snapshot, setSnapshot] = useState(() => store.getState())\n useEffect(() => {\n setSnapshot(store.getState())\n return store.subscribe(() => { setSnapshot(store.getState()) })\n }, [store])\n return snapshot\n}\n\n/** Props for the announcement area. */\ninterface AnnouncementProps {\n readonly t: (key: GroupChatKey) => string\n readonly roomId: string\n /** The room's announcement, or `undefined` when this deployment has no such field. */\n readonly announcement: string | null | undefined\n /** Whether the operator may write it. Only the owner may, and the server enforces it. */\n readonly canEdit: boolean\n /** Called after a successful save so the room list re-reads the stored value. */\n readonly onSaved: () => void\n}\n\n/**\n * The announcement area: read it, and for the owner, write it.\n *\n * Saving re-reads the room list instead of patching local state. The server stores a cleared\n * announcement as `null`, so a local patch would leave this panel and the sidebar showing\n * different spellings of the same absence.\n *\n * @param props - the text, the permission, and how to report a save.\n * @returns the section.\n */\nfunction Announcement(props: AnnouncementProps): ReactNode {\n const { t, roomId, announcement, canEdit, onSaved } = props\n const view = announcementView(announcement)\n const [editing, setEditing] = useState(false)\n const [draft, setDraft] = useState('')\n const [saving, setSaving] = useState(false)\n const [missing, setMissing] = useState(false)\n const [failure, setFailure] = useState<GroupChatKey | undefined>(undefined)\n\n const begin = useCallback((): void => {\n setDraft(view.state === 'text' ? view.text : '')\n setFailure(undefined)\n setEditing(true)\n }, [view])\n\n const save = useCallback(async (): Promise<void> => {\n setSaving(true)\n setFailure(undefined)\n try {\n await setAnnouncement(roomId, draft.trim())\n setEditing(false)\n onSaved()\n } catch (error) {\n // A deployment without the feature answers 404/405/501. That is a different fact from a\n // refused or failed write, and confusing the two would send the operator looking for a\n // network problem when the workspace simply predates the feature.\n if (announcementMissing(error)) setMissing(true)\n else setFailure('announcementFailed')\n } finally {\n setSaving(false)\n }\n }, [draft, onSaved, roomId])\n\n return (\n <CollapsibleSection\n t={t}\n title={t('announcementTitle')}\n actions={canEdit && !editing && !missing && view.state !== 'unavailable' ? (\n <button type=\"button\" className={styles['linkButton']} onClick={begin}>\n {view.state === 'empty' ? t('announcementAdd') : t('announcementEdit')}\n </button>\n ) : undefined}\n >\n {missing || view.state === 'unavailable' ? (\n <p className={styles['unavailable']}>{t('announcementUnavailable')}</p>\n ) : editing ? (\n <div className={styles['editor']}>\n <textarea\n className={styles['textarea']}\n value={draft}\n placeholder={t('announcementPlaceholder')}\n onChange={(event) => { setDraft(event.target.value) }}\n onKeyDown={(event) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n setEditing(false)\n return\n }\n // Enter saves and Shift+Enter breaks the line: an announcement is a short notice,\n // and a text area with no save key reads as a form the operator cannot submit.\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault()\n void save()\n }\n }}\n rows={4}\n />\n <div className={styles['editorFooter']}>\n {saving\n ? <span className={styles['hint']}>{t('announcementSaving')}</span>\n : <span className={styles['hint']}>{t('announcementCleared')}</span>}\n <Button size=\"sm\" onClick={() => { setEditing(false) }}>{t('cancel')}</Button>\n <Button size=\"sm\" variant=\"primary\" onClick={() => void save()} disabled={saving}>\n {t('save')}\n </Button>\n </div>\n </div>\n ) : view.state === 'text' ? (\n <p className={styles['announcementText']}>{view.text}</p>\n ) : (\n // One line, not two: \"no announcement yet\" and \"only the owner can edit\" say the same\n // thing twice when the reader is a plain member, and the second alone is what they can\n // act on. The owner-only note is therefore folded into the empty state instead of\n // stacked beneath it.\n <p className={styles['unavailable']}>\n {canEdit ? t('announcementEmpty') : t('announcementOwnerOnly')}\n </p>\n )}\n\n {failure !== undefined && <p className={styles['error']}>{t(failure)}</p>}\n </CollapsibleSection>\n )\n}\n\n/**\n * A collapsible section header.\n *\n * The right sidebar can be dragged narrow, and this panel stacks three sections in it. A\n * section that stays open at 200px squeezes the roster until names collide, so each section\n * can collapse to its title — the title row is the thinnest thing that still tells a member\n * what exists below. Collapsed state is local: a member who folded \"shared files\" to make\n * room for the roster is describing what they are looking at now, not a preference to\n * persist across rooms.\n *\n * @param props - visible title, optional count, and the body to hide.\n * @returns the section.\n */\nfunction CollapsibleSection(props: {\n title: string\n count?: number\n defaultOpen?: boolean\n actions?: ReactNode\n t: (key: GroupChatKey) => string\n children: ReactNode\n}): ReactNode {\n const { title, count, defaultOpen = true, actions, t, children } = props\n const [open, setOpen] = useState(defaultOpen)\n return (\n <section className={styles['section']}>\n <div className={styles['sectionHeader']}>\n <button\n type=\"button\"\n className={styles['sectionToggle']}\n aria-expanded={open}\n // The label is the disclosure control: a separate caret button would put two targets\n // in a 200px row and leave neither big enough to hit.\n onClick={() => { setOpen((value) => !value) }}\n >\n <span className={styles['caret']} aria-hidden=\"true\">{open ? '\\u25BE' : '\\u25B8'}</span>\n <span className={styles['sectionLabel']}>{title}</span>\n </button>\n {count !== undefined && count > 0 && <span className={styles['count']}>{count}</span>}\n {actions}\n </div>\n {open && <div className={styles['sectionBody']}>{children}</div>}\n </section>\n )\n}\n\n/**\n * The room's shared folder, as the members panel shows it.\n *\n * Read on demand rather than kept in the shared store: the folder changes when someone\n * uploads or when the room's agent writes a file, and a panel that only refreshes on room\n * switch would show a stale list for the whole time it is open. Re-reading each time the\n * panel opens keeps the list honest without a poll of its own.\n *\n * @param props - the room and copy.\n * @returns the section.\n */\nfunction SharedFiles(props: { t: (key: GroupChatKey) => string; roomId: string }): ReactNode {\n const { t, roomId } = props\n const [state, setState] = useState<RoomFiles | undefined>(undefined)\n const [preview, setPreview] = useState<SharedFileText | undefined>(undefined)\n const [previewFailure, setPreviewFailure] = useState<string | undefined>(undefined)\n const [uploading, setUploading] = useState(false)\n const [uploadFailure, setUploadFailure] = useState<string | undefined>(undefined)\n const [reloadKey, setReloadKey] = useState(0)\n const [subPath, setSubPath] = useState('')\n const fileInput = useRef<HTMLInputElement>(null)\n\n useEffect(() => {\n let live = true\n void listRoomFiles(roomId, subPath)\n .then((next) => { if (live) setState(next) })\n // A failed read is not an empty folder: showing \"no files\" would misreport a broken\n // mount as an empty one, so the section says so plainly instead.\n .catch(() => { if (live) setState({ path: null, files: [] }) })\n return () => { live = false }\n }, [roomId, reloadKey, subPath])\n\n if (state === undefined) return null\n // Breadcrumbs come from the server rather than being rebuilt here: it already resolved the\n // path, and a second resolver would disagree with it the first time either changed.\n const crumbs = state.crumbs ?? []\n // No shared tree on this deployment: the section would only ever say \"unavailable\", which\n // is noise for a member who never had the feature.\n if (state.path === null) return null\n\n const openPreview = (path: string): void => {\n setPreviewFailure(undefined)\n void getRoomFile(roomId, path)\n .then((next) => { setPreview(next) })\n .catch(() => { setPreviewFailure(t('membersFileUnreadable')) })\n }\n\n const upload = (picked: File): void => {\n setUploading(true)\n setUploadFailure(undefined)\n void uploadRoomFile(roomId, picked)\n .then(() => { setReloadKey((value) => value + 1) })\n .catch(() => { setUploadFailure(t('membersFileUploadFailed')) })\n .finally(() => {\n setUploading(false)\n // Reset so picking the same file twice still fires a change event.\n if (fileInput.current !== null) fileInput.current.value = ''\n })\n }\n\n return (\n <CollapsibleSection\n t={t}\n title={t('membersFilesTitle')}\n count={state.files.length}\n actions={(\n <>\n {/* A hidden input, opened by the button: the native file dialog is the only picker\n that can hand back a File without a second permission step. */}\n <input\n ref={fileInput}\n type=\"file\"\n className={styles['filePicker']}\n aria-label={t('membersFileUpload')}\n onChange={(event) => {\n const picked = event.target.files?.[0]\n if (picked !== undefined) upload(picked)\n }}\n />\n <button\n type=\"button\"\n className={styles['refresh']}\n aria-label={t('membersFileUpload')}\n title={t('membersFileUpload')}\n disabled={uploading}\n onClick={() => { fileInput.current?.click() }}\n >\n {uploading ? '\\u22EF' : '+'}\n </button>\n {/* The folder changes when the room's agent writes a file, and this panel can stay\n open for a long time; a manual refresh is cheaper than polling and honest about\n when the list is from. */}\n <button\n type=\"button\"\n className={styles['refresh']}\n aria-label={t('membersFilesRefresh')}\n title={t('membersFilesRefresh')}\n onClick={() => { setReloadKey((value) => value + 1) }}\n >\n {'\\u21BB'}\n </button>\n </>\n )}\n >\n {crumbs.length > 0 && (\n <nav className={styles['crumbs']} aria-label={t('membersFilesTitle')}>\n <button type=\"button\" className={styles['crumb']} onClick={() => { setSubPath('') }}>\n {t('membersFilesRoot')}\n </button>\n {crumbs.map((crumb) => (\n <span key={crumb.path} className={styles['crumbGroup']}>\n <span className={styles['crumbSep']} aria-hidden=\"true\">/</span>\n <button\n type=\"button\"\n className={styles['crumb']}\n onClick={() => { setSubPath(crumb.path) }}\n >\n {crumb.name}\n </button>\n </span>\n ))}\n </nav>\n )}\n {state.files.length === 0 ? (\n <p className={styles['hint']}>{t('membersFilesEmpty')}</p>\n ) : (\n <ul className={styles['list']}>\n {state.files.map((file) => (\n <li key={file.path} className={styles['row']}>\n {file.directory ? (\n <button\n type=\"button\"\n className={styles['fileButton']}\n onClick={() => { setSubPath(joinPath(subPath, file.path)) }}\n title={file.path}\n >\n <span className={styles['fileIcon']} aria-hidden=\"true\">{'\\u{1F4C1}'}</span>\n <span className={styles['name']}>{file.name}</span>\n </button>\n ) : (\n <button\n type=\"button\"\n className={styles['fileButton']}\n onClick={() => { openPreview(joinPath(subPath, file.path)) }}\n title={file.path}\n >\n <span className={styles['fileIcon']} aria-hidden=\"true\">{'\\u{1F4C4}'}</span>\n <span className={styles['name']}>{file.name}</span>\n <span className={styles['fileSize']}>{formatBytes(file.size)}</span>\n </button>\n )}\n </li>\n ))}\n </ul>\n )}\n {previewFailure !== undefined && <p className={styles['hint']}>{previewFailure}</p>}\n {uploadFailure !== undefined && <p className={styles['error']}>{uploadFailure}</p>}\n {preview !== undefined && (\n <section className={styles['preview']} aria-label={preview.name}>\n <div className={styles['previewHeader']}>\n <span className={styles['previewTitle']} title={preview.path}>{preview.name}</span>\n <button\n type=\"button\"\n className={styles['previewClose']}\n aria-label={t('membersFileClose')}\n onClick={() => { setPreview(undefined) }}\n >\n {'\\u00D7'}\n </button>\n </div>\n {preview.image !== undefined ? (\n <img className={styles['previewImage']} src={preview.image} alt={preview.name} />\n ) : preview.textLike ? (\n <>\n <pre className={styles['previewBody']}>{preview.text}</pre>\n {preview.truncated && <p className={styles['hint']}>{t('membersFileTruncated')}</p>}\n </>\n ) : (\n <p className={styles['hint']}>{t('membersFileBinary')}</p>\n )}\n </section>\n )}\n </CollapsibleSection>\n )\n}\n\n/** Joins a folder-relative path onto the folder currently shown. */\nconst joinPath = (prefix: string, name: string): string =>\n prefix === '' ? name : `${prefix}/${name}`\n\n/** Bytes as a short human string; the panel has one column, not a table. */\nconst formatBytes = (bytes: number): string => {\n if (bytes < 1024) return `${String(bytes)} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\n/** Props for the roster. */\ninterface RosterProps {\n readonly t: (key: GroupChatKey) => string\n readonly rows: readonly MemberRow[] | undefined\n /** Whether this reader may remove another member; the owner's own row is excluded too. */\n readonly canRemove: boolean\n /** Remove one member by account id. */\n readonly onRemove: (userId: string) => void\n}\n\n/**\n * The roster: a search box and one row per member.\n *\n * The search box appears only once the roster is long enough that scanning is slower than\n * typing. Below that it would be a control that cannot help.\n *\n * @param props - the rows and copy.\n * @returns the section.\n */\nfunction Roster(props: RosterProps): ReactNode {\n const { t, rows, canRemove, onRemove } = props\n const [query, setQuery] = useState('')\n // The field starts folded away: a roster of a handful of names reads faster than a control row,\n // and the header already carries the section's actions at the same weight. The button is the way\n // in; while the field is open the same button closes it and clears the query.\n const [searching, setSearching] = useState(false)\n const visible = useMemo(\n () => (rows === undefined ? undefined : filterMemberRows(rows, query)),\n [rows, query],\n )\n const closeSearch = (): void => { setSearching(false); setQuery('') }\n\n return (\n <CollapsibleSection\n t={t}\n title={t('membersRosterTitle')}\n count={rows?.length}\n actions={(\n <button\n type=\"button\"\n className={styles['searchToggle']}\n aria-label={searching ? t('membersSearchClear') : t('membersSearchOpen')}\n aria-expanded={searching}\n title={searching ? t('membersSearchClear') : t('membersSearchOpen')}\n onClick={() => { if (searching) closeSearch(); else setSearching(true) }}\n >\n {searching ? (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n strokeWidth=\"1.6\" strokeLinecap=\"round\" aria-hidden=\"true\">\n <path d=\"M4 4l8 8M12 4l-8 8\" />\n </svg>\n ) : (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n strokeWidth=\"1.6\" strokeLinecap=\"round\" aria-hidden=\"true\">\n <circle cx=\"7\" cy=\"7\" r=\"4.5\" />\n <path d=\"M10.5 10.5L14 14\" />\n </svg>\n )}\n </button>\n )}\n >\n\n {searching && rows !== undefined && (\n <div className={styles['search']}>\n <Input\n value={query}\n // Opened by an explicit action, so it takes focus; the reader asked for it and would\n // otherwise have to reach for it a second time.\n autoFocus\n placeholder={t('membersSearchPlaceholder')}\n aria-label={t('membersSearch')}\n onChange={(event) => { setQuery(event.target.value) }}\n />\n </div>\n )}\n\n {visible === undefined ? (\n <p className={styles['hint']}>{t('loading')}</p>\n ) : visible.length === 0 ? (\n <p className={styles['hint']}>\n {rows !== undefined && rows.length > 0 ? t('membersSearchEmpty') : t('membersEmpty')}\n </p>\n ) : (\n <ul className={styles['list']}>\n {visible.map((row) => (\n <li key={row.userId} className={styles['row']}>\n <Avatar name={row.name} src={row.avatarUrl} size=\"md\" />\n <span className={styles['name']} title={row.name}>{row.name}</span>\n {row.self && <span className={styles['selfTag']}>{t('membersSelf')}</span>}\n {row.owner && <span className={styles['ownerTag']}>{t('membersOwner')}</span>}\n {canRemove && !row.self && (\n <button\n type=\"button\"\n className={styles['remove']}\n title={t('removeMember')}\n aria-label={t('removeMember')}\n onClick={() => { onRemove(row.userId) }}\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\"\n strokeWidth=\"1.6\" strokeLinecap=\"round\" aria-hidden=\"true\">\n <path d=\"M3 4.5h10M6.5 4.5V3h3v1.5M5 4.5l.6 8a1 1 0 0 0 1 1h2.8a1 1 0 0 0 1-1l.6-8\" />\n </svg>\n </button>\n )}\n </li>\n ))}\n </ul>\n )}\n </CollapsibleSection>\n )\n}\n\n/**\n * The chip that labels the members tab in the sidebar's tab strip.\n *\n * The sidebar calls this separately from the body so the strip can render without mounting every\n * placed tab; it therefore needs the label alone, and none of the body's data.\n *\n * @param props - the localized copy.\n * @returns the tab's label.\n */\nexport function MembersTabTitle(props: { readonly t: (key: GroupChatKey) => string }): ReactNode {\n return props.t('membersTitle')\n}\n","/**\n * The members page's box on the right sidebar's start guide, drawn only while a room is open.\n *\n * The start guide lists every registered page type so a reader can open one. Its list is\n * global — the registry builds it from every registration and offers no per-Session filter — so\n * a type that appears there is offered in every conversation, including ordinary ones that have\n * no room behind them. Picking it there would open a panel that can only say \"not a group chat\",\n * which is a dead end for the reader and noise in the guide.\n *\n * The guide renders each box through its own entry seat, keyed by the contributing type's id, so\n * a type may replace its own box. This draws the box while a room is open and nothing otherwise.\n * The cell wrapping it is a fixed-width, auto-height container with no border or background, so\n * an empty box leaves no gap.\n *\n * Rendering `null` rather than unregistering the type is deliberate: the guide is consulted once\n * per registration change, not per navigation, so a registration toggled by the current Session\n * would either be stale by the time the guide renders or churn on every room switch. The type\n * also has to stay registered for a placed tab to survive a reload.\n *\n * @module @sparkelf/dsh-group-chat/client/GuideEntry\n */\n\nimport type { ReactNode } from 'react'\nimport { roomIdOfSession } from '../session-id.ts'\nimport styles from './GuideEntry.module.css'\n\n/**\n * What the guide passes to one entry box.\n *\n * `sessionId` is the guide's own scope: its entry seat is declared `scope: 'session'`, so the\n * framework supplies the Session the guide is drawing beside. `entryId` and `kind` identify which\n * box is being drawn, and the resolved title and description are the guide's own copy — a\n * replacement re-renders them rather than deriving them again.\n */\nexport interface MembersGuideEntryProps {\n /** The contributing entry's id, as registered in this type's `guide` array. */\n readonly entryId: string\n /** The page type this box opens. */\n readonly kind: string\n /** The box's title, already localized by the guide. */\n readonly title: string\n /** The one-line description, present only while the guide lists few enough boxes. */\n readonly description?: string\n /** The Session this guide is drawing beside. */\n readonly sessionId: string\n /** Reveal this page type; supplied by the plugin, since a box's own opener is not passed in. */\n readonly onPick?: () => void\n}\n\n/**\n * Draw the members box, or nothing when the open conversation is not a room.\n *\n * @param props - the box's copy, its Session, and how to open the page.\n * @returns the box, or `null` outside a room.\n */\nexport function MembersGuideEntry(props: MembersGuideEntryProps): ReactNode {\n const { title, description, sessionId, onPick } = props\n // An ordinary conversation has no room, and this page has nothing to show in one. Drawing\n // nothing is what keeps the box off the guide; the wrapping cell collapses to no height.\n if (roomIdOfSession({ id: sessionId }) === undefined) return null\n\n return (\n <button\n type=\"button\"\n className={styles['entry']}\n data-group-chat-guide-entry={props.entryId}\n onClick={() => { onPick?.() }}\n >\n <span className={styles['icon']} aria-hidden=\"true\">\n {/* Drawn inline: this package carries no icon set, and one glyph does not justify a\n dependency. The paths are the familiar \"people\" mark, at the guide's own 26px. */}\n <svg width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n strokeWidth=\"1.7\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\" />\n <circle cx=\"9\" cy=\"7\" r=\"4\" />\n <path d=\"M22 21v-2a4 4 0 0 0-3-3.87\" />\n <path d=\"M16 3.13a4 4 0 0 1 0 7.75\" />\n </svg>\n </span>\n <span className={styles['text']}>\n <span className={styles['title']}>{title}</span>\n {description !== undefined && <span className={styles['description']}>{description}</span>}\n </span>\n </button>\n )\n}\n","/**\n * The two composer-side pieces of a reply: the button that starts one and the bar that shows\n * the message being answered.\n *\n * Both are seats on official composers, and both are deliberately dumb: they read a pending\n * quote from the store and call back into it. Nothing here sends a message — the quote reaches\n * the room because `MessageSide` attaches it when the reply is submitted, and keeping that\n * decision in one place is why these two do not also try to.\n *\n * @module @sparkelf/dsh-group-chat/client/QuoteComposer\n */\n\nimport { useSyncExternalStore, type ReactNode } from 'react'\nimport { IconShareOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { GroupChatKey } from './locales.ts'\nimport type { QuoteStore } from './quote.ts'\nimport type { RoomStore } from './store.ts'\nimport styles from './QuoteComposer.module.css'\n\n/** The message a reply would point at, once the reader has picked one. */\nexport interface ReplyTarget {\n /** The id the room's event log knows this message by. */\n readonly messageId: string\n /** One line of what it said. */\n readonly preview: string\n /** Who said it, when the roster knows them. */\n readonly authorName?: string\n}\n\n/** Props the reply button seat receives. */\nexport interface ReplyButtonProps {\n readonly t: (key: GroupChatKey) => string\n /** The room this composer is writing into, or `undefined` outside a room. */\n readonly roomId: string | undefined\n readonly quotes: QuoteStore\n /** The message the reader picked, or `undefined` while they have not picked one. */\n readonly target: ReplyTarget | undefined\n}\n\n/**\n * The button that begins a reply.\n *\n * The share glyph, which is what this plugin's icon set offers for \"point at another message\";\n * there is no reply or quote icon, and a hand-rolled SVG would be the only one in the region.\n *\n * Rendered only inside a room with a message under the cursor, because a reply needs a target.\n */\nexport const ReplyButton = (props: ReplyButtonProps): ReactNode => {\n const { t, roomId, quotes, target } = props\n if (roomId === undefined || target === undefined) return null\n return (\n <button\n type=\"button\"\n className={styles.button}\n aria-label={t('reply')}\n title={t('reply')}\n onClick={() => {\n quotes.start({ roomId, ...target })\n }}\n >\n <IconShareOutlineRegular size={14} />\n </button>\n )\n}\n\n/** Props the quote bar seat receives. */\nexport interface QuoteBarProps {\n readonly t: (key: GroupChatKey) => string\n readonly quotes: QuoteStore\n /**\n * The room list store, read only for `activeRoomId`.\n *\n * The composer's seat hands over a session id, but it is not the room session's: the seat the\n * composer renders for is an ordinary `session-*`, so deriving the room from it never resolves.\n * The store already tracks which room the main panel has open, which is the same room the\n * composer is writing into.\n */\n readonly store: RoomStore\n}\n\n/**\n * The bar above the composer showing what the reply answers.\n *\n * Sits in the composer's own stack rather than over the input, so the text the reader types is\n * never covered by it. Absent when nothing is being answered, which is the common case.\n */\nexport const PendingQuoteBar = (props: QuoteBarProps): ReactNode => {\n const { t, quotes, store } = props\n const roomId = useSyncExternalStore(store.subscribe, () => store.getState().activeRoomId, () => store.getState().activeRoomId)\n const read = (): ReturnType<QuoteStore['getQuote']> => roomId === undefined ? undefined : quotes.getQuote(roomId)\n const quote = useSyncExternalStore(quotes.subscribe, read, read)\n if (roomId === undefined || quote === undefined) return null\n return (\n <div className={styles.bar} data-group-chat-pending-quote={quote.messageId}>\n <span className={styles.mark} aria-hidden=\"true\" />\n <span className={styles.body}>\n {quote.authorName === undefined ? null : (\n <span className={styles.author}>{quote.authorName}</span>\n )}\n <span className={styles.text}>{quote.preview}</span>\n </span>\n <button\n type=\"button\"\n className={styles.dismiss}\n aria-label={t('replyCancel')}\n title={t('replyCancel')}\n onClick={() => { quotes.clear(roomId) }}\n >\n ×\n </button>\n </div>\n )\n}\n","/**\n * Room messages as `@` references in the composer.\n *\n * Registering a trigger source is the one supported way a plugin adds something to the `@`\n * menu, and the only way to get an official inline reference: the pipeline turns a pick into\n * an atomic chip, and the codec decides what that chip becomes for the model. Drawing a\n * separate quote bar would mean a second, unofficial representation of the same idea.\n *\n * A chip carries an id; the words are resolved through this module's cache because the codec\n * only ever receives `ref`. The cache is bounded to the messages listed most recently, which\n * is the same window the menu offers.\n *\n * @module @sparkelf/dsh-group-chat/client/messageReference\n */\n\nimport { listEvents } from './api.ts'\nimport type { RoomStore } from './store.ts'\n\n/** Unique source name; duplicate `(trigger, name)` pairs throw at registration. */\nconst SOURCE_NAME = '@sparkelf/dsh-group-chat/message'\n\n/** How many messages the menu offers at once. */\nconst MAX_CANDIDATES = 20\n\n/** How much of a message the menu row and the chip label show. */\nconst LABEL_CHARS = 28\n\n/** One resolved reference, kept for the codec and for the chip label. */\ninterface Resolved {\n readonly label: string\n readonly text: string\n readonly authorName?: string\n}\n\n/**\n * Reference id → what it points at.\n *\n * `ref` is `${roomId}:${messageId}` so it stays readable in storage and unique across rooms;\n * the words ride beside it because the codec is synchronous about identity and only receives\n * the id.\n */\nconst resolved = new Map<string, Resolved>()\n\n/** The text of a message body, whether it arrived as blocks or as one plain string. */\nconst textOf = (content: unknown): string => {\n if (typeof content === 'string') return content.replace(/\\s+/g, ' ').trim()\n if (!Array.isArray(content)) return ''\n const parts: string[] = []\n for (const block of content) {\n if (typeof block !== 'object' || block === null) continue\n const record = block as { readonly type?: unknown; readonly text?: unknown }\n if (record.type === 'text' && typeof record.text === 'string') parts.push(record.text)\n }\n return parts.join(' ').replace(/\\s+/g, ' ').trim()\n}\n\n/** One line, cut to `LABEL_CHARS` with an ellipsis. */\nconst shorten = (text: string): string =>\n text.length > LABEL_CHARS ? `${text.slice(0, LABEL_CHARS - 1)}…` : text\n\n/** What one message row shows and what a pick would insert. */\nconst rowOf = (roomId: string, record: { readonly id: string; readonly event: { readonly data?: Record<string, unknown> } }): {\n readonly ref: string\n readonly name: string\n readonly description: string\n readonly resolved: Resolved\n} | undefined => {\n const data = record.event.data\n if (data === undefined) return undefined\n const source = data['source'] as { messageId?: unknown; memberName?: unknown } | undefined\n const rowId = typeof source?.messageId === 'string' && source.messageId.length > 0\n ? source.messageId\n : typeof data['id'] === 'string' ? data['id'] : record.id\n const text = textOf(data['content'])\n if (text.length === 0) return undefined\n const authorName = typeof source?.memberName === 'string' && source.memberName.length > 0\n ? source.memberName\n : undefined\n const label = `${authorName ?? '消息'}:${shorten(text)}`\n return {\n ref: `${roomId}:${rowId}`,\n name: label,\n description: text.length > 60 ? `${text.slice(0, 59)}…` : text,\n resolved: { label, text, ...authorName === undefined ? {} : { authorName } },\n }\n}\n\n/** The slice of the trigger service this module registers into. */\nexport interface InputTriggersLike {\n registerSource(source: unknown): () => void\n}\n\n/**\n * Registers the room-message reference source.\n *\n * @param inputTriggers - the trigger pipeline service (`ctx.get('inputTriggers')`).\n * @param store - room store, read for the room the composer is writing into.\n * @returns the disposer.\n */\nexport const registerMessageSource = (inputTriggers: InputTriggersLike, store: RoomStore): (() => void) => {\n const source = {\n trigger: '@',\n name: SOURCE_NAME,\n order: 30,\n showGroupTitle: true,\n async candidates(_session: unknown, request: { readonly query: string; readonly signal: AbortSignal }) {\n const roomId = store.getState().activeRoomId\n if (roomId === undefined) return []\n const page = await listEvents(roomId)\n const needle = request.query.trim().toLowerCase()\n const out: { name: string; label: string; description: string; icon: string; value: string }[] = []\n // Newest first: a quote is nearly always of something just said.\n for (const record of [...page.events].reverse()) {\n if (request.signal.aborted) break\n const row = rowOf(roomId, record)\n if (row === undefined) continue\n if (needle.length > 0 && !row.resolved.text.toLowerCase().includes(needle)) continue\n resolved.set(row.ref, row.resolved)\n out.push({ name: row.name, label: row.name, description: row.description, icon: 'session', value: row.ref })\n if (out.length >= MAX_CANDIDATES) break\n }\n return out\n },\n onPick(pick: { readonly candidate: { readonly value?: string } }) {\n const ref = pick.candidate.value\n if (ref === undefined) return undefined\n const found = resolved.get(ref)\n if (found === undefined) return undefined\n return {\n insert: {\n source: SOURCE_NAME,\n ref,\n label: found.label,\n appearance: 'session',\n clipboardText: `@${found.label}`,\n },\n }\n },\n codec: {\n // What lands in the draft and the clipboard: the human-facing mention.\n clipboardText: (ref: string): string => {\n const found = resolved.get(ref)\n return found === undefined ? ref : `@${found.label}`\n },\n // What the model reads in place of the chip. A blockquote keeps the author on the first\n // line, which is what makes a quote readable after the session is compacted.\n serialize: (ref: string): Promise<string> => {\n const found = resolved.get(ref)\n if (found === undefined) return Promise.resolve(ref)\n const speaker = found.authorName === undefined ? '' : `${found.authorName}:`\n return Promise.resolve(`> ${speaker}${found.text}`)\n },\n },\n }\n return inputTriggers.registerSource(source)\n}","/**\n * Trigger-source identities shared by both halves of the package.\n *\n * The composer declares a mention with the source that produced the chip, and the Host routes\n * only the ids that source owns, so the string is a contract rather than a label. It lives here\n * so the client bundle and the host bundle cannot drift apart.\n *\n * @module @sparkelf/dsh-group-chat/reference-ids\n */\n\n/** The `@` source whose picks are roster members. */\nexport const MEMBER_MENTION_SOURCE = '@sparkelf/dsh-group-chat/member'\n","/**\n * The member source a future composer mention provider will register.\n *\n * The official composer owns the `@` menu today; this module is the stable data shape the\n * group-chat plugin can hand that extension point once it exists. Keeping it separate from\n * the roster means the provider does not have to know about roles, join times or room state.\n *\n * @module @sparkelf/dsh-group-chat/client/mentionSource\n */\n\nimport type { RoomMember } from './api.ts'\n\n/** One member as the composer's `@` menu needs it. */\nexport interface MentionSourceMember {\n /** Stable account id the server stores on the message. */\n id: string\n /** Name shown after `@` and matched by the search field. */\n displayName: string\n /** Picture, when the account has one; the menu falls back to initials. */\n avatarUrl?: string\n}\n\n/**\n * Build the member source for one room.\n * @param members - the room roster.\n * @param selfId - the caller's account id, excluded because mentioning yourself is not a way\n * to notify anyone; the host's self-mention route handles waking the agent separately.\n * @returns id/name/avatar rows in roster order.\n */\nexport function mentionSourceOf (\n members: readonly RoomMember[],\n selfId?: string,\n): MentionSourceMember[] {\n return members\n .filter((member) => member.userId !== selfId)\n .map((member) => ({\n id: member.userId,\n displayName: member.displayName.trim() === '' ? member.userId : member.displayName,\n ...(member.avatarUrl === undefined || member.avatarUrl === null || member.avatarUrl === ''\n ? {}\n : { avatarUrl: member.avatarUrl }),\n }))\n}\n","/**\n * Room members as `@` mentions in the composer.\n *\n * The `@` menu is a multi-source pipeline: every source owns one group, and a pick becomes an\n * official inline chip. A member is picked the same way a room message is, but the chip declares\n * itself with `appearance: 'mention'`, which is what makes the composer hand the picked ids to the\n * Host as `prompt.mentions` instead of leaving them to be re-parsed out of the prompt text. The\n * Host's routing reads ids by design (\"never parsed out of message text\"): text parsing would\n * misread a code sample containing `@` and would let any message smuggle itself into an execution\n * path.\n *\n * The group appears only in a room: `candidates` answers an empty list everywhere else, and an\n * empty group closes itself, so a normal session's `@` menu is unchanged.\n *\n * @module @sparkelf/dsh-group-chat/client/memberReference\n */\n\nimport { MEMBER_MENTION_SOURCE } from '../reference-ids.ts'\nimport { listMembers } from './api.ts'\nimport { mentionSourceOf } from './mentionSource.ts'\nimport type { RoomStore } from './store.ts'\nimport type { InputTriggersLike } from './messageReference.ts'\n\n/** Unique source name; duplicate `(trigger, name)` pairs throw at registration. */\nconst SOURCE_NAME = MEMBER_MENTION_SOURCE\n\n/** How many members the menu offers at once. */\nconst MAX_CANDIDATES = 20\n\n/** Reference id → the display name the chip and the clipboard need. */\nconst names = new Map<string, string>()\n\n/**\n * Registers the room-member mention source.\n *\n * @param inputTriggers - the trigger pipeline service (`ctx.get('inputTriggers')`).\n * @param store - room store, read for the room the composer is writing into.\n * @returns the disposer.\n */\nexport const registerMemberSource = (inputTriggers: InputTriggersLike, store: RoomStore): (() => void) => {\n const source = {\n trigger: '@',\n name: SOURCE_NAME,\n // Above room messages (30): a member is the more common address in a group chat.\n order: 20,\n showGroupTitle: true,\n async candidates(_session: unknown, request: { readonly query: string; readonly signal: AbortSignal }) {\n const roomId = store.getState().activeRoomId\n if (roomId === undefined) return []\n const members = await listMembers(roomId)\n const roster = mentionSourceOf(members)\n const owners = new Set(members.filter(member => member.owner).map(member => member.userId))\n const needle = request.query.trim().toLowerCase()\n const out: {\n name: string\n description?: string\n icon: string\n value: string\n }[] = []\n for (const member of roster) {\n if (request.signal.aborted) break\n if (needle.length > 0 && !member.displayName.toLowerCase().includes(needle)) continue\n names.set(member.id, member.displayName)\n out.push({\n name: member.displayName,\n ...owners.has(member.id) ? { description: '群主' } : {},\n icon: 'mention',\n value: member.id,\n })\n if (out.length >= MAX_CANDIDATES) break\n }\n return out\n },\n onPick(pick: { readonly candidate: { readonly value?: string; readonly name: string } }) {\n const memberId = pick.candidate.value\n if (memberId === undefined) return undefined\n const name = names.get(memberId) ?? pick.candidate.name\n return {\n insert: {\n source: SOURCE_NAME,\n ref: memberId,\n // The chip shows the same words the clipboard and the model carry, so the prompt text\n // and the declared ids cannot disagree about who was addressed.\n label: name,\n appearance: 'mention',\n clipboardText: `@${name}`,\n },\n }\n },\n codec: {\n /** What lands in the draft and the clipboard: the human-facing mention. */\n clipboardText: (ref: string): string => `@${names.get(ref) ?? ref}`,\n /** What the model reads in place of the chip. The id rides the prompt's `mentions`. */\n serialize: (ref: string): Promise<string> => Promise.resolve(`@${names.get(ref) ?? ref}`),\n },\n }\n return inputTriggers.registerSource(source)\n}","/**\n * Group-chat copy, in the shape every locale-backed DSH client plugin uses: one\n * English object is the key set, and each other locale is a complete record over\n * that key set so a missing translation fails to compile.\n *\n * @module @sparkelf/dsh-group-chat/client/locales\n */\n\n/** English group-chat copy. */\nexport const en = {\n nav: 'Group chat',\n title: 'Group chat',\n description: 'Rooms this workspace has joined. @ yourself to make the agent answer; every other message is recorded and read, but nothing runs.',\n loading: 'Loading rooms…',\n retry: 'Retry',\n\n emptyRooms: 'No rooms yet',\n createRoom: 'New room',\n roomName: 'Room name',\n create: 'Create',\n search: 'Search rooms',\n searchPlaceholder: 'Search rooms or messages…',\n searchClear: 'Clear search',\n searchEmpty: 'No room matches',\n discoverHeading: 'Rooms you can join',\n discoverMembers: '{count} members',\n joinRoom: 'Join',\n joining: 'Joining…',\n viewOptions: 'View options',\n orderBy: 'Sort',\n orderByUpdated: 'Recently active',\n orderByManual: 'Manual',\n archivedFilter: 'Left rooms',\n archivedHide: 'Hide',\n archivedShow: 'Show',\n copyRoomId: 'Copy room ID',\n left: 'Left',\n memberCountShort: '{count}',\n unreadAria: '{count} unread',\n unreadMentionAria: '{count} unread, one mentions you',\n createTitle: 'New room',\n createName: 'Room name',\n createCwd: 'Working directory',\n createCwdHint: 'Determines which files the agent may change. Members of this room can ask the agent to modify anything under this directory.',\n createPreset: 'Agent preset',\n createPresetHint: 'Determines which tools the agent has in this room.',\n createPresetDefault: \"The deployment's default preset\",\n createPresetUnavailable: 'The preset list is unavailable; the room will use the deployment default.',\n createMembers: 'Initial members',\n createMembersHint: 'You become the first member. Others can be added after the room exists.',\n createPolicy: 'Who may invite',\n createVisibility: 'Who can find this room',\n createTrigger: 'When the agent replies',\n createNotice: 'These settings control where the room appears in the interface. They are not access control.',\n menuRename: 'Rename',\n menuInvite: 'Invite members',\n menuSettings: 'Room settings',\n menuMembers: 'Manage members',\n menuTransfer: 'Transfer ownership',\n menuTransferTitle: 'Transfer ownership',\n menuTransferHint: 'Choose the member who becomes the owner. You become an ordinary member and can then leave.',\n menuTransferConfirm: 'Transfer',\n menuTransferEmpty: 'There is no other member to transfer to yet. Invite someone first.',\n menuOwnerCannotLeave: 'The owner cannot leave. Transfer ownership, or dissolve the room.',\n menuDissolve: 'Dissolve room',\n menuDissolveTarget: 'Room to dissolve: ',\n menuDissolveConfirm: 'Dissolve this room? Every member loses it, and its messages go with it. This cannot be undone.',\n menuLeave: 'Leave room',\n menuLeaveConfirm: 'Leave this room? The history your agent already recorded stays.',\n renameTitle: 'Rename room',\n renamePlaceholder: 'New name',\n membersTitle: 'Manage members',\n /** The roster section's own heading; the tab label above already says \"Manage members\". */\n membersRosterTitle: 'Members',\n membersSearch: 'Search members',\n membersSearchOpen: 'Search members',\n membersSearchPlaceholder: 'Search members…',\n membersSearchClear: 'Clear search',\n membersSearchEmpty: 'No member matches',\n membersEmpty: 'This room has no members yet.',\n membersError: 'The member list could not be read. Retry from the room menu.',\n announcementTitle: 'Announcement',\n announcementEmpty: 'No announcement yet',\n announcementAdd: 'Add announcement',\n announcementEdit: 'Edit',\n announcementPlaceholder: 'Write something every member should see',\n announcementUnavailable: 'Announcements are unavailable in this deployment',\n announcementSaving: 'Saving…',\n announcementOwnerOnly: 'Only the room owner can edit the announcement',\n announcementCleared: 'The announcement was cleared',\n announcementFailed: 'Could not save the announcement. Try again.',\n membersSelf: 'You',\n membersFilesTitle: 'Shared files',\n membersFilesEmpty: 'Nothing shared yet.',\n membersFilesRefresh: 'Refresh files',\n membersFileClose: 'Close preview',\n membersFileUnreadable: 'Could not open that file.',\n membersFileTruncated: 'Only the beginning is shown.',\n membersFileBinary: 'Binary file — no text preview.',\n membersSectionCollapsed: 'Collapsed',\n membersFileUpload: 'Upload a file',\n membersFileUploadFailed: 'Upload failed.',\n resizeRegion: 'Resize the group chat region',\n reply: 'Reply',\n replyCancel: 'Cancel reply',\n searchMessages: 'Search messages',\n searchMessagesPlaceholder: 'Search message text',\n searchMessagesEmpty: 'No message matches',\n searching: 'Searching…',\n membersFilesRoot: 'Files',\n membersOwner: 'Owner',\n membersNotARoom: 'This conversation is not a group chat, so it has no members.',\n membersGuideHint: 'Members and the announcement for this room',\n inviteTitle: 'Invite members',\n invitePlaceholder: 'Search DataOps accounts by name or handle',\n inviteHint: 'Only existing DataOps accounts can be added.',\n inviteSubmit: 'Invite',\n inviteEmpty: 'No matching accounts. They may already be in this room.',\n inviteSearching: 'Searching…',\n inviteRemove: 'Remove',\n inviteSearchLabel: 'Search accounts',\n inviteSelectedCount: '{count} selected',\n inviteSubmitCount: 'Invite ({count})',\n optionEmpty: 'Nothing to choose from.',\n pickThisDirectory: 'Use this directory',\n settingsTitle: 'Room settings',\n removeMember: 'Remove',\n ownerBadge: 'Owner',\n you: 'You',\n createSubmit: 'Create',\n cancel: 'Cancel',\n\n members: 'Members',\n memberCount: 'members',\n invite: 'Invite',\n leave: 'Leave room',\n leaveConfirm: 'Leave this room? Your own agent keeps the history it already recorded.',\n owner: 'Owner',\n\n sendPlaceholder: 'Message the room. @ yourself to have your agent answer.',\n send: 'Send',\n sending: 'Sending…',\n\n agentBadge: 'agent',\n triggeredHint: 'Your agent is answering.',\n\n notConfigured: 'The group-chat server is not configured, so no rooms can be listed.',\n unreachable: 'The group-chat server did not answer.',\n credentialMissing: 'No workspace credential is available, so rooms cannot be read.',\n credentialRejected: 'The workspace credential was refused. Sign in to DataOps again, then retry.',\n ownerCannotLeave: 'The owner cannot leave. Transfer ownership, or dissolve the room.',\n\n policy: 'Who may invite',\n policyAnyone: 'Any member',\n policyAdmin: 'Owner only',\n discoverability: 'Discoverability',\n discoverable: 'Listed in search',\n inviteOnly: 'Invite only',\n\n trigger: 'When the agent replies',\n triggerMention: 'Only when @-mentioned',\n triggerAlways: 'Every message',\n triggerNever: 'Never (record only)',\n\n save: 'Save',\n saved: 'Saved',\n}\n\n/** The key set every locale must cover. */\nexport type GroupChatKey = keyof typeof en\n\n/** Simplified Chinese group-chat copy. */\nexport const zh = {\n nav: '群聊',\n title: '群聊',\n description: '本工作区已加入的群聊。@ 自己会触发 agent 应答;其他消息都会记录并进入上下文,但不执行。',\n loading: '正在加载群聊…',\n retry: '重试',\n\n emptyRooms: '暂无群聊',\n createRoom: '新建群聊',\n roomName: '群聊名称',\n create: '创建',\n search: '搜索群聊',\n searchPlaceholder: '搜索群聊或消息…',\n searchClear: '清除搜索',\n searchEmpty: '无匹配结果',\n discoverHeading: '可以加入的群聊',\n discoverMembers: '{count} 位成员',\n joinRoom: '加入',\n joining: '加入中…',\n viewOptions: '视图选项',\n orderBy: '排序',\n orderByUpdated: '最近活跃',\n orderByManual: '手动',\n archivedFilter: '已退出的群聊',\n archivedHide: '隐藏',\n archivedShow: '显示',\n copyRoomId: '复制群聊 ID',\n left: '已退出',\n memberCountShort: '{count}',\n unreadAria: '{count} 条未读',\n unreadMentionAria: '{count} 条未读,其中有 @ 你',\n createTitle: '新建群聊',\n createName: '群聊名称',\n createCwd: '工作目录',\n createCwdHint: '决定机器人能操作哪些文件。此群成员可以让机器人修改该目录下的任何文件。',\n createPreset: 'Agent preset',\n createPresetHint: '决定机器人在本群可用的工具能力。',\n createPresetDefault: '跟随部署默认预设',\n createPresetUnavailable: '预设列表暂时读取不到;创建后将使用部署默认预设。',\n createMembers: '初始成员',\n createMembersHint: '创建者自动成为第一个成员;其余成员可在建群后邀请。',\n createPolicy: '谁能邀请成员',\n createVisibility: '谁能在群里发现这个房间',\n createTrigger: '机器人何时回复',\n createNotice: '这些设置控制房间在界面上的可见性,不构成访问控制。',\n menuRename: '重命名',\n menuInvite: '邀请成员',\n menuSettings: '群聊设置',\n menuMembers: '成员管理',\n menuTransfer: '转让群主',\n menuTransferTitle: '转让群主',\n menuTransferHint: '选择一位成员成为新群主。转让后你会变成普通成员,那时就可以退出群聊。',\n menuTransferConfirm: '转让',\n menuTransferEmpty: '目前没有其他成员可以转让,请先邀请成员。',\n menuOwnerCannotLeave: '群主不能退出群聊。请先转让群主,或解散群聊。',\n menuDissolve: '解散群聊',\n menuDissolveTarget: '将要解散群聊:',\n menuDissolveConfirm: '解散这个群聊?所有成员都会失去它,群消息也会一并删除,且无法恢复。',\n menuLeave: '退出群聊',\n menuLeaveConfirm: '退出这个群聊?你自己的 agent 已记录的历史会保留。',\n renameTitle: '重命名群聊',\n renamePlaceholder: '新名称',\n membersTitle: '成员管理',\n membersRosterTitle: '成员',\n membersSearch: '搜索成员',\n membersSearchOpen: '搜索成员',\n membersSearchPlaceholder: '搜索成员…',\n membersSearchClear: '清除搜索',\n membersSearchEmpty: '没有匹配的成员',\n membersEmpty: '这个群还没有成员。',\n membersError: '成员列表读取失败,可从群聊菜单重试。',\n announcementTitle: '群公告',\n announcementEmpty: '暂无群公告',\n announcementAdd: '+ 添加公告',\n announcementEdit: '编辑',\n announcementPlaceholder: '写一条所有成员都能看到的公告',\n announcementUnavailable: '公告功能暂不可用',\n announcementSaving: '保存中…',\n announcementOwnerOnly: '只有群主可以编辑公告',\n announcementCleared: '公告已清空',\n announcementFailed: '公告保存失败,请重试。',\n membersSelf: '我',\n membersFilesTitle: '群文件',\n membersFilesEmpty: '还没有共享文件。',\n membersFilesRefresh: '刷新文件',\n membersFileClose: '关闭预览',\n membersFileUnreadable: '无法打开该文件。',\n membersFileTruncated: '仅显示开头部分。',\n membersFileBinary: '二进制文件,不提供文本预览。',\n membersSectionCollapsed: '已折叠',\n membersFileUpload: '上传文件',\n membersFileUploadFailed: '上传失败。',\n resizeRegion: '拖动调节群聊区域高度',\n reply: '引用',\n replyCancel: '取消引用',\n searchMessages: '搜索消息',\n searchMessagesPlaceholder: '搜索消息内容',\n searchMessagesEmpty: '没有匹配的消息',\n searching: '搜索中…',\n membersFilesRoot: '群文件',\n membersOwner: '群主',\n membersNotARoom: '该会话不是群聊,没有群成员。',\n membersGuideHint: '本群成员与群公告',\n inviteTitle: '邀请成员',\n invitePlaceholder: '按姓名或账号搜索 DataOps 用户',\n inviteHint: '只能加入已存在的 DataOps 账号。',\n inviteEmpty: '没有匹配的账号,可能已在群里。',\n inviteSearching: '搜索中…',\n inviteRemove: '移除',\n inviteSearchLabel: '搜索账号',\n inviteSelectedCount: '已选 {count} 人',\n inviteSubmitCount: '邀请 ({count})',\n optionEmpty: '没有可选项。',\n pickThisDirectory: '用这个目录',\n inviteSubmit: '邀请',\n settingsTitle: '群聊设置',\n removeMember: '移除',\n ownerBadge: '群主',\n you: '我',\n createSubmit: '创建',\n cancel: '取消',\n\n members: '成员',\n memberCount: '位成员',\n invite: '邀请',\n leave: '退出群聊',\n leaveConfirm: '退出这个群聊?你自己的 agent 会保留已记录的历史。',\n owner: '群主',\n\n sendPlaceholder: '发消息。@ 自己可以让你的 agent 应答。',\n send: '发送',\n sending: '发送中…',\n\n agentBadge: 'agent',\n triggeredHint: '你的 agent 正在应答。',\n\n notConfigured: '未配置群聊服务器,无法列出群聊。',\n unreachable: '群聊服务器没有响应。',\n credentialMissing: '没有可用的工作区凭证,无法读取群聊。',\n credentialRejected: '工作区凭证被拒绝。请重新登录 DataOps 后重试。',\n ownerCannotLeave: '群主不能退出群聊。请先转让群主,或解散群聊。',\n\n policy: '谁可以邀请',\n policyAnyone: '任何成员',\n policyAdmin: '仅群主',\n discoverability: '可发现性',\n discoverable: '可在搜索中列出',\n inviteOnly: '仅限邀请',\n\n trigger: '机器人何时回复',\n triggerMention: '仅被 @ 时回复',\n triggerAlways: '每条都回复',\n triggerNever: '从不回复(纯记录)',\n\n save: '保存',\n saved: '已保存',\n} satisfies Record<GroupChatKey, string>\n","/**\n * Group-chat Client plugin.\n *\n * Registers two official DSH surfaces:\n * - `sidebar.footer.action` renders the group-chat region at the sidebar foot, under the\n * workspace list and above Settings. The shell positions it inside `footerActions`\n * (full width, `flex: none`); this plugin draws the section header and the flat room\n * list. It is an ordinary list seat, so no other plugin's contribution is disturbed.\n * - The matching `main` key gives the room page the region's rows select, so a room opens\n * in the main column rather than replacing the navigation column.\n *\n * This half owns the room list so the two surfaces cannot disagree about which rooms\n * exist. Both read the one store below and both re-read through the same action.\n *\n * The page is a pure consumer of the Host plugin's `/dsh-group-chat/api` route, so every\n * room operation it performs is the same call the agent's own turn makes. The browser\n * never talks to DataOps directly and never handles a credential: the Host holds the\n * workspace identity, and this half only renders what that identity can already see.\n *\n * @module @sparkelf/dsh-group-chat/client\n */\n\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'\n// Brings in the official `sidebarRight` / `sidebarRightTabs` members this plugin consumes, the\n// right-sidebar slot names it registers into, and the merge map its page parameters are typed by.\n// The augmentation lives in the package's `/client` entry, so importing it is what makes those\n// names real in this compilation.\nimport type {} from '@deepseek-ai/dsh-client-ui-sidebar-right/client'\nimport type { SessionId } from '@deepseek-ai/dsh-session/types'\nimport type { MainPanelId } from '@deepseek-ai/dsh-client-ui-layout/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type { StoredEntry } from '@deepseek-ai/dsh-client-ui-slots'\n// The slot this plugin shadows needs a local declaration; see the module doc comment.\nimport './contract.ts'\nimport { SidebarRooms, type SidebarRoomsInjected } from './SidebarRooms.tsx'\nimport { MessageSide } from './MessageSide.tsx'\nimport {\n MembersPanel, MEMBERS_TAB_KIND, MembersTabTitle,\n type MembersPanelInjected,\n} from './MembersPanel.tsx'\nimport { MembersGuideEntry, type MembersGuideEntryProps } from './GuideEntry.tsx'\nimport { createRoomStore, type RoomStore } from './store.ts'\nimport { createQuoteStore } from './quote.ts'\nimport { PendingQuoteBar } from './QuoteComposer.tsx'\nimport { registerMessageSource, type InputTriggersLike } from './messageReference.ts'\nimport { registerMemberSource } from './memberReference.ts'\nimport { PRESET_LOCALE_NS } from './rows.ts'\nimport { roomSessionId } from '../session-id.ts'\nimport { readRoomState } from './api.ts'\nimport { en, zh, type GroupChatKey } from './locales.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n 'settings.groupChat': GroupChatKey\n }\n}\n\ndeclare module '@deepseek-ai/dsh-client-ui-sidebar-right/client' {\n /**\n * The navigation parameters this page type accepts.\n *\n * The official service types `openTab`'s `params` by this map, and a kind that declares none\n * accepts `undefined` only. Declaring the room here keeps the opener typed against the body's\n * real contract instead of casting the service to a looser shape.\n */\n interface SidebarRightTabParamsMap {\n groupChatMembers: { readonly roomId: string }\n }\n}\n\nconst NS = 'settings.groupChat'\n\n/**\n * The `user` cell this plugin shadows, and the one the official Chat renderer occupies.\n *\n * Named rather than inlined because shadowing is a two-sided fact: winning the cell means\n * reading past the winner to find what would have rendered.\n */\nconst CHAT_NODE_SLOT = 'conversation.chat.node'\n\n/**\n * Every `user`-shaped cell of the chat-node seat.\n *\n * `user` and `steering` are separate cells that render the *same* official component: a room\n * message is `steering` once a turn has claimed it and `user` otherwise, so shadowing only\n * `user` would leave exactly the messages that arrived while the agent was working on the\n * right. Both are cell-local shadows, so neither reaches an ordinary Session: the side is\n * decided from the message's own room source, and an ordinary message has none.\n */\nconst CHAT_NODE_KEYS = ['user', 'steering'] as const\n\n/** Priority this plugin claims its chat-node cells at; the official entries sit at `0`. */\nconst SHADOW_PRIORITY = -1\n\n/**\n * This plugin's own chat-node renderer, so the lookup below cannot mistake it for the\n * official one. Compared by identity against the registered component.\n */\nconst OUR_VIEW = MessageSide\n\n/**\n * The entry the shell would render for one cell without this plugin.\n *\n * The registry projects shadowing winners through `entriesOfSlot`, which is exactly the\n * wrong question here — this plugin is the winner. `entries` keeps the whole ledger, so the\n * official entry is still visible, and picking the lowest-priority live one other than this\n * plugin's own reproduces the pre-plugin choice.\n *\n * @param ledger - every registered entry for the chat-node slot.\n * @param key - the cell being claimed.\n * @param ours - this plugin's own component, excluded from the candidates.\n * @returns the official component, or `undefined` when nothing else is registered.\n */\nconst officialViewFor = (\n ledger: readonly StoredEntry[],\n key: string,\n ours: unknown,\n): StoredEntry['component'] | undefined =>\n ledger\n .filter((entry) => entry.options.key === key && entry.component !== ours)\n .reduce<StoredEntry | undefined>(\n (lowest, entry) =>\n lowest === undefined || (entry.options.priority ?? 0) < (lowest.options.priority ?? 0)\n ? entry\n : lowest,\n undefined,\n )?.component\n\n/**\n * Report why one chat-node cell could not be claimed.\n *\n * A rejected claim is not fatal — the official renderer still draws that cell — but it does\n * mean the side never flips, which is invisible in the UI. The console is the only channel\n * this half has, and the message has to name the cell so the missing side can be traced to it.\n */\nconst logClaimFailure = (key: string, error: unknown): void => {\n console.warn(`[group-chat] could not claim the \"${key}\" chat-node cell; messages of this kind stay on the official side`, error)\n}\n\n/** Main-panel key the sidebar rows select. */\nconst PANEL_ID = 'group-chat' as MainPanelId\n\n/**\n * The members tab type.\n *\n * `MEMBERS_TAB_ID` keys the type's slot entries; `MEMBERS_TAB_KIND` is what `openTab` resolves.\n * They are separate values because the sidebar stores a placed tab by kind while the slot ledger\n * is keyed by id — the same split the official schedule plugin makes.\n */\nconst MEMBERS_TAB_ID = '@sparkelf/dsh-group-chat/members'\n\n/** Id of the pending-reply bar in the composer's dock. */\nconst QUOTE_DOCK_ID = '@sparkelf/dsh-group-chat/quote'\n\n/**\n * The guide entry's own identity, and where it sorts on the start page.\n *\n * An id rather than the tab's: the guide lists one box per entry, and the id is what the\n * entry-level slot replacement below is keyed by. The order lands the box among the other\n * `extension` types instead of at either end.\n */\nconst MEMBERS_GUIDE_ID = '@sparkelf/dsh-group-chat/members-guide'\nconst MEMBERS_GUIDE_ORDER = 60\n/** Client services required for these contributions. */\n/**\n * The Workspace navigation service, declared structurally.\n *\n * `@deepseek-ai/dsh-client-ui-workspace` is not a dependency of this package and declaring it as\n * one would tie the plugin to the plugin that happens to own the service today. Only the one\n * method used here is named, which is also what keeps this honest: the room opens the way a\n * session row opens, and nothing else about that plugin is assumed.\n */\ninterface WorkspaceNavigation {\n /**\n * Select a Session and show its Conversation.\n * @param sessionId - the room's Session, which `roomSessionId` derives from the room.\n */\n openSession(sessionId: string): void\n}\n\n/**\n * The slice of the Workspace controller this package needs.\n *\n * A dissolved room's Session outlives the room: it is a durable record, so the shell keeps\n * listing it under whatever grouping it landed in. Archiving is the documented way to take a\n * Session off those surfaces without destroying it, and it is exactly what the shell's own\n * \"archive session\" action does.\n */\ninterface WorkspaceArchive {\n archiveSession(sessionId: string, options?: { readonly stopActivity?: boolean }): Promise<void>\n /** Remove a Workspace registration; its Sessions and files are kept. */\n delete(workspaceId: string): Promise<void>\n /** Host-authoritative Workspace rows, including each one's owning Session ids. */\n readonly list: { getSnapshot(): WorkspaceListSnapshot | undefined }\n}\n\n/** The fields of the Workspace snapshot this package reads. */\ninterface WorkspaceListSnapshot {\n readonly phase?: string\n readonly items: readonly {\n readonly workspaceId: string\n readonly path: string\n readonly sessionIds: readonly string[]\n }[]\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n /** Workspace navigation, contributed by the workspace picker plugin. */\n readonly uiWorkspace: WorkspaceNavigation\n /** Workspace state and commands, contributed by the workspace controller plugin. */\n readonly workspaces: WorkspaceArchive\n }\n}\n\nexport const inject = ['slots', 'locale', 'uiWorkspace', 'workspaces', 'sidebarRightTabs', 'sidebarRight']\n\n/**\n * Register the sidebar region and the room page.\n * @param ctx - DSH client context with locale and slot registries.\n */\nexport function apply(ctx: ClientContext): void {\n const context = ctx as unknown as {\n sidebarRight?: { openTab(kind: string, options: { params: { url: string } }): void }\n get?: (name: string) => unknown\n }\n const sidebarRight = (context.sidebarRight ?? context.get?.('sidebarRight')) as\n { openResource?(address: string): void } | undefined\n const openRoomFile = (attachment: { attachmentId: string; name: string; bytes: number }, roomId: string): void => {\n const query = 'attachmentId=' + encodeURIComponent(attachment.attachmentId)\n + '&name=' + encodeURIComponent(attachment.name)\n + '&bytes=' + encodeURIComponent(String(attachment.bytes))\n void (async () => {\n const materialized = await fetch('/dsh-group-chat/api/attachment-materialize?' + query)\n if (!materialized.ok) throw new Error('附件预览准备失败')\n const target = await materialized.json() as { path?: string }\n if (typeof target.path !== 'string') throw new Error('附件预览路径缺失')\n const sessionId = 'group-room-' + roomId\n const address = 'dsh-resource://file/session/' + encodeURIComponent(sessionId)\n + '/' + target.path.split('/').map((part) => encodeURIComponent(part)).join('/')\n if (sidebarRight !== undefined && sidebarRight.openResource !== undefined) {\n sidebarRight.openResource(address)\n return\n }\n const fallback = new URL('/dsh-group-chat/api/attachment-preview?' + query, window.location.origin).toString()\n window.open(fallback, '_blank', 'noopener,noreferrer')\n })().catch((error) => {\n console.error('[group-chat] file preview failed', error)\n })\n }\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'group-chat: copy')\n const t = ctx.locale.bind(NS) as (key: GroupChatKey) => string\n // A second binding rather than a merged dictionary: the preset copy belongs to the platform's\n // `settings.agentPreset` namespace, and a preset name this plugin translated itself would\n // drift from the hero chip that shows the same preset under the same id.\n const tPreset = ctx.locale.bind(PRESET_LOCALE_NS) as (key: string) => string\n\n // One store shared by both surfaces, created here because the sidebar must show the\n // same list the page is operating on.\n const base = createRoomStore()\n // One store for the pending reply, shared by the message that starts it and the composer that\n // shows it. Created here because both surfaces are registered from this entry.\n const quotes = createQuoteStore()\n /**\n * Drops the pending reply once the composer submits.\n *\n * The composer belongs to the official chat surface, so there is no submit callback to hook.\n * What is stable is how a submission looks: Enter inside the editor, or a click on the send\n * button. Both are observed at the document in the capture phase — before the official handler\n * runs — and this side's pending reply is dropped for the open room. Only the local copy goes:\n * the host still needs its own to attach the quote to the message being published, and deleting\n * it here would send the reply bare.\n */\n const dropQuoteOnSubmit = (): (() => void) => {\n const clearActive = (): void => {\n const roomId = store.getState().activeRoomId\n if (roomId !== undefined) quotes.clearLocal(roomId)\n }\n const onSubmitKey = (event: KeyboardEvent): void => {\n if (event.key !== 'Enter' || event.shiftKey || event.isComposing) return\n const target = event.target\n if (target instanceof Element && target.closest('[contenteditable=\"true\"]') !== null) clearActive()\n }\n const onSubmitClick = (event: MouseEvent): void => {\n const target = event.target\n if (!(target instanceof Element)) return\n const button = target.closest('button[aria-label]')\n const label = button?.getAttribute('aria-label') ?? ''\n // Matches the shipped labels without pinning one language: both the zh and en strings\n // describe sending a message.\n if (/发送|send/i.test(label)) clearActive()\n }\n document.addEventListener('keydown', onSubmitKey, true)\n document.addEventListener('click', onSubmitClick, true)\n return () => {\n document.removeEventListener('keydown', onSubmitKey, true)\n document.removeEventListener('click', onSubmitClick, true)\n }\n }\n ctx.effect(dropQuoteOnSubmit)\n // The one action that means \"show me this room\" is opening its Session, so every caller —\n // the row today, a keyboard shortcut or a deep link later — goes through it and no caller has\n // to remember a second step.\n const store: RoomStore = {\n ...base,\n open: (roomId) => {\n base.open(roomId)\n // Wait for the host to have the room's Session before asking the shell to open it.\n //\n // The host creates room Sessions on its own startup poll; a member who clicks a room in\n // the first seconds after the plugin comes up otherwise retains an id that does not exist\n // yet. That failure is quiet — the history still draws — but nothing written afterwards\n // lands, because the composer is bound to a session scope with no session behind it.\n void (async () => {\n for (let attempt = 0; attempt < 25; attempt += 1) {\n try {\n if ((await readRoomState(roomId)).open) break\n } catch {\n // A read that fails says nothing about readiness; the open below is still the best\n // thing to try, and the shell reports its own failure if the session is missing.\n break\n }\n await new Promise((resolve) => { setTimeout(resolve, 300) })\n }\n ctx.uiWorkspace.openSession(roomSessionId(roomId))\n })()\n // Opening a room is the operator saying \"show me this conversation\", and the roster is the\n // other half of it: who is in here, and what the room has announced. The tab is revealed\n // rather than forced — `openTab` focuses an already-placed tab of this type instead of\n // stacking a second one, so re-entering a room keeps whatever the operator had open beside\n // it, and a reader who closed the roster stays closed because nothing re-opens it later.\n // Reached through the store's own action so the timing rule lives in one place: the room has\n // to be the mounted Session before a right-sidebar tab can be placed beside it.\n queueMicrotask(() => { store.openMembers?.(roomId) })\n },\n /**\n * Dissolve a room, then take its Session off the shell's grouping surfaces.\n *\n * The room row disappears because the server deleted it, but the Session is a durable\n * record that outlives the room: `dissolveRoom` cannot reach it, so without this the\n * room's leftover workspace (from before rooms stopped registering one) and its Session\n * entry would stay listed for a conversation that no longer exists. Archiving is the\n * documented, reversible way to remove a Session from those surfaces — the same action\n * the shell's own \"archive session\" uses.\n *\n * Runs after the delete so a refused dissolve leaves the Session alone.\n */\n dissolve: async (roomId) => {\n await base.dissolve(roomId)\n try {\n await ctx.workspaces.archiveSession(roomSessionId(roomId), { stopActivity: true })\n // A room opened before rooms stopped registering one still owns a Workspace entry,\n // and dissolving it has to take that entry with it. Matched by the Session it owns\n // rather than by path: the path is the room's directory, and a room may have been\n // pointed at a directory a real workspace also uses — deleting by path would remove\n // that one.\n const sessionId = roomSessionId(roomId)\n const snapshot = ctx.workspaces.list.getSnapshot()\n for (const workspace of snapshot?.items ?? []) {\n if (!workspace.sessionIds.includes(sessionId)) continue\n await ctx.workspaces.delete(workspace.workspaceId)\n }\n } catch {\n // The room is gone either way; a Session that could not be archived is a leftover\n // row, not a failed dissolve. Saying so would make the delete look unsuccessful.\n }\n },\n /**\n * Open the members panel for a room.\n *\n * Two steps, in this order and not the other way round: the room has to be the *mounted*\n * Session before a right-sidebar tab can be placed beside it, because `openTab` acts on the\n * mounted seat and throws when there is none. `openSession` is synchronous in what it records\n * but the seat re-binds on the next frame, so the tab is opened on a zero-delay task rather\n * than inline — inline would aim at the session the operator is leaving.\n */\n openMembers: (roomId) => {\n // `sidebarRight` is injected, so it is live for this whole turn. Reading it through\n // `ctx.get` was the one-shot lookup that cannot see a service another plugin provides.\n const open = (): void => {\n // A page type that is not `multiple` is recorded under one address, and the official\n // service reveals the tab already at that address instead of stacking a second copy — so\n // re-entering a room focuses the page the reader already had, with no probing here.\n ctx.sidebarRight.openTab(MEMBERS_TAB_KIND, { params: { roomId } })\n }\n if (base.getState().activeRoomId === roomId) open()\n else setTimeout(open, 0)\n },\n }\n\n ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({\n name: 'sidebar.footer.action',\n id: PANEL_ID,\n order: 20,\n inject: (): SidebarRoomsInjected => ({ t, tPreset, store }),\n }, SidebarRooms))\n\n /**\n * The members panel, as a tab in the official right sidebar.\n *\n * Three registrations, because the sidebar keeps three separate facts about a tab: the type it\n * belongs to (so navigation can resolve and persist it), the body to draw, and the chip that\n * labels it. `id` identifies the type's slot entries; `kind` is what `openTab` takes. They are\n * not the same value — the official schedule plugin uses\n * `\"@deepseek-ai/dsh-client-ui-schedule/task\"` and `\"scheduleTask\"` respectively — and\n * collapsing them would make `openTab` throw where nobody can see it.\n *\n * The panel is registered for every profile and decides at render time whether the Session it\n * was opened beside is a room. Registering conditionally instead would lose a placed tab on the\n * next reload: a tab whose type is gone cannot be restored.\n */\n // Registered through `ctx.inject`, not a one-shot `ctx.get`: the official right sidebar is a\n // separate plugin, so this one may load before the registry it contributes to exists. `inject`\n // runs the body when the service is present and re-runs it if the provider is replaced, which\n // is also what keeps the panel alive across an HMR of either side. A profile without the right\n // sidebar simply never runs this body, so the room list still comes up.\n //\n // The guide box is registered in the same body. That is deliberate: both entries are faces of\n // one page type, and a type that appears on the start page must be openable from it.\n /**\n * Open this plugin's page type from the start guide.\n *\n * The guide's shipped box opens through the guide's own `openTab`; a replacement box is not\n * handed that callback, so it goes through the same service the plugin already holds.\n */\n const openTabFromGuide = (kind: string): void => {\n // The shipped box opens through the guide's own `tab.actions.openTab`, which a replacement\n // box is not handed. This reaches the same service directly, so a click behaves exactly like\n // one on a shipped box: the type opens beside the guide the reader is looking at.\n // A click that cannot reach the sidebar is a wiring fault, not a reader's mistake: surface it\n // rather than leaving a dead box behind.\n try {\n ctx.sidebarRight.openTab(kind)\n } catch (error) {\n console.error('[group-chat] opening the members page failed', error)\n }\n }\n\n // The page type, its body, its title, and its start-guide box. `sidebarRightTabs` and\n // `sidebarRight` are injected (see the module's `inject`), so by the time `apply` runs they\n // are live: the official right sidebar always loads before the types that contribute to it.\n // A profile without that sidebar does not load this plugin at all, which is the intended\n // outcome — there would be nowhere to put the page.\n ctx.effect(() => ctx.sidebarRightTabs.register({\n id: MEMBERS_TAB_ID,\n kind: MEMBERS_TAB_KIND,\n // `extension` is the band for a type from outside the product; the default already means\n // exactly that, and naming it keeps the choice visible next to `builtin` types.\n priority: 'extension',\n title: () => t('membersTitle'),\n // The guide entry is what puts this type on the right sidebar's start page, beside Files,\n // Database and the rest. Omitting `guide` is the documented way to stay off that page.\n guide: [{\n id: MEMBERS_GUIDE_ID,\n order: MEMBERS_GUIDE_ORDER,\n title: () => t('membersTitle'),\n description: () => t('membersGuideHint'),\n }],\n }), 'group-chat: members tab type')\n\n ctx.effect(() => ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({\n name: 'sidebar.right.pane.tab',\n key: MEMBERS_TAB_ID,\n locale: NS,\n // The seat hands the inject factory the Session id and its bound actions, not a props\n // object; the panel needs the id to know which room it belongs to.\n inject: ((sessionId: string): MembersPanelInjected => ({ sessionId, t, store })) as never,\n }, MembersPanel)), 'group-chat: members tab body')\n\n ctx.effect(() => ctx.slots.inject('sidebar.right.pane.tab.title', () => ctx.slots.register({\n name: 'sidebar.right.pane.tab.title',\n key: MEMBERS_TAB_ID,\n locale: NS,\n inject: () => ({ t }),\n }, MembersTabTitle)), 'group-chat: members tab title')\n\n // Replace this type's own box on the start guide. The seat is keyed by the contributing\n // type's id, and the replacement decides per Session whether the box is drawn: the guide's\n // own list is global, so this is the only place the decision can be made.\n ctx.effect(() => ctx.slots.inject('sidebar.right.tab.guide.entry', () => ctx.slots.register({\n name: 'sidebar.right.tab.guide.entry',\n key: MEMBERS_TAB_ID,\n locale: NS,\n // The seat calls an inject factory with the Session id; the box's own copy arrives as owner\n // props at render time, so this returns only what the factory itself must supply.\n inject: ((sessionId: string): Partial<MembersGuideEntryProps> => ({\n sessionId,\n onPick: () => { openTabFromGuide(MEMBERS_TAB_KIND) },\n })) as never,\n }, MembersGuideEntry)), 'group-chat: members guide box')\n\n // A file card lives inside the official Chat renderer, including occurrences that are\n // emitted before the room wrapper claims a cell. Delegate from the document so every file\n // card in a room opens the same right-Sidebar preview, regardless of which cell drew it.\n ctx.effect(() => {\n const onFileClick = (event: MouseEvent): void => {\n const target = event.target\n if (!(target instanceof Element)) return\n const card = target.closest('[data-message-attachments] > *')\n const roomId = store.getState().activeRoomId\n if (!(card instanceof HTMLElement) || roomId === undefined) return\n const name = (card.getAttribute('title') ?? card.innerText.split('\\n')[0] ?? '').trim()\n if (name === '') return\n void (async () => {\n try {\n let cursor: string | null = null\n let found: { attachmentId: string; name: string; bytes: number } | undefined\n for (let page = 0; page < 20 && found === undefined; page += 1) {\n const suffix = cursor === null ? '' : '?cursor=' + encodeURIComponent(cursor)\n const response = await fetch('/dsh-group-chat/api/rooms/' + encodeURIComponent(roomId) + '/events' + suffix)\n if (!response.ok) return\n const payload = await response.json() as { events?: Array<{ event?: Record<string, unknown> }>; nextCursor?: string | null; hasMore?: boolean }\n for (const record of payload.events ?? []) {\n const data = record.event?.data as Record<string, unknown> | undefined\n const content = data?.content\n if (!Array.isArray(content)) continue\n for (const block of content) {\n if (block === null || typeof block !== 'object') continue\n const item = block as Record<string, unknown>\n const attachment = item.attachment as Record<string, unknown> | undefined\n if (item.type === 'file' && attachment?.name === name\n && typeof attachment.attachmentId === 'string' && typeof attachment.bytes === 'number') {\n found = { attachmentId: attachment.attachmentId, name, bytes: attachment.bytes }\n break\n }\n }\n if (found !== undefined) break\n }\n if (payload.hasMore !== true || payload.nextCursor === null || payload.nextCursor === undefined) break\n cursor = payload.nextCursor\n }\n if (found !== undefined) openRoomFile(found, roomId)\n } catch (error) {\n console.error('[group-chat] file preview lookup failed', error)\n }\n })()\n }\n document.addEventListener('click', onFileClick, true)\n return () => { document.removeEventListener('click', onFileClick, true) }\n }, 'group-chat: file preview delegation')\n\n // A room is a Session. Its messages are ordinary `user/message` events on that Session's log,\n // so the shell's own Conversation already renders them — bubbles, markdown, tool calls,\n // streaming, scrollback. Opening the room is therefore one navigation action, not a panel to\n // draw: `openSession` is the same call the workspace list makes for its own rows.\n //\n // Anything the plugin drew here instead would be a second renderer that has to keep pace with\n // the first, and would silently drop every feature the Conversation gains.\n //\n // One thing the official renderer cannot know is which side a message belongs on: in an\n // ordinary Session the only author is the operator, so its user row is unconditionally\n // right-aligned. A room has several authors. Shadowing the `user`-shaped cells at a\n // negative priority is how those cells are reached — the renderer picks the lowest live\n // entry, and the official one sits at `0`. The shadowing entries render the official\n // component read back out of this same registry, so the bubble itself is not\n // reimplemented; only its placement is decided here.\n //\n // This waits for the official entries rather than reading the ledger once. `slots.inject`\n // re-runs on a *declaration* change, and the Chat node slot is declared by the shell's\n // root before any of its renderers register — so at this point the ledger can hold a\n // different cell's entry and nothing else. Reading it once would find no official `user`\n // view, and registering nothing is invisible: the cells would simply keep their official\n // alignment, with no error anywhere to say why. Subscribing to the slot is what makes the\n // claim happen whenever the official renderers actually arrive.\n ctx.slots.inject('conversation.chat.node', () => {\n let claims: (() => void)[] = []\n const release = (): void => {\n for (const dispose of claims) dispose()\n claims = []\n }\n const reconcile = (): void => {\n const ledger = ctx.slots.entries('conversation.chat.node')\n // Cells already claimed are left alone: re-registering at the same priority throws, and\n // the throw would take the whole contribution down. Occupancy is read from key and\n // priority rather than from component identity, because the client bundle can be\n // evaluated more than once in a page (a reload, or the HMR receiver re-running the\n // factory): each evaluation gets its own `OUR_VIEW` object, so an identity check misses\n // the previous evaluation's entry and the re-registration throws. `SHADOW_PRIORITY` is\n // ours alone — the official entries sit at the default `0` — so a `-1` occupant of this\n // cell is always a previous evaluation of this plugin, never something to displace.\n const claimed = new Set(\n ledger\n .filter((entry) => (entry.options.priority ?? 0) === SHADOW_PRIORITY)\n .map((entry) => entry.options.key),\n )\n for (const key of CHAT_NODE_KEYS) {\n if (claimed.has(key)) continue\n const official = officialViewFor(ledger, key, OUR_VIEW)\n // Without the official view there is nothing to place: registering a wrapper that\n // renders nothing would blank every message of that cell in every Session, which is\n // far worse than leaving the bubble on its official (right) side. The subscription is\n // what gives this another chance once the renderer does arrive.\n if (official === undefined) continue\n // Wrapped because a lost race must not take the plugin down with it. The guard above\n // covers the ordinary case, but the slot core can also reject a registration this\n // plugin cannot observe first (another evaluation mid-flight). Throwing here would\n // abort the whole `inject` callback — losing the room list's own renderer wiring — so a\n // rejected claim is skipped, leaving that cell with the official placement.\n let dispose: (() => void) | undefined\n try {\n dispose = ctx.slots.register({\n name: 'conversation.chat.node',\n key,\n priority: SHADOW_PRIORITY,\n // Required, not cosmetic: the seat synthesizes the `t` prop from the *registering*\n // entry's namespace, so this entry must name the official Chat namespace. Declaring\n // our own here would leave the official bubble's copy looking up keys that namespace\n // does not hold. `chat` is already registered by the shell, so naming it adds no\n // dictionary and cannot collide with the shell's own registration.\n locale: 'chat',\n }, (props: Record<string, unknown>) =>\n MessageSide({\n ...props,\n Official: official,\n // Read as a snapshot rather than captured: identity arrives after the first paint,\n // and a value captured when this renderer was registered would stay `undefined` for\n // the life of the page, leaving every message on the unknown-author side forever.\n getSelfId: store.selfId,\n subscribeSelf: store.subscribe,\n // Reads through the live store rather than a captured value: the roster arrives after\n // the first paint, and a snapshot taken here would stay `undefined` for the life of\n // the page, leaving every other author's bubble unlabelled.\n getRoster: (roomId: string) => store.getState().members[roomId],\n sided: props['node'] !== undefined,\n quotes,\n // Our own copy, kept apart from the seat's `t`: that one is bound to the official\n // `chat` namespace the bubble renders against.\n tOwn: t,\n }))\n } catch (error) {\n logClaimFailure(key, error)\n }\n if (dispose !== undefined) claims.push(dispose)\n }\n }\n // Registration mutates the ledger, so the notification that a claim triggers would\n // re-enter this. `reconcile` is idempotent through the `claimed` set, which makes the\n // re-entry a no-op rather than a loop.\n const unsubscribe = ctx.slots.subscribe('conversation.chat.node', reconcile)\n reconcile()\n return () => { unsubscribe(); release() }\n })\n\n // The pending-quote bar above the composer. `conversation.input.dock` is a list seat, so this\n // adds a row beside the official queue/todo docks instead of replacing one of them. The seat\n // is session-scoped and hands the factory a session id; a non-room session draws nothing.\n // Room messages join the official `@` menu. This is the only route to a real inline\n // reference: the pipeline owns chip insertion, so a plugin-provided pick is what makes a\n // quote look and behave like a session reference instead of a second, parallel widget.\n const inputTriggers = ctx.get('inputTriggers') as InputTriggersLike | undefined\n if (inputTriggers !== undefined) {\n ctx.effect(() => registerMessageSource(inputTriggers, store))\n // Members are a second group in the same menu. Only a room answers it, and a pick declares\n // `appearance: 'mention'`, which is what carries the member ids to the Host as prompt.mentions.\n ctx.effect(() => registerMemberSource(inputTriggers, store))\n }\n\n ctx.effect(() => ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({\n name: 'conversation.input.dock',\n id: QUOTE_DOCK_ID,\n locale: NS,\n inject: (() => ({\n t,\n quotes,\n store,\n })) as never,\n }, PendingQuoteBar)), 'group-chat: pending quote bar')\n\n void PANEL_ID\n}\n"],"mappings":"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"}