@athenaintel/react 0.57.1 → 0.57.2

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.
Files changed (31) hide show
  1. package/README.md +19 -0
  2. package/dist/chat/AthenaChat.cjs +47 -2
  3. package/dist/chat/AthenaChat.cjs.map +1 -1
  4. package/dist/chat/AthenaChat.js +47 -2
  5. package/dist/chat/AthenaChat.js.map +1 -1
  6. package/dist/provider/AthenaProvider.cjs +7 -0
  7. package/dist/provider/AthenaProvider.cjs.map +1 -1
  8. package/dist/provider/AthenaProvider.js +9 -2
  9. package/dist/provider/AthenaProvider.js.map +1 -1
  10. package/dist/react/package.json.cjs +1 -1
  11. package/dist/react/package.json.js +1 -1
  12. package/dist/runtime/statewire-run-config.cjs +15 -0
  13. package/dist/runtime/statewire-run-config.cjs.map +1 -1
  14. package/dist/runtime/statewire-run-config.d.ts +13 -0
  15. package/dist/runtime/statewire-run-config.js +15 -0
  16. package/dist/runtime/statewire-run-config.js.map +1 -1
  17. package/dist/threads/SessionList.cjs +22 -19
  18. package/dist/threads/SessionList.cjs.map +1 -1
  19. package/dist/threads/SessionList.js +22 -19
  20. package/dist/threads/SessionList.js.map +1 -1
  21. package/dist/threads/live-session-rows.cjs +207 -0
  22. package/dist/threads/live-session-rows.cjs.map +1 -0
  23. package/dist/threads/live-session-rows.d.ts +175 -0
  24. package/dist/threads/live-session-rows.js +207 -0
  25. package/dist/threads/live-session-rows.js.map +1 -0
  26. package/dist/threads/statewire-thread-list.cjs +173 -20
  27. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  28. package/dist/threads/statewire-thread-list.d.ts +44 -1
  29. package/dist/threads/statewire-thread-list.js +174 -21
  30. package/dist/threads/statewire-thread-list.js.map +1 -1
  31. package/package.json +1 -1
@@ -27,6 +27,19 @@ function effectiveStatewireModel({
27
27
  }
28
28
  return model === "" ? void 0 : model;
29
29
  }
30
+ function effectiveStatewireStartChannel({
31
+ agent,
32
+ channel,
33
+ mode,
34
+ extraRunConfig
35
+ }) {
36
+ if (extraRunConfig !== void 0 && Object.hasOwn(extraRunConfig, "start_channel")) {
37
+ const override = extraRunConfig.start_channel;
38
+ return typeof override === "string" && override.trim() !== "" ? override : null;
39
+ }
40
+ const collabChannelSelected = parseCollabAgentRef(agent == null ? void 0 : agent.trim()) !== null && Boolean((mode == null ? void 0 : mode.trim()) || (channel == null ? void 0 : channel.trim()));
41
+ return collabChannelSelected ? null : STATEWIRE_START_CHANNEL;
42
+ }
30
43
  function buildStatewireRunConfig(options = {}) {
31
44
  var _a, _b, _c, _d, _e, _f;
32
45
  const collabAgentId = resolveAgentProp({ agent: options.agent });
@@ -78,8 +91,10 @@ export {
78
91
  COLLAB_AGENT_REF_PREFIX,
79
92
  COLLAB_AGENT_REF_SHAPE,
80
93
  DEFAULT_STATEWIRE_MODEL,
94
+ STATEWIRE_START_CHANNEL,
81
95
  buildStatewireRunConfig,
82
96
  effectiveStatewireModel,
97
+ effectiveStatewireStartChannel,
83
98
  parseCollabAgentRef,
84
99
  resolveAgentProp
85
100
  };
@@ -1 +1 @@
1
- {"version":3,"file":"statewire-run-config.js","sources":["../../src/runtime/statewire-run-config.ts"],"sourcesContent":["/** Runtime marker the deep-agent session substrate stamps on sessions. */\nconst DEEP_AGENT_RUNTIME_MARKER = 'athena_deep_agent';\n\n/** Session-origin channel. The backend clamps unknown values to 'web', which\n * is how every SDK-started session has always classified. */\nconst STATEWIRE_START_CHANNEL = 'web';\n\n/** Session label persisted in the thread's panel config (mirrors\n * 'Athena Chrome' / 'Athena mobile' in the sibling statewire hosts). */\nconst STATEWIRE_RUN_LABEL = 'Athena SDK';\n\n/** Deep-agent default model — matches the Olympus and mobile statewire hosts. */\nexport const DEFAULT_STATEWIRE_MODEL = 'claude-sonnet-5-5';\n\n/** Agent-id namespace for collab agents (`collab_agent:<asset_id>`). */\nexport const COLLAB_AGENT_REF_PREFIX = 'collab_agent:';\n\n/** The only shape the `agent` prop accepts, for error messages and docs. */\nexport const COLLAB_AGENT_REF_SHAPE = 'collab_agent:<asset_id>';\n\n/** The asset id when `agent` is a `collab_agent:<asset_id>` ref, else null. */\nexport function parseCollabAgentRef(agent: string | undefined | null): string | null {\n if (typeof agent !== 'string' || !agent.startsWith(COLLAB_AGENT_REF_PREFIX)) return null;\n const assetId = agent.slice(COLLAB_AGENT_REF_PREFIX.length).trim();\n return assetId || null;\n}\n\n/**\n * Resolve the provider's `agent` prop to a collab-agent asset id.\n *\n * An omitted or blank `agent` means \"no agent selected\" — the thread runs the\n * Athena deep agent — and resolves to `null`. A `collab_agent:<asset_id>` ref\n * resolves to the asset id. Any other non-blank value throws: the statewire\n * host cannot honour it, and a selector that is silently dropped would let a\n * misconfigured deployment ship answers from the wrong agent.\n */\nexport function resolveAgentProp({ agent }: { agent: string | undefined | null }): string | null {\n if (agent === undefined || agent === null || agent.trim() === '') return null;\n const assetId = parseCollabAgentRef(agent.trim());\n if (assetId) return assetId;\n throw new Error(\n `[AthenaSDK] Unsupported \\`agent\\` prop ${JSON.stringify(agent)}. The only accepted shape is '${COLLAB_AGENT_REF_SHAPE}' (the asset id of a published collab agent). Named agents are not supported — the statewire host always runs the Athena deep agent; omit \\`agent\\` to use it.`,\n );\n}\n\nexport interface StatewireRunConfigOptions {\n /** LLM model identifier. Defaults to the deep-agent default model — unless a\n * collab agent is selected and its author set one (the backend overlay\n * applies the agent's model when the run sends none), in which case no\n * model is sent so the agent's choice wins. */\n model?: string;\n /** Agent selector. Only the `collab_agent:<asset_id>` shape is accepted: the\n * run executes AS that published collab agent (prompt, tools, model\n * default). Any other non-empty value throws (see {@link resolveAgentProp}). */\n agent?: string;\n /** Collab-agent channel id (built-in like `email`/`sms`, or a user-defined\n * custom channel id) selecting that channel's configuration overrides.\n * Sent as `collab_channel_id`; only meaningful with a collab `agent`. */\n channel?: string;\n /** The agent's mode this send runs in, picked from the chat's mode menu\n * (`AthenaProvider` `modes`): `base` or a mode's id. Sent as\n * `collab_channel_id` in place of `channel`, which then rides as\n * `collab_entry_channel_id` so a session the pick starts still enters\n * through the channel (its switch list, lock and thread list). Only\n * meaningful with a collab `agent`. */\n mode?: string;\n /** Backend toolkit IDs to enable. */\n tools?: string[];\n /** Frontend tool IDs to include in enabled_tools. */\n frontendToolIds?: string[];\n /** Asset IDs to include in the workbench. */\n workbench?: string[];\n /** Knowledge base IDs for RAG context. */\n knowledgeBase?: string[];\n /** System prompt override. */\n systemPrompt?: string;\n /** Client app identifier, passed through as app_id. */\n appId?: string;\n /** Client-executed tool declarations, passed through as client_tools. The\n * deep-agent runtime binds each as an interrupt-backed tool the client\n * answers via the statewire client-tool bridge. */\n clientTools?: Record<string, unknown>[];\n /** Additional fields merged into runConfig.custom. */\n extraRunConfig?: Record<string, unknown>;\n}\n\n/**\n * The model a send carries for the host's `model` and `extraRunConfig`, with\n * the precedence {@link buildStatewireRunConfig} applies: `extraRunConfig` is\n * merged last, so its `model` key (when it has one) wins over the `model`\n * prop. `undefined` when the send carries no explicit model.\n */\nexport function effectiveStatewireModel({\n model,\n extraRunConfig,\n}: {\n model: string | undefined;\n extraRunConfig: Record<string, unknown> | undefined;\n}): string | undefined {\n if (extraRunConfig !== undefined && Object.hasOwn(extraRunConfig, 'model')) {\n const override = extraRunConfig.model;\n return typeof override === 'string' && override !== '' ? override : undefined;\n }\n return model === '' ? undefined : model;\n}\n\n/**\n * Build the `runConfig` a deep-agent statewire run is driven by. Commands\n * travel bare on the statewire channel, so this payload rides the ambient\n * model context (`config.runConfig`) instead of the request body.\n *\n * The flattened `custom` fields drive the run, while `panel_config` carries a\n * minimal panel state the backend persists per thread and replays on the\n * statewire snapshot — so a session started from the SDK stays configured\n * when reopened elsewhere.\n */\nexport function buildStatewireRunConfig(\n options: StatewireRunConfigOptions = {},\n): Record<string, unknown> {\n const collabAgentId = resolveAgentProp({ agent: options.agent });\n const channel = collabAgentId ? options.channel?.trim() || undefined : undefined;\n const mode = collabAgentId ? options.mode?.trim() || undefined : undefined;\n const collabChannelId = mode ?? channel;\n const collabEntryChannelId = mode && channel && mode !== channel ? channel : undefined;\n // With a collab agent selected and no explicit model, send NO model: the\n // backend overlay then applies the model the agent's author configured\n // instead of the SDK default overriding it.\n const model = options.model ?? (collabAgentId ? undefined : DEFAULT_STATEWIRE_MODEL);\n const enabledTools = Array.from(\n new Set([...(options.tools ?? []), ...(options.frontendToolIds ?? [])]),\n );\n const systemPrompt = options.systemPrompt?.trim() ?? '';\n\n return {\n custom: {\n runtime: DEEP_AGENT_RUNTIME_MARKER,\n // With a collab channel selected, omit the SDK's own 'web' stamp: the\n // request's explicit start_channel would win over the backend overlay's\n // kind-mapped provenance (a custom SMS channel must stamp 'sms').\n ...(collabChannelId ? {} : { start_channel: STATEWIRE_START_CHANNEL }),\n ...(model ? { model } : {}),\n // An empty list is behavior-identical to an absent key for the parser\n // (both coerce to []), but the collab-agent overlay merges request keys\n // OVER the agent definition — so an explicit empty list would erase the\n // agent's configured tools. Omit it when empty.\n ...(enabledTools.length > 0 ? { enabled_tools: enabledTools } : {}),\n panel_config: {\n // A collab agent's thread with no explicit model names none here\n // either: agora stores this panel on the thread's first run and\n // replays it to every client, and a stored SDK default would show as\n // the session's model while the agent's own model runs.\n ...(model ? { model } : {}),\n enabled_tools: enabledTools,\n system_prompt: systemPrompt,\n run_label: STATEWIRE_RUN_LABEL,\n },\n ...(collabAgentId ? { collab_agent_id: collabAgentId } : {}),\n ...(collabChannelId ? { collab_channel_id: collabChannelId } : {}),\n ...(collabEntryChannelId ? { collab_entry_channel_id: collabEntryChannelId } : {}),\n ...(systemPrompt ? { system_prompt: systemPrompt } : {}),\n ...(options.workbench?.length ? { workbench: options.workbench } : {}),\n ...(options.knowledgeBase?.length\n ? { knowledge_base: options.knowledgeBase }\n : {}),\n ...(options.appId ? { app_id: options.appId } : {}),\n ...(options.clientTools?.length ? { client_tools: options.clientTools } : {}),\n ...(options.extraRunConfig ?? {}),\n },\n };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"statewire-run-config.js","sources":["../../src/runtime/statewire-run-config.ts"],"sourcesContent":["/** Runtime marker the deep-agent session substrate stamps on sessions. */\nconst DEEP_AGENT_RUNTIME_MARKER = 'athena_deep_agent';\n\n/** Session-origin channel. The backend clamps unknown values to 'web', which\n * is how every SDK-started session has always classified. */\n/** The start channel every run this SDK starts is stamped with (unless a collab channel decides). */\nexport const STATEWIRE_START_CHANNEL = 'web';\n\n/** Session label persisted in the thread's panel config (mirrors\n * 'Athena Chrome' / 'Athena mobile' in the sibling statewire hosts). */\nconst STATEWIRE_RUN_LABEL = 'Athena SDK';\n\n/** Deep-agent default model — matches the Olympus and mobile statewire hosts. */\nexport const DEFAULT_STATEWIRE_MODEL = 'claude-sonnet-5-5';\n\n/** Agent-id namespace for collab agents (`collab_agent:<asset_id>`). */\nexport const COLLAB_AGENT_REF_PREFIX = 'collab_agent:';\n\n/** The only shape the `agent` prop accepts, for error messages and docs. */\nexport const COLLAB_AGENT_REF_SHAPE = 'collab_agent:<asset_id>';\n\n/** The asset id when `agent` is a `collab_agent:<asset_id>` ref, else null. */\nexport function parseCollabAgentRef(agent: string | undefined | null): string | null {\n if (typeof agent !== 'string' || !agent.startsWith(COLLAB_AGENT_REF_PREFIX)) return null;\n const assetId = agent.slice(COLLAB_AGENT_REF_PREFIX.length).trim();\n return assetId || null;\n}\n\n/**\n * Resolve the provider's `agent` prop to a collab-agent asset id.\n *\n * An omitted or blank `agent` means \"no agent selected\" — the thread runs the\n * Athena deep agent — and resolves to `null`. A `collab_agent:<asset_id>` ref\n * resolves to the asset id. Any other non-blank value throws: the statewire\n * host cannot honour it, and a selector that is silently dropped would let a\n * misconfigured deployment ship answers from the wrong agent.\n */\nexport function resolveAgentProp({ agent }: { agent: string | undefined | null }): string | null {\n if (agent === undefined || agent === null || agent.trim() === '') return null;\n const assetId = parseCollabAgentRef(agent.trim());\n if (assetId) return assetId;\n throw new Error(\n `[AthenaSDK] Unsupported \\`agent\\` prop ${JSON.stringify(agent)}. The only accepted shape is '${COLLAB_AGENT_REF_SHAPE}' (the asset id of a published collab agent). Named agents are not supported — the statewire host always runs the Athena deep agent; omit \\`agent\\` to use it.`,\n );\n}\n\nexport interface StatewireRunConfigOptions {\n /** LLM model identifier. Defaults to the deep-agent default model — unless a\n * collab agent is selected and its author set one (the backend overlay\n * applies the agent's model when the run sends none), in which case no\n * model is sent so the agent's choice wins. */\n model?: string;\n /** Agent selector. Only the `collab_agent:<asset_id>` shape is accepted: the\n * run executes AS that published collab agent (prompt, tools, model\n * default). Any other non-empty value throws (see {@link resolveAgentProp}). */\n agent?: string;\n /** Collab-agent channel id (built-in like `email`/`sms`, or a user-defined\n * custom channel id) selecting that channel's configuration overrides.\n * Sent as `collab_channel_id`; only meaningful with a collab `agent`. */\n channel?: string;\n /** The agent's mode this send runs in, picked from the chat's mode menu\n * (`AthenaProvider` `modes`): `base` or a mode's id. Sent as\n * `collab_channel_id` in place of `channel`, which then rides as\n * `collab_entry_channel_id` so a session the pick starts still enters\n * through the channel (its switch list, lock and thread list). Only\n * meaningful with a collab `agent`. */\n mode?: string;\n /** Backend toolkit IDs to enable. */\n tools?: string[];\n /** Frontend tool IDs to include in enabled_tools. */\n frontendToolIds?: string[];\n /** Asset IDs to include in the workbench. */\n workbench?: string[];\n /** Knowledge base IDs for RAG context. */\n knowledgeBase?: string[];\n /** System prompt override. */\n systemPrompt?: string;\n /** Client app identifier, passed through as app_id. */\n appId?: string;\n /** Client-executed tool declarations, passed through as client_tools. The\n * deep-agent runtime binds each as an interrupt-backed tool the client\n * answers via the statewire client-tool bridge. */\n clientTools?: Record<string, unknown>[];\n /** Additional fields merged into runConfig.custom. */\n extraRunConfig?: Record<string, unknown>;\n}\n\n/**\n * The model a send carries for the host's `model` and `extraRunConfig`, with\n * the precedence {@link buildStatewireRunConfig} applies: `extraRunConfig` is\n * merged last, so its `model` key (when it has one) wins over the `model`\n * prop. `undefined` when the send carries no explicit model.\n */\nexport function effectiveStatewireModel({\n model,\n extraRunConfig,\n}: {\n model: string | undefined;\n extraRunConfig: Record<string, unknown> | undefined;\n}): string | undefined {\n if (extraRunConfig !== undefined && Object.hasOwn(extraRunConfig, 'model')) {\n const override = extraRunConfig.model;\n return typeof override === 'string' && override !== '' ? override : undefined;\n }\n return model === '' ? undefined : model;\n}\n\n/**\n * The `start_channel` a send stamps on its run, with the precedence\n * {@link buildStatewireRunConfig} applies: the host's\n * `extraRunConfig.start_channel` wins; a selected collab channel (or mode)\n * stamps nothing, so the backend stamps that channel's own kind; otherwise the\n * SDK's own {@link STATEWIRE_START_CHANNEL}. `null` when the backend decides,\n * since the provenance is then unknown until the server reports the session.\n */\nexport function effectiveStatewireStartChannel({\n agent,\n channel,\n mode,\n extraRunConfig,\n}: Pick<StatewireRunConfigOptions, 'agent' | 'channel' | 'mode' | 'extraRunConfig'>): string | null {\n if (extraRunConfig !== undefined && Object.hasOwn(extraRunConfig, 'start_channel')) {\n const override = extraRunConfig.start_channel;\n return typeof override === 'string' && override.trim() !== '' ? override : null;\n }\n // The non-throwing parse: an agent prop of another shape makes the runtime\n // refuse to start (`resolveAgentProp`), so nothing is sent to stamp.\n const collabChannelSelected =\n parseCollabAgentRef(agent?.trim()) !== null && Boolean(mode?.trim() || channel?.trim());\n return collabChannelSelected ? null : STATEWIRE_START_CHANNEL;\n}\n\n/**\n * Build the `runConfig` a deep-agent statewire run is driven by. Commands\n * travel bare on the statewire channel, so this payload rides the ambient\n * model context (`config.runConfig`) instead of the request body.\n *\n * The flattened `custom` fields drive the run, while `panel_config` carries a\n * minimal panel state the backend persists per thread and replays on the\n * statewire snapshot — so a session started from the SDK stays configured\n * when reopened elsewhere.\n */\nexport function buildStatewireRunConfig(\n options: StatewireRunConfigOptions = {},\n): Record<string, unknown> {\n const collabAgentId = resolveAgentProp({ agent: options.agent });\n const channel = collabAgentId ? options.channel?.trim() || undefined : undefined;\n const mode = collabAgentId ? options.mode?.trim() || undefined : undefined;\n const collabChannelId = mode ?? channel;\n const collabEntryChannelId = mode && channel && mode !== channel ? channel : undefined;\n // With a collab agent selected and no explicit model, send NO model: the\n // backend overlay then applies the model the agent's author configured\n // instead of the SDK default overriding it.\n const model = options.model ?? (collabAgentId ? undefined : DEFAULT_STATEWIRE_MODEL);\n const enabledTools = Array.from(\n new Set([...(options.tools ?? []), ...(options.frontendToolIds ?? [])]),\n );\n const systemPrompt = options.systemPrompt?.trim() ?? '';\n\n return {\n custom: {\n runtime: DEEP_AGENT_RUNTIME_MARKER,\n // With a collab channel selected, omit the SDK's own 'web' stamp: the\n // request's explicit start_channel would win over the backend overlay's\n // kind-mapped provenance (a custom SMS channel must stamp 'sms').\n ...(collabChannelId ? {} : { start_channel: STATEWIRE_START_CHANNEL }),\n ...(model ? { model } : {}),\n // An empty list is behavior-identical to an absent key for the parser\n // (both coerce to []), but the collab-agent overlay merges request keys\n // OVER the agent definition — so an explicit empty list would erase the\n // agent's configured tools. Omit it when empty.\n ...(enabledTools.length > 0 ? { enabled_tools: enabledTools } : {}),\n panel_config: {\n // A collab agent's thread with no explicit model names none here\n // either: agora stores this panel on the thread's first run and\n // replays it to every client, and a stored SDK default would show as\n // the session's model while the agent's own model runs.\n ...(model ? { model } : {}),\n enabled_tools: enabledTools,\n system_prompt: systemPrompt,\n run_label: STATEWIRE_RUN_LABEL,\n },\n ...(collabAgentId ? { collab_agent_id: collabAgentId } : {}),\n ...(collabChannelId ? { collab_channel_id: collabChannelId } : {}),\n ...(collabEntryChannelId ? { collab_entry_channel_id: collabEntryChannelId } : {}),\n ...(systemPrompt ? { system_prompt: systemPrompt } : {}),\n ...(options.workbench?.length ? { workbench: options.workbench } : {}),\n ...(options.knowledgeBase?.length\n ? { knowledge_base: options.knowledgeBase }\n : {}),\n ...(options.appId ? { app_id: options.appId } : {}),\n ...(options.clientTools?.length ? { client_tools: options.clientTools } : {}),\n ...(options.extraRunConfig ?? {}),\n },\n };\n}\n"],"names":[],"mappings":"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"}
@@ -148,7 +148,7 @@ const SessionRow = ({
148
148
  const indicator = sessionListModel.sessionIndicator(thread);
149
149
  const line = sessionListModel.sessionSecondaryLine(thread);
150
150
  const chip = sessionListModel.pullRequestChip(thread.pullRequests);
151
- const origin = sessionListModel.sessionOriginBadge(thread, originBadges);
151
+ const origin = thread.isPending && !thread.startChannel ? null : sessionListModel.sessionOriginBadge(thread, originBadges);
152
152
  const title = thread.title ?? "Untitled";
153
153
  const hoverTimerRef = require$$0.useRef(null);
154
154
  const cancelHover = require$$0.useCallback(() => {
@@ -438,24 +438,27 @@ function SessionList({
438
438
  "data-group": group ?? void 0,
439
439
  children: [
440
440
  group ? /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "px-3 pb-1 font-medium text-[11px] text-muted-foreground/70 uppercase tracking-wide", children: group }) : null,
441
- /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "flex flex-col gap-0.5", children: items.map((thread) => /* @__PURE__ */ jsxRuntime.jsx(
442
- SessionRow,
443
- {
444
- thread,
445
- isActive: thread.id === list.activeThreadId,
446
- onSelect: list.selectThread,
447
- onPrefetch,
448
- onToggleFavorite,
449
- favoritePending: list.favoritePendingThreadIds.has(thread.id),
450
- onRename,
451
- renamePending: list.renamePendingThreadIds.has(thread.id),
452
- onArchive,
453
- archivePending: list.archivePendingThreadIds.has(thread.id),
454
- onOpenInNewTab,
455
- originBadges
456
- },
457
- thread.id
458
- )) })
441
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "flex flex-col gap-0.5", children: items.map((thread) => {
442
+ const known = !thread.isPending;
443
+ return /* @__PURE__ */ jsxRuntime.jsx(
444
+ SessionRow,
445
+ {
446
+ thread,
447
+ isActive: thread.id === list.activeThreadId,
448
+ onSelect: list.selectThread,
449
+ onPrefetch: known ? onPrefetch : void 0,
450
+ onToggleFavorite: known ? onToggleFavorite : void 0,
451
+ favoritePending: list.favoritePendingThreadIds.has(thread.id),
452
+ onRename: known ? onRename : void 0,
453
+ renamePending: list.renamePendingThreadIds.has(thread.id),
454
+ onArchive: known ? onArchive : void 0,
455
+ archivePending: list.archivePendingThreadIds.has(thread.id),
456
+ onOpenInNewTab: known ? onOpenInNewTab : void 0,
457
+ originBadges
458
+ },
459
+ thread.id
460
+ );
461
+ }) })
459
462
  ]
460
463
  },
461
464
  group ?? "all"
@@ -1 +1 @@
1
- {"version":3,"file":"SessionList.cjs","sources":["../../src/threads/SessionList.tsx"],"sourcesContent":["import {\n ArchiveIcon,\n ClockIcon,\n LoaderCircleIcon,\n PencilIcon,\n PinIcon,\n PlusIcon,\n SquareArrowOutUpRightIcon,\n RefreshCwIcon,\n SearchIcon,\n} from 'lucide-react';\nimport type { ChangeEvent, FC, KeyboardEvent, MouseEvent, ReactNode } from 'react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { cn } from '../lib/utils';\nimport { THREAD_TITLE_MAX_LENGTH } from './api';\nimport {\n groupSessions,\n pullRequestChip,\n type SessionIndicator as SessionIndicatorKind,\n type SessionLineTone,\n type SessionOriginBadges,\n sessionIndicator,\n sessionOriginBadge,\n sessionSecondaryLine,\n} from './session-list-model';\nimport { FOCUS_RING_CLASS, PullRequestChip, stopPropagation } from './session-row-parts';\nimport {\n type AthenaStatewireThreadSummary,\n useAthenaStatewireThreadList,\n} from './statewire-thread-list';\nimport { THREAD_PREFETCH_HOVER_DELAY_MS } from './thread-prefetcher';\nimport type { ThreadFavoriteTarget } from './use-thread-favorite';\nimport type { ThreadRenameTarget } from './use-thread-rename';\n\nexport interface SessionListProps {\n className?: string;\n /**\n * Group rows under \"Needs you\" / \"Working\" / \"Pinned\" / \"Recent\" (default\n * `true`); a session appears once, in the first group that applies. `false`\n * renders one newest-first list.\n */\n grouped?: boolean;\n /** Show the title search box, wired to the server-side `search` (default `true`). */\n showSearch?: boolean;\n /**\n * Show the \"New Chat\" and refresh buttons above the rows (default `true`).\n * `false` for a host that places its own new-session control.\n */\n showNewChat?: boolean;\n /**\n * Which rows carry an origin badge (Athena, Slack, GitHub, Chrome …):\n * `all` (default); `external`, every origin but the web app, so sessions\n * started in a browser tab of the app carry none; or `none`.\n */\n originBadges?: SessionOriginBadges;\n /**\n * Offer the pin toggle on each row (default `true`). `false` — or a backend\n * without the favorite route — shows a pinned row's pin read-only instead.\n */\n showPinToggle?: boolean;\n /**\n * Offer the inline rename on each row (default `true`). Withdrawn on a\n * backend without the rename route (`renameAvailable`).\n */\n showRename?: boolean;\n /**\n * Offer the archive action on each row (default `true`). Withdrawn on a\n * backend without the archive route (`archiveAvailable`).\n */\n showArchive?: boolean;\n /**\n * Warm the provider's snapshot cache for a row after a 120 ms hover dwell\n * and on row focus (default `true`), so selecting it paints at once.\n */\n prefetchOnHover?: boolean;\n /**\n * Open a session somewhere other than the provider's chat (a host showing\n * several chats side by side): each row offers \"Open in new tab\", and a\n * ⌘-click (Ctrl-click) or middle-click on it opens it there instead of\n * selecting it. Absent, rows only select.\n */\n onOpenInNewTab?: (target: SessionOpenTarget) => void;\n}\n\n/** The session a row hands `SessionListProps.onOpenInNewTab`. */\nexport interface SessionOpenTarget {\n threadId: string;\n /** The row's title, `null` while the session has none. */\n title: string | null;\n}\n\nconst INDICATOR_LABEL: Record<NonNullable<SessionIndicatorKind>, string> = {\n needs_you: 'Needs you',\n working: 'Working',\n unread: 'Unread',\n watching: 'Watching',\n};\n\n/**\n * The row's status indicator; an empty spacer keeps titles aligned when a\n * row has none.\n */\nconst SessionStatusIndicator: FC<{ kind: SessionIndicatorKind }> = ({ kind }) => {\n if (kind === null) return <span aria-hidden=\"true\" className=\"mt-0.5 size-4 shrink-0\" />;\n const label = INDICATOR_LABEL[kind];\n if (kind === 'working') {\n return (\n <LoaderCircleIcon\n role=\"img\"\n aria-label={label}\n data-indicator={kind}\n className=\"mt-0.5 size-4 shrink-0 animate-spin text-primary\"\n />\n );\n }\n if (kind === 'watching') {\n return (\n <ClockIcon\n role=\"img\"\n aria-label={label}\n data-indicator={kind}\n className=\"mt-0.5 size-4 shrink-0 text-muted-foreground\"\n />\n );\n }\n return (\n <span\n role=\"img\"\n aria-label={label}\n data-indicator={kind}\n className=\"mt-0.5 flex size-4 shrink-0 items-center justify-center\"\n >\n <span\n className={cn(\n 'size-2 rounded-full',\n kind === 'needs_you' ? 'bg-orange-500' : 'bg-primary',\n )}\n />\n </span>\n );\n};\n\nconst LINE_TONE_CLASS: Record<SessionLineTone, string> = {\n needs_you: 'text-orange-600',\n ready: 'text-green-600',\n error: 'text-red-600',\n muted: 'text-muted-foreground',\n};\n\n/**\n * One action in the row's trailing cluster: a sibling of the row control\n * (never nested in it — a click must not select the row), labelled for\n * assistive technology, revealed on hover or focus unless `alwaysVisible`,\n * and disabled while its write is in flight so one row never has two writes\n * racing.\n */\nconst RowActionButton: FC<{\n label: string;\n onClick: (event: MouseEvent<HTMLButtonElement>) => void;\n pending: boolean;\n alwaysVisible: boolean;\n action: string;\n pressed?: boolean;\n children: ReactNode;\n}> = ({ label, onClick, pending, alwaysVisible, action, pressed, children }) => (\n <button\n type=\"button\"\n onClick={onClick}\n disabled={pending}\n aria-busy={pending || undefined}\n aria-pressed={pressed}\n aria-label={label}\n title={label}\n data-action={action}\n data-pending={pending || undefined}\n className={cn(\n 'rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-wait disabled:opacity-60',\n FOCUS_RING_CLASS,\n !alwaysVisible &&\n !pending &&\n 'opacity-0 focus-visible:opacity-100 group-focus-within:opacity-100 group-hover:opacity-100',\n )}\n >\n {children}\n </button>\n);\n\n/**\n * The pin toggle: pressed while the session is pinned (and then always\n * visible); otherwise an outline pin revealed on hover or focus so an\n * unpinned row stays quiet.\n */\nconst PinToggle: FC<{\n thread: Pick<AthenaStatewireThreadSummary, 'id' | 'isFavorited'>;\n pending: boolean;\n onToggle: (target: ThreadFavoriteTarget) => void;\n}> = ({ thread, pending, onToggle }) => {\n const label = thread.isFavorited ? 'Unpin session' : 'Pin session';\n const handleClick = (event: MouseEvent) => {\n event.stopPropagation();\n if (pending) return;\n onToggle({ threadId: thread.id, isFavorited: !thread.isFavorited });\n };\n return (\n <span data-pin-toggle={thread.isFavorited ? 'pinned' : 'unpinned'} className=\"contents\">\n <RowActionButton\n label={label}\n onClick={handleClick}\n pending={pending}\n alwaysVisible={thread.isFavorited}\n action=\"pin\"\n pressed={thread.isFavorited}\n >\n <PinIcon className={cn('size-3.5', thread.isFavorited && 'fill-current')} aria-hidden />\n </RowActionButton>\n </span>\n );\n};\n\nconst SKELETON_TITLE_WIDTHS = ['w-3/5', 'w-4/5', 'w-1/2', 'w-2/3'] as const;\n\n/**\n * Placeholder rows shaped like session rows (indicator, title line, meta\n * line) for the first page and for a refresh that has no rows to keep yet.\n */\nexport const SessionListSkeleton: FC<{ rows?: number }> = ({ rows = 4 }) => (\n <div\n role=\"status\"\n aria-busy=\"true\"\n aria-label=\"Loading sessions\"\n data-testid=\"session-list-skeleton\"\n className=\"flex flex-col gap-0.5\"\n >\n {Array.from({ length: rows }, (_, index) => (\n <div key={index} aria-hidden=\"true\" className=\"flex items-start gap-2 px-3 py-2\">\n <span className=\"mt-0.5 size-4 shrink-0 animate-pulse rounded-full bg-muted\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex h-5 items-center\">\n <span\n className={cn(\n 'h-3.5 animate-pulse rounded bg-muted',\n SKELETON_TITLE_WIDTHS[index % SKELETON_TITLE_WIDTHS.length],\n )}\n />\n </span>\n <span className=\"mt-0.5 flex h-4 items-center\">\n <span className=\"h-3 w-1/3 animate-pulse rounded bg-muted/70\" />\n </span>\n </span>\n </div>\n ))}\n </div>\n);\n\nconst SessionRow: FC<{\n thread: AthenaStatewireThreadSummary;\n isActive: boolean;\n onSelect: (threadId: string) => void;\n /** Absent when hover prefetching is off or the provider has no snapshot cache to warm. */\n onPrefetch: ((threadId: string) => void) | undefined;\n /** Absent when the toggle is not offered: a pinned row then shows a read-only pin. */\n onToggleFavorite: ((target: ThreadFavoriteTarget) => void) | undefined;\n /** True while this session's pin write is in flight. */\n favoritePending: boolean;\n /** Absent when the rename is not offered (host opt-out, or a backend without the route). */\n onRename: ((target: ThreadRenameTarget) => void) | undefined;\n renamePending: boolean;\n /** Absent when the archive is not offered (host opt-out, or a backend without the route). */\n onArchive: ((threadId: string) => void) | undefined;\n archivePending: boolean;\n /** Absent when the host offers no other place to open a session. */\n onOpenInNewTab: ((target: SessionOpenTarget) => void) | undefined;\n originBadges: SessionOriginBadges;\n}> = ({\n thread,\n isActive,\n onSelect,\n onPrefetch,\n onToggleFavorite,\n favoritePending,\n onRename,\n renamePending,\n onArchive,\n archivePending,\n onOpenInNewTab,\n originBadges,\n}) => {\n const indicator = sessionIndicator(thread);\n const line = sessionSecondaryLine(thread);\n const chip = pullRequestChip(thread.pullRequests);\n const origin = sessionOriginBadge(thread, originBadges);\n const title = thread.title ?? 'Untitled';\n\n // Hover prefetch: the pointer must rest on the row for the dwell before\n // the fetch starts, so sweeping the pointer down the list fetches nothing.\n const hoverTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const cancelHover = useCallback(() => {\n if (hoverTimerRef.current === null) return;\n clearTimeout(hoverTimerRef.current);\n hoverTimerRef.current = null;\n }, []);\n useEffect(() => cancelHover, [cancelHover]);\n const handlePointerEnter = () => {\n if (!onPrefetch) return;\n cancelHover();\n hoverTimerRef.current = setTimeout(() => {\n hoverTimerRef.current = null;\n onPrefetch(thread.id);\n }, THREAD_PREFETCH_HOVER_DELAY_MS);\n };\n const handleFocus = () => {\n cancelHover();\n onPrefetch?.(thread.id);\n };\n\n // Inline rename: the title becomes an input; Enter commits, Escape cancels,\n // blur commits. An Enter or Escape settles the edit before the blur its\n // unmount may raise, so the blur never commits a second time.\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState('');\n const settledRef = useRef(false);\n const inputRef = useRef<HTMLInputElement | null>(null);\n useEffect(() => {\n if (!editing) return;\n inputRef.current?.focus();\n inputRef.current?.select();\n }, [editing]);\n const beginRename = (event: MouseEvent) => {\n event.stopPropagation();\n if (renamePending) return;\n settledRef.current = false;\n setDraft(thread.title ?? '');\n setEditing(true);\n };\n const commitRename = () => {\n if (settledRef.current) return;\n settledRef.current = true;\n setEditing(false);\n const next = draft.trim();\n if (!next || next === (thread.title ?? '').trim()) return;\n onRename?.({ threadId: thread.id, title: next });\n };\n const cancelRename = () => {\n settledRef.current = true;\n setEditing(false);\n };\n const handleEditorKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n commitRename();\n } else if (event.key === 'Escape') {\n event.preventDefault();\n event.stopPropagation();\n cancelRename();\n }\n };\n\n const handleArchive = (event: MouseEvent) => {\n event.stopPropagation();\n if (archivePending) return;\n onArchive?.(thread.id);\n };\n\n const openInNewTab = (event: MouseEvent) => {\n event.preventDefault();\n event.stopPropagation();\n onOpenInNewTab?.({ threadId: thread.id, title: thread.title });\n };\n // A ⌘-click (Ctrl-click) opens the row elsewhere, as a browser opens a link in a new tab.\n const handleRowClick = (event: MouseEvent) => {\n if (onOpenInNewTab && (event.metaKey || event.ctrlKey)) {\n openInNewTab(event);\n return;\n }\n onSelect(thread.id);\n };\n const handleRowAuxClick = (event: MouseEvent) => {\n if (onOpenInNewTab && event.button === 1) openInNewTab(event);\n };\n\n const hasActions = Boolean(onRename || onToggleFavorite || onArchive || onOpenInNewTab);\n const body = (\n <>\n <SessionStatusIndicator kind={indicator} />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex h-5 items-center gap-1.5\">\n {editing ? (\n <input\n ref={inputRef}\n type=\"text\"\n value={draft}\n maxLength={THREAD_TITLE_MAX_LENGTH}\n aria-label=\"Session title\"\n data-rename-input\n onChange={(event: ChangeEvent<HTMLInputElement>) => setDraft(event.target.value)}\n onKeyDown={handleEditorKeyDown}\n onBlur={commitRename}\n onClick={stopPropagation}\n className={cn(\n 'h-5 min-w-0 flex-1 rounded border border-primary/60 bg-background px-1 font-medium text-foreground text-sm',\n FOCUS_RING_CLASS,\n )}\n />\n ) : (\n <span data-title className=\"min-w-0 truncate font-medium text-sm\">\n {title}\n </span>\n )}\n {thread.isFavorited && !onToggleFavorite ? (\n <PinIcon\n role=\"img\"\n aria-label=\"Pinned\"\n className=\"size-3 shrink-0 fill-current text-muted-foreground\"\n />\n ) : null}\n {origin ? (\n <span\n data-origin={origin}\n className=\"shrink-0 rounded-full border border-border/60 px-1.5 py-px text-[10px] text-muted-foreground\"\n >\n {origin}\n </span>\n ) : null}\n </span>\n <span\n data-line={line.kind}\n className={cn('mt-0.5 block h-4 truncate font-normal text-xs leading-4', LINE_TONE_CLASS[line.tone])}\n >\n {line.text}\n </span>\n </span>\n </>\n );\n\n return (\n <li\n data-active={isActive || undefined}\n data-thread-id={thread.id}\n data-editing={editing || undefined}\n onPointerEnter={handlePointerEnter}\n onPointerLeave={cancelHover}\n className={cn(\n 'group relative flex items-start gap-1 rounded-lg text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground',\n isActive && 'bg-muted text-foreground',\n )}\n >\n {isActive ? (\n <span\n aria-hidden=\"true\"\n data-active-accent\n className=\"absolute inset-y-2 left-0 w-0.5 rounded-full bg-primary\"\n />\n ) : null}\n {editing ? (\n <div className=\"flex min-w-0 flex-1 items-start gap-2 p-2 pl-3 text-left\">{body}</div>\n ) : (\n <button\n type=\"button\"\n onClick={handleRowClick}\n onAuxClick={handleRowAuxClick}\n onFocus={handleFocus}\n className={cn(\n 'flex min-w-0 flex-1 cursor-pointer items-start gap-2 rounded-lg p-2 pl-3 text-left',\n FOCUS_RING_CLASS,\n )}\n >\n {body}\n </button>\n )}\n {chip || hasActions ? (\n <div className=\"flex shrink-0 flex-col items-end justify-between gap-0.5 py-2 pr-2\">\n {hasActions ? (\n <div data-row-actions className=\"flex h-5 items-center gap-0.5\">\n {onOpenInNewTab ? (\n <RowActionButton\n label=\"Open in new tab\"\n onClick={openInNewTab}\n pending={false}\n alwaysVisible={false}\n action=\"open-in-new-tab\"\n >\n <SquareArrowOutUpRightIcon className=\"size-3.5\" aria-hidden />\n </RowActionButton>\n ) : null}\n {onRename ? (\n <RowActionButton\n label=\"Rename session\"\n onClick={beginRename}\n pending={renamePending}\n alwaysVisible={false}\n action=\"rename\"\n >\n <PencilIcon className=\"size-3.5\" aria-hidden />\n </RowActionButton>\n ) : null}\n {onToggleFavorite ? (\n <PinToggle thread={thread} pending={favoritePending} onToggle={onToggleFavorite} />\n ) : null}\n {onArchive ? (\n <RowActionButton\n label=\"Archive session\"\n onClick={handleArchive}\n pending={archivePending}\n alwaysVisible={false}\n action=\"archive\"\n >\n <ArchiveIcon className=\"size-3.5\" aria-hidden />\n </RowActionButton>\n ) : null}\n </div>\n ) : (\n <span aria-hidden=\"true\" className=\"h-5\" />\n )}\n {chip ? <PullRequestChip chip={chip} pullRequests={thread.pullRequests ?? []} /> : null}\n </div>\n ) : null}\n </li>\n );\n};\n\n/**\n * Devin-style session list: the `ThreadList` rows with an attention\n * indicator, the server's next action, pull-request chips, an origin badge\n * and a trailing action cluster (rename inline, pin, archive) revealed on\n * hover or focus, grouped by what each session asks of the viewer. Reads the\n * thread-list state that `<AthenaProvider enableThreadList>` provides and\n * renders nothing outside that mode.\n *\n * Every write is optimistic and rolls back on failure: the pin flips the row\n * and moves it between `Pinned` and `Recent`; the rename shows the new title\n * on commit; the archive removes the row. On a backend that predates a route\n * the matching action is withdrawn (`favoriteToggleAvailable` falls back to a\n * read-only pin; `renameAvailable` / `archiveAvailable` hide the action).\n *\n * Rows warm the provider's snapshot cache after a 120 ms hover dwell (and on\n * focus) so a selection paints its transcript at once. Skeleton rows stand in\n * while the first page loads or a refresh has nothing to keep; the empty\n * state appears only after a settled empty answer, and a background refresh\n * never blanks the rows it already has.\n */\nexport function SessionList({\n className,\n grouped = true,\n showSearch = true,\n showNewChat = true,\n originBadges = 'all',\n showPinToggle = true,\n showRename = true,\n showArchive = true,\n prefetchOnHover = true,\n onOpenInNewTab,\n}: SessionListProps) {\n const list = useAthenaStatewireThreadList();\n if (!list) return null;\n\n const sections = groupSessions(list.threads, grouped);\n const hasSearch = list.search.trim().length > 0;\n const handleSearch = (event: ChangeEvent<HTMLInputElement>) =>\n list.setSearch(event.target.value);\n const onToggleFavorite =\n showPinToggle && list.favoriteToggleAvailable ? list.setFavorited : undefined;\n const onRename = showRename && list.renameAvailable ? list.renameThread : undefined;\n const onArchive = showArchive && list.archiveAvailable ? list.archiveThread : undefined;\n const onPrefetch = prefetchOnHover ? list.prefetchThread : undefined;\n // Skeleton until the first page has answered (no credential yet, or the\n // page in flight) and for a refresh with no rows to keep; a refresh that\n // has rows keeps showing them.\n const showSkeleton =\n !list.hasResolvedFirstPage || (list.isFetching && list.threads.length === 0);\n\n return (\n <div\n className={cn('athena-sdk-chrome flex flex-col gap-2', className)}\n data-testid=\"session-list\"\n >\n {showNewChat ? (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={list.newThread}\n className={cn(\n 'flex h-9 flex-1 items-center gap-2 rounded-lg border border-border/60 px-3 font-medium text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground',\n FOCUS_RING_CLASS,\n )}\n >\n <PlusIcon className=\"size-4\" />\n New Chat\n </button>\n <button\n type=\"button\"\n onClick={list.refresh}\n disabled={list.isFetching}\n className={cn(\n 'flex size-9 items-center justify-center rounded-lg border border-border/60 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50',\n FOCUS_RING_CLASS,\n )}\n title=\"Refresh sessions\"\n aria-label=\"Refresh sessions\"\n >\n <RefreshCwIcon className={cn('size-4', list.isFetching && 'animate-spin')} />\n </button>\n </div>\n ) : null}\n\n {showSearch ? (\n <label className=\"relative block\">\n <SearchIcon className=\"pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <span className=\"sr-only\">Search sessions</span>\n <input\n type=\"search\"\n value={list.search}\n onChange={handleSearch}\n placeholder=\"Search sessions…\"\n className=\"h-8 w-full rounded-lg border border-border/60 bg-muted/40 pr-2 pl-8 text-sm outline-none focus:border-primary focus:bg-background\"\n />\n </label>\n ) : null}\n\n {showSkeleton ? (\n <SessionListSkeleton />\n ) : sections.length === 0 ? (\n <p data-testid=\"session-list-empty\" className=\"px-3 py-4 text-muted-foreground text-xs\">\n {hasSearch ? 'No matching sessions.' : 'No sessions yet.'}\n </p>\n ) : (\n <div className=\"flex flex-col gap-2\" aria-busy={list.isFetching || undefined}>\n {sections.map(({ group, items }) => (\n <section\n key={group ?? 'all'}\n aria-label={group ?? 'Sessions'}\n data-group={group ?? undefined}\n >\n {group ? (\n <h3 className=\"px-3 pb-1 font-medium text-[11px] text-muted-foreground/70 uppercase tracking-wide\">\n {group}\n </h3>\n ) : null}\n <ul className=\"flex flex-col gap-0.5\">\n {items.map((thread) => (\n <SessionRow\n key={thread.id}\n thread={thread}\n isActive={thread.id === list.activeThreadId}\n onSelect={list.selectThread}\n onPrefetch={onPrefetch}\n onToggleFavorite={onToggleFavorite}\n favoritePending={list.favoritePendingThreadIds.has(thread.id)}\n onRename={onRename}\n renamePending={list.renamePendingThreadIds.has(thread.id)}\n onArchive={onArchive}\n archivePending={list.archivePendingThreadIds.has(thread.id)}\n onOpenInNewTab={onOpenInNewTab}\n originBadges={originBadges}\n />\n ))}\n </ul>\n </section>\n ))}\n </div>\n )}\n\n {list.hasNextPage && !showSkeleton ? (\n <button\n type=\"button\"\n disabled={list.isFetchingNextPage}\n onClick={list.fetchNextPage}\n className={cn(\n 'w-full rounded-lg py-2 text-muted-foreground text-xs transition-colors hover:bg-muted disabled:opacity-50',\n FOCUS_RING_CLASS,\n )}\n >\n {list.isFetchingNextPage ? 'Loading…' : 'Load more'}\n </button>\n ) : null}\n </div>\n );\n}\n"],"names":["jsx","LoaderCircleIcon","ClockIcon","cn","FOCUS_RING_CLASS","PinIcon","jsxs","sessionIndicator","sessionSecondaryLine","pullRequestChip","sessionOriginBadge","useRef","useCallback","useEffect","THREAD_PREFETCH_HOVER_DELAY_MS","useState","Fragment","THREAD_TITLE_MAX_LENGTH","stopPropagation","SquareArrowOutUpRightIcon","PencilIcon","ArchiveIcon","PullRequestChip","useAthenaStatewireThreadList","groupSessions","PlusIcon","RefreshCwIcon","SearchIcon"],"mappings":"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"}
1
+ {"version":3,"file":"SessionList.cjs","sources":["../../src/threads/SessionList.tsx"],"sourcesContent":["import {\n ArchiveIcon,\n ClockIcon,\n LoaderCircleIcon,\n PencilIcon,\n PinIcon,\n PlusIcon,\n SquareArrowOutUpRightIcon,\n RefreshCwIcon,\n SearchIcon,\n} from 'lucide-react';\nimport type { ChangeEvent, FC, KeyboardEvent, MouseEvent, ReactNode } from 'react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { cn } from '../lib/utils';\nimport { THREAD_TITLE_MAX_LENGTH } from './api';\nimport {\n groupSessions,\n pullRequestChip,\n type SessionIndicator as SessionIndicatorKind,\n type SessionLineTone,\n type SessionOriginBadges,\n sessionIndicator,\n sessionOriginBadge,\n sessionSecondaryLine,\n} from './session-list-model';\nimport { FOCUS_RING_CLASS, PullRequestChip, stopPropagation } from './session-row-parts';\nimport {\n type AthenaStatewireThreadSummary,\n useAthenaStatewireThreadList,\n} from './statewire-thread-list';\nimport { THREAD_PREFETCH_HOVER_DELAY_MS } from './thread-prefetcher';\nimport type { ThreadFavoriteTarget } from './use-thread-favorite';\nimport type { ThreadRenameTarget } from './use-thread-rename';\n\nexport interface SessionListProps {\n className?: string;\n /**\n * Group rows under \"Needs you\" / \"Working\" / \"Pinned\" / \"Recent\" (default\n * `true`); a session appears once, in the first group that applies. `false`\n * renders one newest-first list.\n */\n grouped?: boolean;\n /** Show the title search box, wired to the server-side `search` (default `true`). */\n showSearch?: boolean;\n /**\n * Show the \"New Chat\" and refresh buttons above the rows (default `true`).\n * `false` for a host that places its own new-session control.\n */\n showNewChat?: boolean;\n /**\n * Which rows carry an origin badge (Athena, Slack, GitHub, Chrome …):\n * `all` (default); `external`, every origin but the web app, so sessions\n * started in a browser tab of the app carry none; or `none`.\n */\n originBadges?: SessionOriginBadges;\n /**\n * Offer the pin toggle on each row (default `true`). `false` — or a backend\n * without the favorite route — shows a pinned row's pin read-only instead.\n */\n showPinToggle?: boolean;\n /**\n * Offer the inline rename on each row (default `true`). Withdrawn on a\n * backend without the rename route (`renameAvailable`).\n */\n showRename?: boolean;\n /**\n * Offer the archive action on each row (default `true`). Withdrawn on a\n * backend without the archive route (`archiveAvailable`).\n */\n showArchive?: boolean;\n /**\n * Warm the provider's snapshot cache for a row after a 120 ms hover dwell\n * and on row focus (default `true`), so selecting it paints at once.\n */\n prefetchOnHover?: boolean;\n /**\n * Open a session somewhere other than the provider's chat (a host showing\n * several chats side by side): each row offers \"Open in new tab\", and a\n * ⌘-click (Ctrl-click) or middle-click on it opens it there instead of\n * selecting it. Absent, rows only select.\n */\n onOpenInNewTab?: (target: SessionOpenTarget) => void;\n}\n\n/** The session a row hands `SessionListProps.onOpenInNewTab`. */\nexport interface SessionOpenTarget {\n threadId: string;\n /** The row's title, `null` while the session has none. */\n title: string | null;\n}\n\nconst INDICATOR_LABEL: Record<NonNullable<SessionIndicatorKind>, string> = {\n needs_you: 'Needs you',\n working: 'Working',\n unread: 'Unread',\n watching: 'Watching',\n};\n\n/**\n * The row's status indicator; an empty spacer keeps titles aligned when a\n * row has none.\n */\nconst SessionStatusIndicator: FC<{ kind: SessionIndicatorKind }> = ({ kind }) => {\n if (kind === null) return <span aria-hidden=\"true\" className=\"mt-0.5 size-4 shrink-0\" />;\n const label = INDICATOR_LABEL[kind];\n if (kind === 'working') {\n return (\n <LoaderCircleIcon\n role=\"img\"\n aria-label={label}\n data-indicator={kind}\n className=\"mt-0.5 size-4 shrink-0 animate-spin text-primary\"\n />\n );\n }\n if (kind === 'watching') {\n return (\n <ClockIcon\n role=\"img\"\n aria-label={label}\n data-indicator={kind}\n className=\"mt-0.5 size-4 shrink-0 text-muted-foreground\"\n />\n );\n }\n return (\n <span\n role=\"img\"\n aria-label={label}\n data-indicator={kind}\n className=\"mt-0.5 flex size-4 shrink-0 items-center justify-center\"\n >\n <span\n className={cn(\n 'size-2 rounded-full',\n kind === 'needs_you' ? 'bg-orange-500' : 'bg-primary',\n )}\n />\n </span>\n );\n};\n\nconst LINE_TONE_CLASS: Record<SessionLineTone, string> = {\n needs_you: 'text-orange-600',\n ready: 'text-green-600',\n error: 'text-red-600',\n muted: 'text-muted-foreground',\n};\n\n/**\n * One action in the row's trailing cluster: a sibling of the row control\n * (never nested in it — a click must not select the row), labelled for\n * assistive technology, revealed on hover or focus unless `alwaysVisible`,\n * and disabled while its write is in flight so one row never has two writes\n * racing.\n */\nconst RowActionButton: FC<{\n label: string;\n onClick: (event: MouseEvent<HTMLButtonElement>) => void;\n pending: boolean;\n alwaysVisible: boolean;\n action: string;\n pressed?: boolean;\n children: ReactNode;\n}> = ({ label, onClick, pending, alwaysVisible, action, pressed, children }) => (\n <button\n type=\"button\"\n onClick={onClick}\n disabled={pending}\n aria-busy={pending || undefined}\n aria-pressed={pressed}\n aria-label={label}\n title={label}\n data-action={action}\n data-pending={pending || undefined}\n className={cn(\n 'rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-wait disabled:opacity-60',\n FOCUS_RING_CLASS,\n !alwaysVisible &&\n !pending &&\n 'opacity-0 focus-visible:opacity-100 group-focus-within:opacity-100 group-hover:opacity-100',\n )}\n >\n {children}\n </button>\n);\n\n/**\n * The pin toggle: pressed while the session is pinned (and then always\n * visible); otherwise an outline pin revealed on hover or focus so an\n * unpinned row stays quiet.\n */\nconst PinToggle: FC<{\n thread: Pick<AthenaStatewireThreadSummary, 'id' | 'isFavorited'>;\n pending: boolean;\n onToggle: (target: ThreadFavoriteTarget) => void;\n}> = ({ thread, pending, onToggle }) => {\n const label = thread.isFavorited ? 'Unpin session' : 'Pin session';\n const handleClick = (event: MouseEvent) => {\n event.stopPropagation();\n if (pending) return;\n onToggle({ threadId: thread.id, isFavorited: !thread.isFavorited });\n };\n return (\n <span data-pin-toggle={thread.isFavorited ? 'pinned' : 'unpinned'} className=\"contents\">\n <RowActionButton\n label={label}\n onClick={handleClick}\n pending={pending}\n alwaysVisible={thread.isFavorited}\n action=\"pin\"\n pressed={thread.isFavorited}\n >\n <PinIcon className={cn('size-3.5', thread.isFavorited && 'fill-current')} aria-hidden />\n </RowActionButton>\n </span>\n );\n};\n\nconst SKELETON_TITLE_WIDTHS = ['w-3/5', 'w-4/5', 'w-1/2', 'w-2/3'] as const;\n\n/**\n * Placeholder rows shaped like session rows (indicator, title line, meta\n * line) for the first page and for a refresh that has no rows to keep yet.\n */\nexport const SessionListSkeleton: FC<{ rows?: number }> = ({ rows = 4 }) => (\n <div\n role=\"status\"\n aria-busy=\"true\"\n aria-label=\"Loading sessions\"\n data-testid=\"session-list-skeleton\"\n className=\"flex flex-col gap-0.5\"\n >\n {Array.from({ length: rows }, (_, index) => (\n <div key={index} aria-hidden=\"true\" className=\"flex items-start gap-2 px-3 py-2\">\n <span className=\"mt-0.5 size-4 shrink-0 animate-pulse rounded-full bg-muted\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex h-5 items-center\">\n <span\n className={cn(\n 'h-3.5 animate-pulse rounded bg-muted',\n SKELETON_TITLE_WIDTHS[index % SKELETON_TITLE_WIDTHS.length],\n )}\n />\n </span>\n <span className=\"mt-0.5 flex h-4 items-center\">\n <span className=\"h-3 w-1/3 animate-pulse rounded bg-muted/70\" />\n </span>\n </span>\n </div>\n ))}\n </div>\n);\n\nconst SessionRow: FC<{\n thread: AthenaStatewireThreadSummary;\n isActive: boolean;\n onSelect: (threadId: string) => void;\n /** Absent when hover prefetching is off or the provider has no snapshot cache to warm. */\n onPrefetch: ((threadId: string) => void) | undefined;\n /** Absent when the toggle is not offered: a pinned row then shows a read-only pin. */\n onToggleFavorite: ((target: ThreadFavoriteTarget) => void) | undefined;\n /** True while this session's pin write is in flight. */\n favoritePending: boolean;\n /** Absent when the rename is not offered (host opt-out, or a backend without the route). */\n onRename: ((target: ThreadRenameTarget) => void) | undefined;\n renamePending: boolean;\n /** Absent when the archive is not offered (host opt-out, or a backend without the route). */\n onArchive: ((threadId: string) => void) | undefined;\n archivePending: boolean;\n /** Absent when the host offers no other place to open a session. */\n onOpenInNewTab: ((target: SessionOpenTarget) => void) | undefined;\n originBadges: SessionOriginBadges;\n}> = ({\n thread,\n isActive,\n onSelect,\n onPrefetch,\n onToggleFavorite,\n favoritePending,\n onRename,\n renamePending,\n onArchive,\n archivePending,\n onOpenInNewTab,\n originBadges,\n}) => {\n const indicator = sessionIndicator(thread);\n const line = sessionSecondaryLine(thread);\n const chip = pullRequestChip(thread.pullRequests);\n // A new chat's pending row whose provenance the backend decides (a collab channel stamps its\n // own kind) shows no badge until the server's row brings the real one.\n const origin =\n thread.isPending && !thread.startChannel ? null : sessionOriginBadge(thread, originBadges);\n const title = thread.title ?? 'Untitled';\n\n // Hover prefetch: the pointer must rest on the row for the dwell before\n // the fetch starts, so sweeping the pointer down the list fetches nothing.\n const hoverTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const cancelHover = useCallback(() => {\n if (hoverTimerRef.current === null) return;\n clearTimeout(hoverTimerRef.current);\n hoverTimerRef.current = null;\n }, []);\n useEffect(() => cancelHover, [cancelHover]);\n const handlePointerEnter = () => {\n if (!onPrefetch) return;\n cancelHover();\n hoverTimerRef.current = setTimeout(() => {\n hoverTimerRef.current = null;\n onPrefetch(thread.id);\n }, THREAD_PREFETCH_HOVER_DELAY_MS);\n };\n const handleFocus = () => {\n cancelHover();\n onPrefetch?.(thread.id);\n };\n\n // Inline rename: the title becomes an input; Enter commits, Escape cancels,\n // blur commits. An Enter or Escape settles the edit before the blur its\n // unmount may raise, so the blur never commits a second time.\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState('');\n const settledRef = useRef(false);\n const inputRef = useRef<HTMLInputElement | null>(null);\n useEffect(() => {\n if (!editing) return;\n inputRef.current?.focus();\n inputRef.current?.select();\n }, [editing]);\n const beginRename = (event: MouseEvent) => {\n event.stopPropagation();\n if (renamePending) return;\n settledRef.current = false;\n setDraft(thread.title ?? '');\n setEditing(true);\n };\n const commitRename = () => {\n if (settledRef.current) return;\n settledRef.current = true;\n setEditing(false);\n const next = draft.trim();\n if (!next || next === (thread.title ?? '').trim()) return;\n onRename?.({ threadId: thread.id, title: next });\n };\n const cancelRename = () => {\n settledRef.current = true;\n setEditing(false);\n };\n const handleEditorKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n commitRename();\n } else if (event.key === 'Escape') {\n event.preventDefault();\n event.stopPropagation();\n cancelRename();\n }\n };\n\n const handleArchive = (event: MouseEvent) => {\n event.stopPropagation();\n if (archivePending) return;\n onArchive?.(thread.id);\n };\n\n const openInNewTab = (event: MouseEvent) => {\n event.preventDefault();\n event.stopPropagation();\n onOpenInNewTab?.({ threadId: thread.id, title: thread.title });\n };\n // A ⌘-click (Ctrl-click) opens the row elsewhere, as a browser opens a link in a new tab.\n const handleRowClick = (event: MouseEvent) => {\n if (onOpenInNewTab && (event.metaKey || event.ctrlKey)) {\n openInNewTab(event);\n return;\n }\n onSelect(thread.id);\n };\n const handleRowAuxClick = (event: MouseEvent) => {\n if (onOpenInNewTab && event.button === 1) openInNewTab(event);\n };\n\n const hasActions = Boolean(onRename || onToggleFavorite || onArchive || onOpenInNewTab);\n const body = (\n <>\n <SessionStatusIndicator kind={indicator} />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex h-5 items-center gap-1.5\">\n {editing ? (\n <input\n ref={inputRef}\n type=\"text\"\n value={draft}\n maxLength={THREAD_TITLE_MAX_LENGTH}\n aria-label=\"Session title\"\n data-rename-input\n onChange={(event: ChangeEvent<HTMLInputElement>) => setDraft(event.target.value)}\n onKeyDown={handleEditorKeyDown}\n onBlur={commitRename}\n onClick={stopPropagation}\n className={cn(\n 'h-5 min-w-0 flex-1 rounded border border-primary/60 bg-background px-1 font-medium text-foreground text-sm',\n FOCUS_RING_CLASS,\n )}\n />\n ) : (\n <span data-title className=\"min-w-0 truncate font-medium text-sm\">\n {title}\n </span>\n )}\n {thread.isFavorited && !onToggleFavorite ? (\n <PinIcon\n role=\"img\"\n aria-label=\"Pinned\"\n className=\"size-3 shrink-0 fill-current text-muted-foreground\"\n />\n ) : null}\n {origin ? (\n <span\n data-origin={origin}\n className=\"shrink-0 rounded-full border border-border/60 px-1.5 py-px text-[10px] text-muted-foreground\"\n >\n {origin}\n </span>\n ) : null}\n </span>\n <span\n data-line={line.kind}\n className={cn('mt-0.5 block h-4 truncate font-normal text-xs leading-4', LINE_TONE_CLASS[line.tone])}\n >\n {line.text}\n </span>\n </span>\n </>\n );\n\n return (\n <li\n data-active={isActive || undefined}\n data-thread-id={thread.id}\n data-editing={editing || undefined}\n onPointerEnter={handlePointerEnter}\n onPointerLeave={cancelHover}\n className={cn(\n 'group relative flex items-start gap-1 rounded-lg text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground',\n isActive && 'bg-muted text-foreground',\n )}\n >\n {isActive ? (\n <span\n aria-hidden=\"true\"\n data-active-accent\n className=\"absolute inset-y-2 left-0 w-0.5 rounded-full bg-primary\"\n />\n ) : null}\n {editing ? (\n <div className=\"flex min-w-0 flex-1 items-start gap-2 p-2 pl-3 text-left\">{body}</div>\n ) : (\n <button\n type=\"button\"\n onClick={handleRowClick}\n onAuxClick={handleRowAuxClick}\n onFocus={handleFocus}\n className={cn(\n 'flex min-w-0 flex-1 cursor-pointer items-start gap-2 rounded-lg p-2 pl-3 text-left',\n FOCUS_RING_CLASS,\n )}\n >\n {body}\n </button>\n )}\n {chip || hasActions ? (\n <div className=\"flex shrink-0 flex-col items-end justify-between gap-0.5 py-2 pr-2\">\n {hasActions ? (\n <div data-row-actions className=\"flex h-5 items-center gap-0.5\">\n {onOpenInNewTab ? (\n <RowActionButton\n label=\"Open in new tab\"\n onClick={openInNewTab}\n pending={false}\n alwaysVisible={false}\n action=\"open-in-new-tab\"\n >\n <SquareArrowOutUpRightIcon className=\"size-3.5\" aria-hidden />\n </RowActionButton>\n ) : null}\n {onRename ? (\n <RowActionButton\n label=\"Rename session\"\n onClick={beginRename}\n pending={renamePending}\n alwaysVisible={false}\n action=\"rename\"\n >\n <PencilIcon className=\"size-3.5\" aria-hidden />\n </RowActionButton>\n ) : null}\n {onToggleFavorite ? (\n <PinToggle thread={thread} pending={favoritePending} onToggle={onToggleFavorite} />\n ) : null}\n {onArchive ? (\n <RowActionButton\n label=\"Archive session\"\n onClick={handleArchive}\n pending={archivePending}\n alwaysVisible={false}\n action=\"archive\"\n >\n <ArchiveIcon className=\"size-3.5\" aria-hidden />\n </RowActionButton>\n ) : null}\n </div>\n ) : (\n <span aria-hidden=\"true\" className=\"h-5\" />\n )}\n {chip ? <PullRequestChip chip={chip} pullRequests={thread.pullRequests ?? []} /> : null}\n </div>\n ) : null}\n </li>\n );\n};\n\n/**\n * Devin-style session list: the `ThreadList` rows with an attention\n * indicator, the server's next action, pull-request chips, an origin badge\n * and a trailing action cluster (rename inline, pin, archive) revealed on\n * hover or focus, grouped by what each session asks of the viewer. Reads the\n * thread-list state that `<AthenaProvider enableThreadList>` provides and\n * renders nothing outside that mode.\n *\n * Every write is optimistic and rolls back on failure: the pin flips the row\n * and moves it between `Pinned` and `Recent`; the rename shows the new title\n * on commit; the archive removes the row. On a backend that predates a route\n * the matching action is withdrawn (`favoriteToggleAvailable` falls back to a\n * read-only pin; `renameAvailable` / `archiveAvailable` hide the action).\n *\n * Rows warm the provider's snapshot cache after a 120 ms hover dwell (and on\n * focus) so a selection paints its transcript at once. Skeleton rows stand in\n * while the first page loads or a refresh has nothing to keep; the empty\n * state appears only after a settled empty answer, and a background refresh\n * never blanks the rows it already has.\n */\nexport function SessionList({\n className,\n grouped = true,\n showSearch = true,\n showNewChat = true,\n originBadges = 'all',\n showPinToggle = true,\n showRename = true,\n showArchive = true,\n prefetchOnHover = true,\n onOpenInNewTab,\n}: SessionListProps) {\n const list = useAthenaStatewireThreadList();\n if (!list) return null;\n\n const sections = groupSessions(list.threads, grouped);\n const hasSearch = list.search.trim().length > 0;\n const handleSearch = (event: ChangeEvent<HTMLInputElement>) =>\n list.setSearch(event.target.value);\n const onToggleFavorite =\n showPinToggle && list.favoriteToggleAvailable ? list.setFavorited : undefined;\n const onRename = showRename && list.renameAvailable ? list.renameThread : undefined;\n const onArchive = showArchive && list.archiveAvailable ? list.archiveThread : undefined;\n const onPrefetch = prefetchOnHover ? list.prefetchThread : undefined;\n // Skeleton until the first page has answered (no credential yet, or the\n // page in flight) and for a refresh with no rows to keep; a refresh that\n // has rows keeps showing them.\n const showSkeleton =\n !list.hasResolvedFirstPage || (list.isFetching && list.threads.length === 0);\n\n return (\n <div\n className={cn('athena-sdk-chrome flex flex-col gap-2', className)}\n data-testid=\"session-list\"\n >\n {showNewChat ? (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={list.newThread}\n className={cn(\n 'flex h-9 flex-1 items-center gap-2 rounded-lg border border-border/60 px-3 font-medium text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground',\n FOCUS_RING_CLASS,\n )}\n >\n <PlusIcon className=\"size-4\" />\n New Chat\n </button>\n <button\n type=\"button\"\n onClick={list.refresh}\n disabled={list.isFetching}\n className={cn(\n 'flex size-9 items-center justify-center rounded-lg border border-border/60 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50',\n FOCUS_RING_CLASS,\n )}\n title=\"Refresh sessions\"\n aria-label=\"Refresh sessions\"\n >\n <RefreshCwIcon className={cn('size-4', list.isFetching && 'animate-spin')} />\n </button>\n </div>\n ) : null}\n\n {showSearch ? (\n <label className=\"relative block\">\n <SearchIcon className=\"pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <span className=\"sr-only\">Search sessions</span>\n <input\n type=\"search\"\n value={list.search}\n onChange={handleSearch}\n placeholder=\"Search sessions…\"\n className=\"h-8 w-full rounded-lg border border-border/60 bg-muted/40 pr-2 pl-8 text-sm outline-none focus:border-primary focus:bg-background\"\n />\n </label>\n ) : null}\n\n {showSkeleton ? (\n <SessionListSkeleton />\n ) : sections.length === 0 ? (\n <p data-testid=\"session-list-empty\" className=\"px-3 py-4 text-muted-foreground text-xs\">\n {hasSearch ? 'No matching sessions.' : 'No sessions yet.'}\n </p>\n ) : (\n <div className=\"flex flex-col gap-2\" aria-busy={list.isFetching || undefined}>\n {sections.map(({ group, items }) => (\n <section\n key={group ?? 'all'}\n aria-label={group ?? 'Sessions'}\n data-group={group ?? undefined}\n >\n {group ? (\n <h3 className=\"px-3 pb-1 font-medium text-[11px] text-muted-foreground/70 uppercase tracking-wide\">\n {group}\n </h3>\n ) : null}\n <ul className=\"flex flex-col gap-0.5\">\n {items.map((thread) => {\n // A pending row is a chat the server does not know yet: nothing to\n // prefetch, pin, rename, archive or open elsewhere until its own row lands.\n const known = !thread.isPending;\n return (\n <SessionRow\n key={thread.id}\n thread={thread}\n isActive={thread.id === list.activeThreadId}\n onSelect={list.selectThread}\n onPrefetch={known ? onPrefetch : undefined}\n onToggleFavorite={known ? onToggleFavorite : undefined}\n favoritePending={list.favoritePendingThreadIds.has(thread.id)}\n onRename={known ? onRename : undefined}\n renamePending={list.renamePendingThreadIds.has(thread.id)}\n onArchive={known ? onArchive : undefined}\n archivePending={list.archivePendingThreadIds.has(thread.id)}\n onOpenInNewTab={known ? onOpenInNewTab : undefined}\n originBadges={originBadges}\n />\n );\n })}\n </ul>\n </section>\n ))}\n </div>\n )}\n\n {list.hasNextPage && !showSkeleton ? (\n <button\n type=\"button\"\n disabled={list.isFetchingNextPage}\n onClick={list.fetchNextPage}\n className={cn(\n 'w-full rounded-lg py-2 text-muted-foreground text-xs transition-colors hover:bg-muted disabled:opacity-50',\n FOCUS_RING_CLASS,\n )}\n >\n {list.isFetchingNextPage ? 'Loading…' : 'Load more'}\n </button>\n ) : null}\n </div>\n );\n}\n"],"names":["jsx","LoaderCircleIcon","ClockIcon","cn","FOCUS_RING_CLASS","PinIcon","jsxs","sessionIndicator","sessionSecondaryLine","pullRequestChip","sessionOriginBadge","useRef","useCallback","useEffect","THREAD_PREFETCH_HOVER_DELAY_MS","useState","Fragment","THREAD_TITLE_MAX_LENGTH","stopPropagation","SquareArrowOutUpRightIcon","PencilIcon","ArchiveIcon","PullRequestChip","useAthenaStatewireThreadList","groupSessions","PlusIcon","RefreshCwIcon","SearchIcon"],"mappings":"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"}
@@ -146,7 +146,7 @@ const SessionRow = ({
146
146
  const indicator = sessionIndicator(thread);
147
147
  const line = sessionSecondaryLine(thread);
148
148
  const chip = pullRequestChip(thread.pullRequests);
149
- const origin = sessionOriginBadge(thread, originBadges);
149
+ const origin = thread.isPending && !thread.startChannel ? null : sessionOriginBadge(thread, originBadges);
150
150
  const title = thread.title ?? "Untitled";
151
151
  const hoverTimerRef = useRef(null);
152
152
  const cancelHover = useCallback(() => {
@@ -436,24 +436,27 @@ function SessionList({
436
436
  "data-group": group ?? void 0,
437
437
  children: [
438
438
  group ? /* @__PURE__ */ jsx("h3", { className: "px-3 pb-1 font-medium text-[11px] text-muted-foreground/70 uppercase tracking-wide", children: group }) : null,
439
- /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-0.5", children: items.map((thread) => /* @__PURE__ */ jsx(
440
- SessionRow,
441
- {
442
- thread,
443
- isActive: thread.id === list.activeThreadId,
444
- onSelect: list.selectThread,
445
- onPrefetch,
446
- onToggleFavorite,
447
- favoritePending: list.favoritePendingThreadIds.has(thread.id),
448
- onRename,
449
- renamePending: list.renamePendingThreadIds.has(thread.id),
450
- onArchive,
451
- archivePending: list.archivePendingThreadIds.has(thread.id),
452
- onOpenInNewTab,
453
- originBadges
454
- },
455
- thread.id
456
- )) })
439
+ /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-0.5", children: items.map((thread) => {
440
+ const known = !thread.isPending;
441
+ return /* @__PURE__ */ jsx(
442
+ SessionRow,
443
+ {
444
+ thread,
445
+ isActive: thread.id === list.activeThreadId,
446
+ onSelect: list.selectThread,
447
+ onPrefetch: known ? onPrefetch : void 0,
448
+ onToggleFavorite: known ? onToggleFavorite : void 0,
449
+ favoritePending: list.favoritePendingThreadIds.has(thread.id),
450
+ onRename: known ? onRename : void 0,
451
+ renamePending: list.renamePendingThreadIds.has(thread.id),
452
+ onArchive: known ? onArchive : void 0,
453
+ archivePending: list.archivePendingThreadIds.has(thread.id),
454
+ onOpenInNewTab: known ? onOpenInNewTab : void 0,
455
+ originBadges
456
+ },
457
+ thread.id
458
+ );
459
+ }) })
457
460
  ]
458
461
  },
459
462
  group ?? "all"
@@ -1 +1 @@
1
- {"version":3,"file":"SessionList.js","sources":["../../src/threads/SessionList.tsx"],"sourcesContent":["import {\n ArchiveIcon,\n ClockIcon,\n LoaderCircleIcon,\n PencilIcon,\n PinIcon,\n PlusIcon,\n SquareArrowOutUpRightIcon,\n RefreshCwIcon,\n SearchIcon,\n} from 'lucide-react';\nimport type { ChangeEvent, FC, KeyboardEvent, MouseEvent, ReactNode } from 'react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { cn } from '../lib/utils';\nimport { THREAD_TITLE_MAX_LENGTH } from './api';\nimport {\n groupSessions,\n pullRequestChip,\n type SessionIndicator as SessionIndicatorKind,\n type SessionLineTone,\n type SessionOriginBadges,\n sessionIndicator,\n sessionOriginBadge,\n sessionSecondaryLine,\n} from './session-list-model';\nimport { FOCUS_RING_CLASS, PullRequestChip, stopPropagation } from './session-row-parts';\nimport {\n type AthenaStatewireThreadSummary,\n useAthenaStatewireThreadList,\n} from './statewire-thread-list';\nimport { THREAD_PREFETCH_HOVER_DELAY_MS } from './thread-prefetcher';\nimport type { ThreadFavoriteTarget } from './use-thread-favorite';\nimport type { ThreadRenameTarget } from './use-thread-rename';\n\nexport interface SessionListProps {\n className?: string;\n /**\n * Group rows under \"Needs you\" / \"Working\" / \"Pinned\" / \"Recent\" (default\n * `true`); a session appears once, in the first group that applies. `false`\n * renders one newest-first list.\n */\n grouped?: boolean;\n /** Show the title search box, wired to the server-side `search` (default `true`). */\n showSearch?: boolean;\n /**\n * Show the \"New Chat\" and refresh buttons above the rows (default `true`).\n * `false` for a host that places its own new-session control.\n */\n showNewChat?: boolean;\n /**\n * Which rows carry an origin badge (Athena, Slack, GitHub, Chrome …):\n * `all` (default); `external`, every origin but the web app, so sessions\n * started in a browser tab of the app carry none; or `none`.\n */\n originBadges?: SessionOriginBadges;\n /**\n * Offer the pin toggle on each row (default `true`). `false` — or a backend\n * without the favorite route — shows a pinned row's pin read-only instead.\n */\n showPinToggle?: boolean;\n /**\n * Offer the inline rename on each row (default `true`). Withdrawn on a\n * backend without the rename route (`renameAvailable`).\n */\n showRename?: boolean;\n /**\n * Offer the archive action on each row (default `true`). Withdrawn on a\n * backend without the archive route (`archiveAvailable`).\n */\n showArchive?: boolean;\n /**\n * Warm the provider's snapshot cache for a row after a 120 ms hover dwell\n * and on row focus (default `true`), so selecting it paints at once.\n */\n prefetchOnHover?: boolean;\n /**\n * Open a session somewhere other than the provider's chat (a host showing\n * several chats side by side): each row offers \"Open in new tab\", and a\n * ⌘-click (Ctrl-click) or middle-click on it opens it there instead of\n * selecting it. Absent, rows only select.\n */\n onOpenInNewTab?: (target: SessionOpenTarget) => void;\n}\n\n/** The session a row hands `SessionListProps.onOpenInNewTab`. */\nexport interface SessionOpenTarget {\n threadId: string;\n /** The row's title, `null` while the session has none. */\n title: string | null;\n}\n\nconst INDICATOR_LABEL: Record<NonNullable<SessionIndicatorKind>, string> = {\n needs_you: 'Needs you',\n working: 'Working',\n unread: 'Unread',\n watching: 'Watching',\n};\n\n/**\n * The row's status indicator; an empty spacer keeps titles aligned when a\n * row has none.\n */\nconst SessionStatusIndicator: FC<{ kind: SessionIndicatorKind }> = ({ kind }) => {\n if (kind === null) return <span aria-hidden=\"true\" className=\"mt-0.5 size-4 shrink-0\" />;\n const label = INDICATOR_LABEL[kind];\n if (kind === 'working') {\n return (\n <LoaderCircleIcon\n role=\"img\"\n aria-label={label}\n data-indicator={kind}\n className=\"mt-0.5 size-4 shrink-0 animate-spin text-primary\"\n />\n );\n }\n if (kind === 'watching') {\n return (\n <ClockIcon\n role=\"img\"\n aria-label={label}\n data-indicator={kind}\n className=\"mt-0.5 size-4 shrink-0 text-muted-foreground\"\n />\n );\n }\n return (\n <span\n role=\"img\"\n aria-label={label}\n data-indicator={kind}\n className=\"mt-0.5 flex size-4 shrink-0 items-center justify-center\"\n >\n <span\n className={cn(\n 'size-2 rounded-full',\n kind === 'needs_you' ? 'bg-orange-500' : 'bg-primary',\n )}\n />\n </span>\n );\n};\n\nconst LINE_TONE_CLASS: Record<SessionLineTone, string> = {\n needs_you: 'text-orange-600',\n ready: 'text-green-600',\n error: 'text-red-600',\n muted: 'text-muted-foreground',\n};\n\n/**\n * One action in the row's trailing cluster: a sibling of the row control\n * (never nested in it — a click must not select the row), labelled for\n * assistive technology, revealed on hover or focus unless `alwaysVisible`,\n * and disabled while its write is in flight so one row never has two writes\n * racing.\n */\nconst RowActionButton: FC<{\n label: string;\n onClick: (event: MouseEvent<HTMLButtonElement>) => void;\n pending: boolean;\n alwaysVisible: boolean;\n action: string;\n pressed?: boolean;\n children: ReactNode;\n}> = ({ label, onClick, pending, alwaysVisible, action, pressed, children }) => (\n <button\n type=\"button\"\n onClick={onClick}\n disabled={pending}\n aria-busy={pending || undefined}\n aria-pressed={pressed}\n aria-label={label}\n title={label}\n data-action={action}\n data-pending={pending || undefined}\n className={cn(\n 'rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-wait disabled:opacity-60',\n FOCUS_RING_CLASS,\n !alwaysVisible &&\n !pending &&\n 'opacity-0 focus-visible:opacity-100 group-focus-within:opacity-100 group-hover:opacity-100',\n )}\n >\n {children}\n </button>\n);\n\n/**\n * The pin toggle: pressed while the session is pinned (and then always\n * visible); otherwise an outline pin revealed on hover or focus so an\n * unpinned row stays quiet.\n */\nconst PinToggle: FC<{\n thread: Pick<AthenaStatewireThreadSummary, 'id' | 'isFavorited'>;\n pending: boolean;\n onToggle: (target: ThreadFavoriteTarget) => void;\n}> = ({ thread, pending, onToggle }) => {\n const label = thread.isFavorited ? 'Unpin session' : 'Pin session';\n const handleClick = (event: MouseEvent) => {\n event.stopPropagation();\n if (pending) return;\n onToggle({ threadId: thread.id, isFavorited: !thread.isFavorited });\n };\n return (\n <span data-pin-toggle={thread.isFavorited ? 'pinned' : 'unpinned'} className=\"contents\">\n <RowActionButton\n label={label}\n onClick={handleClick}\n pending={pending}\n alwaysVisible={thread.isFavorited}\n action=\"pin\"\n pressed={thread.isFavorited}\n >\n <PinIcon className={cn('size-3.5', thread.isFavorited && 'fill-current')} aria-hidden />\n </RowActionButton>\n </span>\n );\n};\n\nconst SKELETON_TITLE_WIDTHS = ['w-3/5', 'w-4/5', 'w-1/2', 'w-2/3'] as const;\n\n/**\n * Placeholder rows shaped like session rows (indicator, title line, meta\n * line) for the first page and for a refresh that has no rows to keep yet.\n */\nexport const SessionListSkeleton: FC<{ rows?: number }> = ({ rows = 4 }) => (\n <div\n role=\"status\"\n aria-busy=\"true\"\n aria-label=\"Loading sessions\"\n data-testid=\"session-list-skeleton\"\n className=\"flex flex-col gap-0.5\"\n >\n {Array.from({ length: rows }, (_, index) => (\n <div key={index} aria-hidden=\"true\" className=\"flex items-start gap-2 px-3 py-2\">\n <span className=\"mt-0.5 size-4 shrink-0 animate-pulse rounded-full bg-muted\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex h-5 items-center\">\n <span\n className={cn(\n 'h-3.5 animate-pulse rounded bg-muted',\n SKELETON_TITLE_WIDTHS[index % SKELETON_TITLE_WIDTHS.length],\n )}\n />\n </span>\n <span className=\"mt-0.5 flex h-4 items-center\">\n <span className=\"h-3 w-1/3 animate-pulse rounded bg-muted/70\" />\n </span>\n </span>\n </div>\n ))}\n </div>\n);\n\nconst SessionRow: FC<{\n thread: AthenaStatewireThreadSummary;\n isActive: boolean;\n onSelect: (threadId: string) => void;\n /** Absent when hover prefetching is off or the provider has no snapshot cache to warm. */\n onPrefetch: ((threadId: string) => void) | undefined;\n /** Absent when the toggle is not offered: a pinned row then shows a read-only pin. */\n onToggleFavorite: ((target: ThreadFavoriteTarget) => void) | undefined;\n /** True while this session's pin write is in flight. */\n favoritePending: boolean;\n /** Absent when the rename is not offered (host opt-out, or a backend without the route). */\n onRename: ((target: ThreadRenameTarget) => void) | undefined;\n renamePending: boolean;\n /** Absent when the archive is not offered (host opt-out, or a backend without the route). */\n onArchive: ((threadId: string) => void) | undefined;\n archivePending: boolean;\n /** Absent when the host offers no other place to open a session. */\n onOpenInNewTab: ((target: SessionOpenTarget) => void) | undefined;\n originBadges: SessionOriginBadges;\n}> = ({\n thread,\n isActive,\n onSelect,\n onPrefetch,\n onToggleFavorite,\n favoritePending,\n onRename,\n renamePending,\n onArchive,\n archivePending,\n onOpenInNewTab,\n originBadges,\n}) => {\n const indicator = sessionIndicator(thread);\n const line = sessionSecondaryLine(thread);\n const chip = pullRequestChip(thread.pullRequests);\n const origin = sessionOriginBadge(thread, originBadges);\n const title = thread.title ?? 'Untitled';\n\n // Hover prefetch: the pointer must rest on the row for the dwell before\n // the fetch starts, so sweeping the pointer down the list fetches nothing.\n const hoverTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const cancelHover = useCallback(() => {\n if (hoverTimerRef.current === null) return;\n clearTimeout(hoverTimerRef.current);\n hoverTimerRef.current = null;\n }, []);\n useEffect(() => cancelHover, [cancelHover]);\n const handlePointerEnter = () => {\n if (!onPrefetch) return;\n cancelHover();\n hoverTimerRef.current = setTimeout(() => {\n hoverTimerRef.current = null;\n onPrefetch(thread.id);\n }, THREAD_PREFETCH_HOVER_DELAY_MS);\n };\n const handleFocus = () => {\n cancelHover();\n onPrefetch?.(thread.id);\n };\n\n // Inline rename: the title becomes an input; Enter commits, Escape cancels,\n // blur commits. An Enter or Escape settles the edit before the blur its\n // unmount may raise, so the blur never commits a second time.\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState('');\n const settledRef = useRef(false);\n const inputRef = useRef<HTMLInputElement | null>(null);\n useEffect(() => {\n if (!editing) return;\n inputRef.current?.focus();\n inputRef.current?.select();\n }, [editing]);\n const beginRename = (event: MouseEvent) => {\n event.stopPropagation();\n if (renamePending) return;\n settledRef.current = false;\n setDraft(thread.title ?? '');\n setEditing(true);\n };\n const commitRename = () => {\n if (settledRef.current) return;\n settledRef.current = true;\n setEditing(false);\n const next = draft.trim();\n if (!next || next === (thread.title ?? '').trim()) return;\n onRename?.({ threadId: thread.id, title: next });\n };\n const cancelRename = () => {\n settledRef.current = true;\n setEditing(false);\n };\n const handleEditorKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n commitRename();\n } else if (event.key === 'Escape') {\n event.preventDefault();\n event.stopPropagation();\n cancelRename();\n }\n };\n\n const handleArchive = (event: MouseEvent) => {\n event.stopPropagation();\n if (archivePending) return;\n onArchive?.(thread.id);\n };\n\n const openInNewTab = (event: MouseEvent) => {\n event.preventDefault();\n event.stopPropagation();\n onOpenInNewTab?.({ threadId: thread.id, title: thread.title });\n };\n // A ⌘-click (Ctrl-click) opens the row elsewhere, as a browser opens a link in a new tab.\n const handleRowClick = (event: MouseEvent) => {\n if (onOpenInNewTab && (event.metaKey || event.ctrlKey)) {\n openInNewTab(event);\n return;\n }\n onSelect(thread.id);\n };\n const handleRowAuxClick = (event: MouseEvent) => {\n if (onOpenInNewTab && event.button === 1) openInNewTab(event);\n };\n\n const hasActions = Boolean(onRename || onToggleFavorite || onArchive || onOpenInNewTab);\n const body = (\n <>\n <SessionStatusIndicator kind={indicator} />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex h-5 items-center gap-1.5\">\n {editing ? (\n <input\n ref={inputRef}\n type=\"text\"\n value={draft}\n maxLength={THREAD_TITLE_MAX_LENGTH}\n aria-label=\"Session title\"\n data-rename-input\n onChange={(event: ChangeEvent<HTMLInputElement>) => setDraft(event.target.value)}\n onKeyDown={handleEditorKeyDown}\n onBlur={commitRename}\n onClick={stopPropagation}\n className={cn(\n 'h-5 min-w-0 flex-1 rounded border border-primary/60 bg-background px-1 font-medium text-foreground text-sm',\n FOCUS_RING_CLASS,\n )}\n />\n ) : (\n <span data-title className=\"min-w-0 truncate font-medium text-sm\">\n {title}\n </span>\n )}\n {thread.isFavorited && !onToggleFavorite ? (\n <PinIcon\n role=\"img\"\n aria-label=\"Pinned\"\n className=\"size-3 shrink-0 fill-current text-muted-foreground\"\n />\n ) : null}\n {origin ? (\n <span\n data-origin={origin}\n className=\"shrink-0 rounded-full border border-border/60 px-1.5 py-px text-[10px] text-muted-foreground\"\n >\n {origin}\n </span>\n ) : null}\n </span>\n <span\n data-line={line.kind}\n className={cn('mt-0.5 block h-4 truncate font-normal text-xs leading-4', LINE_TONE_CLASS[line.tone])}\n >\n {line.text}\n </span>\n </span>\n </>\n );\n\n return (\n <li\n data-active={isActive || undefined}\n data-thread-id={thread.id}\n data-editing={editing || undefined}\n onPointerEnter={handlePointerEnter}\n onPointerLeave={cancelHover}\n className={cn(\n 'group relative flex items-start gap-1 rounded-lg text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground',\n isActive && 'bg-muted text-foreground',\n )}\n >\n {isActive ? (\n <span\n aria-hidden=\"true\"\n data-active-accent\n className=\"absolute inset-y-2 left-0 w-0.5 rounded-full bg-primary\"\n />\n ) : null}\n {editing ? (\n <div className=\"flex min-w-0 flex-1 items-start gap-2 p-2 pl-3 text-left\">{body}</div>\n ) : (\n <button\n type=\"button\"\n onClick={handleRowClick}\n onAuxClick={handleRowAuxClick}\n onFocus={handleFocus}\n className={cn(\n 'flex min-w-0 flex-1 cursor-pointer items-start gap-2 rounded-lg p-2 pl-3 text-left',\n FOCUS_RING_CLASS,\n )}\n >\n {body}\n </button>\n )}\n {chip || hasActions ? (\n <div className=\"flex shrink-0 flex-col items-end justify-between gap-0.5 py-2 pr-2\">\n {hasActions ? (\n <div data-row-actions className=\"flex h-5 items-center gap-0.5\">\n {onOpenInNewTab ? (\n <RowActionButton\n label=\"Open in new tab\"\n onClick={openInNewTab}\n pending={false}\n alwaysVisible={false}\n action=\"open-in-new-tab\"\n >\n <SquareArrowOutUpRightIcon className=\"size-3.5\" aria-hidden />\n </RowActionButton>\n ) : null}\n {onRename ? (\n <RowActionButton\n label=\"Rename session\"\n onClick={beginRename}\n pending={renamePending}\n alwaysVisible={false}\n action=\"rename\"\n >\n <PencilIcon className=\"size-3.5\" aria-hidden />\n </RowActionButton>\n ) : null}\n {onToggleFavorite ? (\n <PinToggle thread={thread} pending={favoritePending} onToggle={onToggleFavorite} />\n ) : null}\n {onArchive ? (\n <RowActionButton\n label=\"Archive session\"\n onClick={handleArchive}\n pending={archivePending}\n alwaysVisible={false}\n action=\"archive\"\n >\n <ArchiveIcon className=\"size-3.5\" aria-hidden />\n </RowActionButton>\n ) : null}\n </div>\n ) : (\n <span aria-hidden=\"true\" className=\"h-5\" />\n )}\n {chip ? <PullRequestChip chip={chip} pullRequests={thread.pullRequests ?? []} /> : null}\n </div>\n ) : null}\n </li>\n );\n};\n\n/**\n * Devin-style session list: the `ThreadList` rows with an attention\n * indicator, the server's next action, pull-request chips, an origin badge\n * and a trailing action cluster (rename inline, pin, archive) revealed on\n * hover or focus, grouped by what each session asks of the viewer. Reads the\n * thread-list state that `<AthenaProvider enableThreadList>` provides and\n * renders nothing outside that mode.\n *\n * Every write is optimistic and rolls back on failure: the pin flips the row\n * and moves it between `Pinned` and `Recent`; the rename shows the new title\n * on commit; the archive removes the row. On a backend that predates a route\n * the matching action is withdrawn (`favoriteToggleAvailable` falls back to a\n * read-only pin; `renameAvailable` / `archiveAvailable` hide the action).\n *\n * Rows warm the provider's snapshot cache after a 120 ms hover dwell (and on\n * focus) so a selection paints its transcript at once. Skeleton rows stand in\n * while the first page loads or a refresh has nothing to keep; the empty\n * state appears only after a settled empty answer, and a background refresh\n * never blanks the rows it already has.\n */\nexport function SessionList({\n className,\n grouped = true,\n showSearch = true,\n showNewChat = true,\n originBadges = 'all',\n showPinToggle = true,\n showRename = true,\n showArchive = true,\n prefetchOnHover = true,\n onOpenInNewTab,\n}: SessionListProps) {\n const list = useAthenaStatewireThreadList();\n if (!list) return null;\n\n const sections = groupSessions(list.threads, grouped);\n const hasSearch = list.search.trim().length > 0;\n const handleSearch = (event: ChangeEvent<HTMLInputElement>) =>\n list.setSearch(event.target.value);\n const onToggleFavorite =\n showPinToggle && list.favoriteToggleAvailable ? list.setFavorited : undefined;\n const onRename = showRename && list.renameAvailable ? list.renameThread : undefined;\n const onArchive = showArchive && list.archiveAvailable ? list.archiveThread : undefined;\n const onPrefetch = prefetchOnHover ? list.prefetchThread : undefined;\n // Skeleton until the first page has answered (no credential yet, or the\n // page in flight) and for a refresh with no rows to keep; a refresh that\n // has rows keeps showing them.\n const showSkeleton =\n !list.hasResolvedFirstPage || (list.isFetching && list.threads.length === 0);\n\n return (\n <div\n className={cn('athena-sdk-chrome flex flex-col gap-2', className)}\n data-testid=\"session-list\"\n >\n {showNewChat ? (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={list.newThread}\n className={cn(\n 'flex h-9 flex-1 items-center gap-2 rounded-lg border border-border/60 px-3 font-medium text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground',\n FOCUS_RING_CLASS,\n )}\n >\n <PlusIcon className=\"size-4\" />\n New Chat\n </button>\n <button\n type=\"button\"\n onClick={list.refresh}\n disabled={list.isFetching}\n className={cn(\n 'flex size-9 items-center justify-center rounded-lg border border-border/60 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50',\n FOCUS_RING_CLASS,\n )}\n title=\"Refresh sessions\"\n aria-label=\"Refresh sessions\"\n >\n <RefreshCwIcon className={cn('size-4', list.isFetching && 'animate-spin')} />\n </button>\n </div>\n ) : null}\n\n {showSearch ? (\n <label className=\"relative block\">\n <SearchIcon className=\"pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <span className=\"sr-only\">Search sessions</span>\n <input\n type=\"search\"\n value={list.search}\n onChange={handleSearch}\n placeholder=\"Search sessions…\"\n className=\"h-8 w-full rounded-lg border border-border/60 bg-muted/40 pr-2 pl-8 text-sm outline-none focus:border-primary focus:bg-background\"\n />\n </label>\n ) : null}\n\n {showSkeleton ? (\n <SessionListSkeleton />\n ) : sections.length === 0 ? (\n <p data-testid=\"session-list-empty\" className=\"px-3 py-4 text-muted-foreground text-xs\">\n {hasSearch ? 'No matching sessions.' : 'No sessions yet.'}\n </p>\n ) : (\n <div className=\"flex flex-col gap-2\" aria-busy={list.isFetching || undefined}>\n {sections.map(({ group, items }) => (\n <section\n key={group ?? 'all'}\n aria-label={group ?? 'Sessions'}\n data-group={group ?? undefined}\n >\n {group ? (\n <h3 className=\"px-3 pb-1 font-medium text-[11px] text-muted-foreground/70 uppercase tracking-wide\">\n {group}\n </h3>\n ) : null}\n <ul className=\"flex flex-col gap-0.5\">\n {items.map((thread) => (\n <SessionRow\n key={thread.id}\n thread={thread}\n isActive={thread.id === list.activeThreadId}\n onSelect={list.selectThread}\n onPrefetch={onPrefetch}\n onToggleFavorite={onToggleFavorite}\n favoritePending={list.favoritePendingThreadIds.has(thread.id)}\n onRename={onRename}\n renamePending={list.renamePendingThreadIds.has(thread.id)}\n onArchive={onArchive}\n archivePending={list.archivePendingThreadIds.has(thread.id)}\n onOpenInNewTab={onOpenInNewTab}\n originBadges={originBadges}\n />\n ))}\n </ul>\n </section>\n ))}\n </div>\n )}\n\n {list.hasNextPage && !showSkeleton ? (\n <button\n type=\"button\"\n disabled={list.isFetchingNextPage}\n onClick={list.fetchNextPage}\n className={cn(\n 'w-full rounded-lg py-2 text-muted-foreground text-xs transition-colors hover:bg-muted disabled:opacity-50',\n FOCUS_RING_CLASS,\n )}\n >\n {list.isFetchingNextPage ? 'Loading…' : 'Load more'}\n </button>\n ) : null}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SessionList.js","sources":["../../src/threads/SessionList.tsx"],"sourcesContent":["import {\n ArchiveIcon,\n ClockIcon,\n LoaderCircleIcon,\n PencilIcon,\n PinIcon,\n PlusIcon,\n SquareArrowOutUpRightIcon,\n RefreshCwIcon,\n SearchIcon,\n} from 'lucide-react';\nimport type { ChangeEvent, FC, KeyboardEvent, MouseEvent, ReactNode } from 'react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { cn } from '../lib/utils';\nimport { THREAD_TITLE_MAX_LENGTH } from './api';\nimport {\n groupSessions,\n pullRequestChip,\n type SessionIndicator as SessionIndicatorKind,\n type SessionLineTone,\n type SessionOriginBadges,\n sessionIndicator,\n sessionOriginBadge,\n sessionSecondaryLine,\n} from './session-list-model';\nimport { FOCUS_RING_CLASS, PullRequestChip, stopPropagation } from './session-row-parts';\nimport {\n type AthenaStatewireThreadSummary,\n useAthenaStatewireThreadList,\n} from './statewire-thread-list';\nimport { THREAD_PREFETCH_HOVER_DELAY_MS } from './thread-prefetcher';\nimport type { ThreadFavoriteTarget } from './use-thread-favorite';\nimport type { ThreadRenameTarget } from './use-thread-rename';\n\nexport interface SessionListProps {\n className?: string;\n /**\n * Group rows under \"Needs you\" / \"Working\" / \"Pinned\" / \"Recent\" (default\n * `true`); a session appears once, in the first group that applies. `false`\n * renders one newest-first list.\n */\n grouped?: boolean;\n /** Show the title search box, wired to the server-side `search` (default `true`). */\n showSearch?: boolean;\n /**\n * Show the \"New Chat\" and refresh buttons above the rows (default `true`).\n * `false` for a host that places its own new-session control.\n */\n showNewChat?: boolean;\n /**\n * Which rows carry an origin badge (Athena, Slack, GitHub, Chrome …):\n * `all` (default); `external`, every origin but the web app, so sessions\n * started in a browser tab of the app carry none; or `none`.\n */\n originBadges?: SessionOriginBadges;\n /**\n * Offer the pin toggle on each row (default `true`). `false` — or a backend\n * without the favorite route — shows a pinned row's pin read-only instead.\n */\n showPinToggle?: boolean;\n /**\n * Offer the inline rename on each row (default `true`). Withdrawn on a\n * backend without the rename route (`renameAvailable`).\n */\n showRename?: boolean;\n /**\n * Offer the archive action on each row (default `true`). Withdrawn on a\n * backend without the archive route (`archiveAvailable`).\n */\n showArchive?: boolean;\n /**\n * Warm the provider's snapshot cache for a row after a 120 ms hover dwell\n * and on row focus (default `true`), so selecting it paints at once.\n */\n prefetchOnHover?: boolean;\n /**\n * Open a session somewhere other than the provider's chat (a host showing\n * several chats side by side): each row offers \"Open in new tab\", and a\n * ⌘-click (Ctrl-click) or middle-click on it opens it there instead of\n * selecting it. Absent, rows only select.\n */\n onOpenInNewTab?: (target: SessionOpenTarget) => void;\n}\n\n/** The session a row hands `SessionListProps.onOpenInNewTab`. */\nexport interface SessionOpenTarget {\n threadId: string;\n /** The row's title, `null` while the session has none. */\n title: string | null;\n}\n\nconst INDICATOR_LABEL: Record<NonNullable<SessionIndicatorKind>, string> = {\n needs_you: 'Needs you',\n working: 'Working',\n unread: 'Unread',\n watching: 'Watching',\n};\n\n/**\n * The row's status indicator; an empty spacer keeps titles aligned when a\n * row has none.\n */\nconst SessionStatusIndicator: FC<{ kind: SessionIndicatorKind }> = ({ kind }) => {\n if (kind === null) return <span aria-hidden=\"true\" className=\"mt-0.5 size-4 shrink-0\" />;\n const label = INDICATOR_LABEL[kind];\n if (kind === 'working') {\n return (\n <LoaderCircleIcon\n role=\"img\"\n aria-label={label}\n data-indicator={kind}\n className=\"mt-0.5 size-4 shrink-0 animate-spin text-primary\"\n />\n );\n }\n if (kind === 'watching') {\n return (\n <ClockIcon\n role=\"img\"\n aria-label={label}\n data-indicator={kind}\n className=\"mt-0.5 size-4 shrink-0 text-muted-foreground\"\n />\n );\n }\n return (\n <span\n role=\"img\"\n aria-label={label}\n data-indicator={kind}\n className=\"mt-0.5 flex size-4 shrink-0 items-center justify-center\"\n >\n <span\n className={cn(\n 'size-2 rounded-full',\n kind === 'needs_you' ? 'bg-orange-500' : 'bg-primary',\n )}\n />\n </span>\n );\n};\n\nconst LINE_TONE_CLASS: Record<SessionLineTone, string> = {\n needs_you: 'text-orange-600',\n ready: 'text-green-600',\n error: 'text-red-600',\n muted: 'text-muted-foreground',\n};\n\n/**\n * One action in the row's trailing cluster: a sibling of the row control\n * (never nested in it — a click must not select the row), labelled for\n * assistive technology, revealed on hover or focus unless `alwaysVisible`,\n * and disabled while its write is in flight so one row never has two writes\n * racing.\n */\nconst RowActionButton: FC<{\n label: string;\n onClick: (event: MouseEvent<HTMLButtonElement>) => void;\n pending: boolean;\n alwaysVisible: boolean;\n action: string;\n pressed?: boolean;\n children: ReactNode;\n}> = ({ label, onClick, pending, alwaysVisible, action, pressed, children }) => (\n <button\n type=\"button\"\n onClick={onClick}\n disabled={pending}\n aria-busy={pending || undefined}\n aria-pressed={pressed}\n aria-label={label}\n title={label}\n data-action={action}\n data-pending={pending || undefined}\n className={cn(\n 'rounded p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-wait disabled:opacity-60',\n FOCUS_RING_CLASS,\n !alwaysVisible &&\n !pending &&\n 'opacity-0 focus-visible:opacity-100 group-focus-within:opacity-100 group-hover:opacity-100',\n )}\n >\n {children}\n </button>\n);\n\n/**\n * The pin toggle: pressed while the session is pinned (and then always\n * visible); otherwise an outline pin revealed on hover or focus so an\n * unpinned row stays quiet.\n */\nconst PinToggle: FC<{\n thread: Pick<AthenaStatewireThreadSummary, 'id' | 'isFavorited'>;\n pending: boolean;\n onToggle: (target: ThreadFavoriteTarget) => void;\n}> = ({ thread, pending, onToggle }) => {\n const label = thread.isFavorited ? 'Unpin session' : 'Pin session';\n const handleClick = (event: MouseEvent) => {\n event.stopPropagation();\n if (pending) return;\n onToggle({ threadId: thread.id, isFavorited: !thread.isFavorited });\n };\n return (\n <span data-pin-toggle={thread.isFavorited ? 'pinned' : 'unpinned'} className=\"contents\">\n <RowActionButton\n label={label}\n onClick={handleClick}\n pending={pending}\n alwaysVisible={thread.isFavorited}\n action=\"pin\"\n pressed={thread.isFavorited}\n >\n <PinIcon className={cn('size-3.5', thread.isFavorited && 'fill-current')} aria-hidden />\n </RowActionButton>\n </span>\n );\n};\n\nconst SKELETON_TITLE_WIDTHS = ['w-3/5', 'w-4/5', 'w-1/2', 'w-2/3'] as const;\n\n/**\n * Placeholder rows shaped like session rows (indicator, title line, meta\n * line) for the first page and for a refresh that has no rows to keep yet.\n */\nexport const SessionListSkeleton: FC<{ rows?: number }> = ({ rows = 4 }) => (\n <div\n role=\"status\"\n aria-busy=\"true\"\n aria-label=\"Loading sessions\"\n data-testid=\"session-list-skeleton\"\n className=\"flex flex-col gap-0.5\"\n >\n {Array.from({ length: rows }, (_, index) => (\n <div key={index} aria-hidden=\"true\" className=\"flex items-start gap-2 px-3 py-2\">\n <span className=\"mt-0.5 size-4 shrink-0 animate-pulse rounded-full bg-muted\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex h-5 items-center\">\n <span\n className={cn(\n 'h-3.5 animate-pulse rounded bg-muted',\n SKELETON_TITLE_WIDTHS[index % SKELETON_TITLE_WIDTHS.length],\n )}\n />\n </span>\n <span className=\"mt-0.5 flex h-4 items-center\">\n <span className=\"h-3 w-1/3 animate-pulse rounded bg-muted/70\" />\n </span>\n </span>\n </div>\n ))}\n </div>\n);\n\nconst SessionRow: FC<{\n thread: AthenaStatewireThreadSummary;\n isActive: boolean;\n onSelect: (threadId: string) => void;\n /** Absent when hover prefetching is off or the provider has no snapshot cache to warm. */\n onPrefetch: ((threadId: string) => void) | undefined;\n /** Absent when the toggle is not offered: a pinned row then shows a read-only pin. */\n onToggleFavorite: ((target: ThreadFavoriteTarget) => void) | undefined;\n /** True while this session's pin write is in flight. */\n favoritePending: boolean;\n /** Absent when the rename is not offered (host opt-out, or a backend without the route). */\n onRename: ((target: ThreadRenameTarget) => void) | undefined;\n renamePending: boolean;\n /** Absent when the archive is not offered (host opt-out, or a backend without the route). */\n onArchive: ((threadId: string) => void) | undefined;\n archivePending: boolean;\n /** Absent when the host offers no other place to open a session. */\n onOpenInNewTab: ((target: SessionOpenTarget) => void) | undefined;\n originBadges: SessionOriginBadges;\n}> = ({\n thread,\n isActive,\n onSelect,\n onPrefetch,\n onToggleFavorite,\n favoritePending,\n onRename,\n renamePending,\n onArchive,\n archivePending,\n onOpenInNewTab,\n originBadges,\n}) => {\n const indicator = sessionIndicator(thread);\n const line = sessionSecondaryLine(thread);\n const chip = pullRequestChip(thread.pullRequests);\n // A new chat's pending row whose provenance the backend decides (a collab channel stamps its\n // own kind) shows no badge until the server's row brings the real one.\n const origin =\n thread.isPending && !thread.startChannel ? null : sessionOriginBadge(thread, originBadges);\n const title = thread.title ?? 'Untitled';\n\n // Hover prefetch: the pointer must rest on the row for the dwell before\n // the fetch starts, so sweeping the pointer down the list fetches nothing.\n const hoverTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const cancelHover = useCallback(() => {\n if (hoverTimerRef.current === null) return;\n clearTimeout(hoverTimerRef.current);\n hoverTimerRef.current = null;\n }, []);\n useEffect(() => cancelHover, [cancelHover]);\n const handlePointerEnter = () => {\n if (!onPrefetch) return;\n cancelHover();\n hoverTimerRef.current = setTimeout(() => {\n hoverTimerRef.current = null;\n onPrefetch(thread.id);\n }, THREAD_PREFETCH_HOVER_DELAY_MS);\n };\n const handleFocus = () => {\n cancelHover();\n onPrefetch?.(thread.id);\n };\n\n // Inline rename: the title becomes an input; Enter commits, Escape cancels,\n // blur commits. An Enter or Escape settles the edit before the blur its\n // unmount may raise, so the blur never commits a second time.\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState('');\n const settledRef = useRef(false);\n const inputRef = useRef<HTMLInputElement | null>(null);\n useEffect(() => {\n if (!editing) return;\n inputRef.current?.focus();\n inputRef.current?.select();\n }, [editing]);\n const beginRename = (event: MouseEvent) => {\n event.stopPropagation();\n if (renamePending) return;\n settledRef.current = false;\n setDraft(thread.title ?? '');\n setEditing(true);\n };\n const commitRename = () => {\n if (settledRef.current) return;\n settledRef.current = true;\n setEditing(false);\n const next = draft.trim();\n if (!next || next === (thread.title ?? '').trim()) return;\n onRename?.({ threadId: thread.id, title: next });\n };\n const cancelRename = () => {\n settledRef.current = true;\n setEditing(false);\n };\n const handleEditorKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n commitRename();\n } else if (event.key === 'Escape') {\n event.preventDefault();\n event.stopPropagation();\n cancelRename();\n }\n };\n\n const handleArchive = (event: MouseEvent) => {\n event.stopPropagation();\n if (archivePending) return;\n onArchive?.(thread.id);\n };\n\n const openInNewTab = (event: MouseEvent) => {\n event.preventDefault();\n event.stopPropagation();\n onOpenInNewTab?.({ threadId: thread.id, title: thread.title });\n };\n // A ⌘-click (Ctrl-click) opens the row elsewhere, as a browser opens a link in a new tab.\n const handleRowClick = (event: MouseEvent) => {\n if (onOpenInNewTab && (event.metaKey || event.ctrlKey)) {\n openInNewTab(event);\n return;\n }\n onSelect(thread.id);\n };\n const handleRowAuxClick = (event: MouseEvent) => {\n if (onOpenInNewTab && event.button === 1) openInNewTab(event);\n };\n\n const hasActions = Boolean(onRename || onToggleFavorite || onArchive || onOpenInNewTab);\n const body = (\n <>\n <SessionStatusIndicator kind={indicator} />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex h-5 items-center gap-1.5\">\n {editing ? (\n <input\n ref={inputRef}\n type=\"text\"\n value={draft}\n maxLength={THREAD_TITLE_MAX_LENGTH}\n aria-label=\"Session title\"\n data-rename-input\n onChange={(event: ChangeEvent<HTMLInputElement>) => setDraft(event.target.value)}\n onKeyDown={handleEditorKeyDown}\n onBlur={commitRename}\n onClick={stopPropagation}\n className={cn(\n 'h-5 min-w-0 flex-1 rounded border border-primary/60 bg-background px-1 font-medium text-foreground text-sm',\n FOCUS_RING_CLASS,\n )}\n />\n ) : (\n <span data-title className=\"min-w-0 truncate font-medium text-sm\">\n {title}\n </span>\n )}\n {thread.isFavorited && !onToggleFavorite ? (\n <PinIcon\n role=\"img\"\n aria-label=\"Pinned\"\n className=\"size-3 shrink-0 fill-current text-muted-foreground\"\n />\n ) : null}\n {origin ? (\n <span\n data-origin={origin}\n className=\"shrink-0 rounded-full border border-border/60 px-1.5 py-px text-[10px] text-muted-foreground\"\n >\n {origin}\n </span>\n ) : null}\n </span>\n <span\n data-line={line.kind}\n className={cn('mt-0.5 block h-4 truncate font-normal text-xs leading-4', LINE_TONE_CLASS[line.tone])}\n >\n {line.text}\n </span>\n </span>\n </>\n );\n\n return (\n <li\n data-active={isActive || undefined}\n data-thread-id={thread.id}\n data-editing={editing || undefined}\n onPointerEnter={handlePointerEnter}\n onPointerLeave={cancelHover}\n className={cn(\n 'group relative flex items-start gap-1 rounded-lg text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground',\n isActive && 'bg-muted text-foreground',\n )}\n >\n {isActive ? (\n <span\n aria-hidden=\"true\"\n data-active-accent\n className=\"absolute inset-y-2 left-0 w-0.5 rounded-full bg-primary\"\n />\n ) : null}\n {editing ? (\n <div className=\"flex min-w-0 flex-1 items-start gap-2 p-2 pl-3 text-left\">{body}</div>\n ) : (\n <button\n type=\"button\"\n onClick={handleRowClick}\n onAuxClick={handleRowAuxClick}\n onFocus={handleFocus}\n className={cn(\n 'flex min-w-0 flex-1 cursor-pointer items-start gap-2 rounded-lg p-2 pl-3 text-left',\n FOCUS_RING_CLASS,\n )}\n >\n {body}\n </button>\n )}\n {chip || hasActions ? (\n <div className=\"flex shrink-0 flex-col items-end justify-between gap-0.5 py-2 pr-2\">\n {hasActions ? (\n <div data-row-actions className=\"flex h-5 items-center gap-0.5\">\n {onOpenInNewTab ? (\n <RowActionButton\n label=\"Open in new tab\"\n onClick={openInNewTab}\n pending={false}\n alwaysVisible={false}\n action=\"open-in-new-tab\"\n >\n <SquareArrowOutUpRightIcon className=\"size-3.5\" aria-hidden />\n </RowActionButton>\n ) : null}\n {onRename ? (\n <RowActionButton\n label=\"Rename session\"\n onClick={beginRename}\n pending={renamePending}\n alwaysVisible={false}\n action=\"rename\"\n >\n <PencilIcon className=\"size-3.5\" aria-hidden />\n </RowActionButton>\n ) : null}\n {onToggleFavorite ? (\n <PinToggle thread={thread} pending={favoritePending} onToggle={onToggleFavorite} />\n ) : null}\n {onArchive ? (\n <RowActionButton\n label=\"Archive session\"\n onClick={handleArchive}\n pending={archivePending}\n alwaysVisible={false}\n action=\"archive\"\n >\n <ArchiveIcon className=\"size-3.5\" aria-hidden />\n </RowActionButton>\n ) : null}\n </div>\n ) : (\n <span aria-hidden=\"true\" className=\"h-5\" />\n )}\n {chip ? <PullRequestChip chip={chip} pullRequests={thread.pullRequests ?? []} /> : null}\n </div>\n ) : null}\n </li>\n );\n};\n\n/**\n * Devin-style session list: the `ThreadList` rows with an attention\n * indicator, the server's next action, pull-request chips, an origin badge\n * and a trailing action cluster (rename inline, pin, archive) revealed on\n * hover or focus, grouped by what each session asks of the viewer. Reads the\n * thread-list state that `<AthenaProvider enableThreadList>` provides and\n * renders nothing outside that mode.\n *\n * Every write is optimistic and rolls back on failure: the pin flips the row\n * and moves it between `Pinned` and `Recent`; the rename shows the new title\n * on commit; the archive removes the row. On a backend that predates a route\n * the matching action is withdrawn (`favoriteToggleAvailable` falls back to a\n * read-only pin; `renameAvailable` / `archiveAvailable` hide the action).\n *\n * Rows warm the provider's snapshot cache after a 120 ms hover dwell (and on\n * focus) so a selection paints its transcript at once. Skeleton rows stand in\n * while the first page loads or a refresh has nothing to keep; the empty\n * state appears only after a settled empty answer, and a background refresh\n * never blanks the rows it already has.\n */\nexport function SessionList({\n className,\n grouped = true,\n showSearch = true,\n showNewChat = true,\n originBadges = 'all',\n showPinToggle = true,\n showRename = true,\n showArchive = true,\n prefetchOnHover = true,\n onOpenInNewTab,\n}: SessionListProps) {\n const list = useAthenaStatewireThreadList();\n if (!list) return null;\n\n const sections = groupSessions(list.threads, grouped);\n const hasSearch = list.search.trim().length > 0;\n const handleSearch = (event: ChangeEvent<HTMLInputElement>) =>\n list.setSearch(event.target.value);\n const onToggleFavorite =\n showPinToggle && list.favoriteToggleAvailable ? list.setFavorited : undefined;\n const onRename = showRename && list.renameAvailable ? list.renameThread : undefined;\n const onArchive = showArchive && list.archiveAvailable ? list.archiveThread : undefined;\n const onPrefetch = prefetchOnHover ? list.prefetchThread : undefined;\n // Skeleton until the first page has answered (no credential yet, or the\n // page in flight) and for a refresh with no rows to keep; a refresh that\n // has rows keeps showing them.\n const showSkeleton =\n !list.hasResolvedFirstPage || (list.isFetching && list.threads.length === 0);\n\n return (\n <div\n className={cn('athena-sdk-chrome flex flex-col gap-2', className)}\n data-testid=\"session-list\"\n >\n {showNewChat ? (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={list.newThread}\n className={cn(\n 'flex h-9 flex-1 items-center gap-2 rounded-lg border border-border/60 px-3 font-medium text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground',\n FOCUS_RING_CLASS,\n )}\n >\n <PlusIcon className=\"size-4\" />\n New Chat\n </button>\n <button\n type=\"button\"\n onClick={list.refresh}\n disabled={list.isFetching}\n className={cn(\n 'flex size-9 items-center justify-center rounded-lg border border-border/60 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50',\n FOCUS_RING_CLASS,\n )}\n title=\"Refresh sessions\"\n aria-label=\"Refresh sessions\"\n >\n <RefreshCwIcon className={cn('size-4', list.isFetching && 'animate-spin')} />\n </button>\n </div>\n ) : null}\n\n {showSearch ? (\n <label className=\"relative block\">\n <SearchIcon className=\"pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <span className=\"sr-only\">Search sessions</span>\n <input\n type=\"search\"\n value={list.search}\n onChange={handleSearch}\n placeholder=\"Search sessions…\"\n className=\"h-8 w-full rounded-lg border border-border/60 bg-muted/40 pr-2 pl-8 text-sm outline-none focus:border-primary focus:bg-background\"\n />\n </label>\n ) : null}\n\n {showSkeleton ? (\n <SessionListSkeleton />\n ) : sections.length === 0 ? (\n <p data-testid=\"session-list-empty\" className=\"px-3 py-4 text-muted-foreground text-xs\">\n {hasSearch ? 'No matching sessions.' : 'No sessions yet.'}\n </p>\n ) : (\n <div className=\"flex flex-col gap-2\" aria-busy={list.isFetching || undefined}>\n {sections.map(({ group, items }) => (\n <section\n key={group ?? 'all'}\n aria-label={group ?? 'Sessions'}\n data-group={group ?? undefined}\n >\n {group ? (\n <h3 className=\"px-3 pb-1 font-medium text-[11px] text-muted-foreground/70 uppercase tracking-wide\">\n {group}\n </h3>\n ) : null}\n <ul className=\"flex flex-col gap-0.5\">\n {items.map((thread) => {\n // A pending row is a chat the server does not know yet: nothing to\n // prefetch, pin, rename, archive or open elsewhere until its own row lands.\n const known = !thread.isPending;\n return (\n <SessionRow\n key={thread.id}\n thread={thread}\n isActive={thread.id === list.activeThreadId}\n onSelect={list.selectThread}\n onPrefetch={known ? onPrefetch : undefined}\n onToggleFavorite={known ? onToggleFavorite : undefined}\n favoritePending={list.favoritePendingThreadIds.has(thread.id)}\n onRename={known ? onRename : undefined}\n renamePending={list.renamePendingThreadIds.has(thread.id)}\n onArchive={known ? onArchive : undefined}\n archivePending={list.archivePendingThreadIds.has(thread.id)}\n onOpenInNewTab={known ? onOpenInNewTab : undefined}\n originBadges={originBadges}\n />\n );\n })}\n </ul>\n </section>\n ))}\n </div>\n )}\n\n {list.hasNextPage && !showSkeleton ? (\n <button\n type=\"button\"\n disabled={list.isFetchingNextPage}\n onClick={list.fetchNextPage}\n className={cn(\n 'w-full rounded-lg py-2 text-muted-foreground text-xs transition-colors hover:bg-muted disabled:opacity-50',\n FOCUS_RING_CLASS,\n )}\n >\n {list.isFetchingNextPage ? 'Loading…' : 'Load more'}\n </button>\n ) : null}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}