@agimon-ai/po-web-dashboard 0.0.0-alpha.78 → 0.0.0-alpha.79

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.
@@ -173,13 +173,15 @@ function TopBar({ view = "conversation", mcpEnabled = false, onShowSessions, onS
173
173
  const noSessions = require_sessionsStore.useNoSessions();
174
174
  const ownerId = (0, _tanstack_react_store.useStore)(require_sessionsStore.sessionsStore, (state) => state.tabOwnerId ?? state.activeId);
175
175
  const transientTabs = require_transientTabsStore.useTransientTabs(ownerId);
176
- const title = meta?.summary.name || session.agent?.sessionName || "Untitled";
177
- const [renaming, setRenaming] = require_dashboardStateStore.useDashboardState(`session-title/${activeId}/open`, false);
178
- const [draft, setDraft] = require_dashboardStateStore.useDashboardState(`session-title/${activeId}/draft`, "");
176
+ const ownerName = (0, _tanstack_react_store.useStore)(require_sessionsStore.sessionsStore, (state) => ownerId === null ? void 0 : state.byId[ownerId]?.summary.name);
177
+ const ownerAgentName = (0, _tanstack_react_store.useStore)(require_sessionStore.sessionStoreFor(ownerId), (state) => state.agent?.sessionName);
178
+ const title = ownerName || ownerAgentName || "Untitled";
179
+ const [renaming, setRenaming] = require_dashboardStateStore.useDashboardState(`session-title/${ownerId}/open`, false);
180
+ const [draft, setDraft] = require_dashboardStateStore.useDashboardState(`session-title/${ownerId}/draft`, "");
179
181
  const backgroundWorkActive = require_composition.useActivityGroups(session.statuses, session.widgets, activeId).some((group) => group.active);
180
182
  const commitRename = () => {
181
183
  const next = draft.trim();
182
- if (next && next !== title) require_sessionStore.renameSession(next, activeId);
184
+ if (next && next !== title) require_sessionStore.renameSession(next, ownerId);
183
185
  setRenaming(false);
184
186
  };
185
187
  const lockAgent = activeId !== null && meta?.summary.dormant !== true && !session.agentLocked ? () => require_sessionStore.setAgentLock(true, activeId) : void 0;
@@ -200,7 +202,7 @@ function TopBar({ view = "conversation", mcpEnabled = false, onShowSessions, onS
200
202
  className: `shrink-0 text-po-dim ${navigationToggle === "mobile" ? "md:hidden" : ""}`,
201
203
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_agimon_ai_po_web_components.FolderIcon, { className: "size-4" })
202
204
  }) : null,
203
- noSessions ? null : renaming && activeId !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_agimon_ai_po_web_components.Input, {
205
+ noSessions ? null : renaming && ownerId !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_agimon_ai_po_web_components.Input, {
204
206
  "data-testid": "session-title-input",
205
207
  value: draft,
206
208
  autoFocus: true,
@@ -215,8 +217,8 @@ function TopBar({ view = "conversation", mcpEnabled = false, onShowSessions, onS
215
217
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_agimon_ai_po_web_components.Button, {
216
218
  variant: "ghost",
217
219
  "data-testid": "session-title",
218
- title: activeId === null ? void 0 : "Rename this session",
219
- disabled: activeId === null,
220
+ title: ownerId === null ? void 0 : "Rename this session",
221
+ disabled: ownerId === null,
220
222
  onClick: () => {
221
223
  setDraft(title);
222
224
  setRenaming(true);
@@ -1 +1 @@
1
- {"version":3,"file":"TopBar.cjs","names":["NavTab","Link","NavTabBadge","Button","closeTransientTab","CloseIcon","useNavigate","webTabs","DropdownMenu","DropdownMenuTrigger","GridIcon","DropdownMenuContent","DropdownMenuLabel","DropdownMenuRadioGroup","DropdownMenuRadioItem","DropdownMenuSeparator","DropdownMenuItem","LockIcon","useActiveSessionMeta","useActiveSession","useStore","sessionsStore","useNoSessions","useTransientTabs","useDashboardState","useActivityGroups","setAgentLock","FolderIcon","Input","MessageIcon","ActivityIcon"],"sources":["../../../../src/web/features/status/TopBar.tsx"],"sourcesContent":["import type { TabContribution, TransientTab } from '@agimon-ai/po-core/web';\nimport {\n ActivityIcon,\n Button,\n CloseIcon,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n FolderIcon,\n GridIcon,\n Input,\n LockIcon,\n MessageIcon,\n NavTab,\n NavTabBadge,\n} from '@agimon-ai/po-web-components';\nimport { Link, useNavigate } from '@tanstack/react-router';\nimport { useStore } from '@tanstack/react-store';\n\nimport { useActivityGroups } from '../../lib/composition';\nimport { webTabs } from '../../lib/pluginRegistry';\nimport { useDashboardState } from '../../stores/dashboardStateStore';\nimport { sessionsStore, useActiveSessionMeta, useNoSessions } from '../../stores/sessionsStore';\nimport { renameSession, setAgentLock, useActiveSession } from '../../stores/sessionStore';\nimport { closeTransientTab, useTransientTabs } from '../../stores/transientTabsStore';\n/** One registry tab: the badge hook is stable per tab, so the call is unconditional. */\nfunction PluginTab({ tab, sessionId, active }: { tab: TabContribution; sessionId: string; active: boolean }) {\n const useBadge = tab.useBadge ?? noBadge;\n const count = useBadge(sessionId);\n return (\n <NavTab asChild active={active}>\n <Link\n to=\"/session/$sessionId/$tabId\"\n params={{ sessionId, tabId: tab.id }}\n data-testid={`tab-${tab.id}`}\n className=\"shrink-0\"\n >\n {tab.label}\n {count > 0 ? (\n <NavTabBadge active={active} data-testid={`tab-${tab.id}-count`}>\n {count}\n </NavTabBadge>\n ) : null}\n </Link>\n </NavTab>\n );\n}\n\nfunction noBadge(): number {\n return 0;\n}\n\n/**\n * A tab a plugin opened at runtime: its link with the close beside it, since\n * a button cannot live inside a link. Closing the shown tab needs no\n * navigation of its own; the page falls back to the conversation once the\n * id is gone.\n */\nfunction TransientTabChip({ tab, sessionId, active }: { tab: TransientTab; sessionId: string; active: boolean }) {\n return (\n <span data-testid={`tab-${tab.id}-chip`} data-active={active} className=\"flex shrink-0 items-center gap-0.5\">\n <NavTab asChild active={active}>\n <Link\n to=\"/session/$sessionId/$tabId\"\n params={{ sessionId, tabId: tab.id }}\n data-testid={`tab-${tab.id}`}\n title={tab.label}\n className=\"block min-w-0 max-w-[160px] truncate\"\n >\n {tab.label}\n </Link>\n </NavTab>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n data-testid={`tab-${tab.id}-close`}\n title=\"close this tab\"\n onClick={() => closeTransientTab(sessionId, tab.id)}\n className=\"h-5 w-5 shrink-0\"\n >\n <CloseIcon className=\"h-2.5 w-2.5\" />\n </Button>\n </span>\n );\n}\n\n/** A plugin tab's count inside the menu; one component per tab keeps the badge hook unconditional. */\nfunction MenuBadge({ tab, sessionId }: { tab: TabContribution; sessionId: string }) {\n const count = (tab.useBadge ?? noBadge)(sessionId);\n return count > 0 ? <NavTabBadge className=\"ml-auto\">{count}</NavTabBadge> : null;\n}\n\n// The shown tab reads jade like the strip's active tab; 44px rows for touch.\nconst MENU_TAB_CLASS = 'min-h-11 data-[state=checked]:font-semibold data-[state=checked]:text-po-green';\n/**\n * Below sm the strip keeps only the shown tab, so the title, that tab and the\n * actions share one row; every tab, and the lock, are one tap away here.\n */\nfunction MobileTabsMenu({\n sessionId,\n view,\n mcpEnabled,\n transientTabs,\n onLock,\n}: {\n sessionId: string;\n view: string;\n mcpEnabled: boolean;\n transientTabs: readonly TransientTab[];\n onLock?: () => void;\n}) {\n const navigate = useNavigate();\n const pluginTabs = webTabs(sessionId);\n const open = (tabId: string): void => {\n if (tabId === 'conversation') void navigate({ to: '/session/$sessionId', params: { sessionId } });\n else void navigate({ to: '/session/$sessionId/$tabId', params: { sessionId, tabId } });\n };\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon-md\"\n data-testid=\"mobile-tabs-open\"\n title=\"Show tabs\"\n aria-label=\"Show tabs\"\n className=\"shrink-0 text-po-dim sm:hidden\"\n >\n <GridIcon className=\"size-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent data-testid=\"mobile-tabs-menu\" className=\"min-w-56 max-w-[calc(100vw-24px)]\">\n <DropdownMenuLabel>Tabs</DropdownMenuLabel>\n <DropdownMenuRadioGroup value={view} onValueChange={open}>\n <DropdownMenuRadioItem value=\"conversation\" data-testid=\"mobile-tab-conversation\" className={MENU_TAB_CLASS}>\n Conversation\n </DropdownMenuRadioItem>\n {mcpEnabled ? (\n <DropdownMenuRadioItem value=\"mcp\" data-testid=\"mobile-tab-mcp\" className={MENU_TAB_CLASS}>\n MCP\n </DropdownMenuRadioItem>\n ) : null}\n {pluginTabs.map((tab) => (\n <DropdownMenuRadioItem\n key={tab.id}\n value={tab.id}\n data-testid={`mobile-tab-${tab.id}`}\n className={MENU_TAB_CLASS}\n >\n <span className=\"min-w-0 truncate\">{tab.label}</span>\n <MenuBadge tab={tab} sessionId={sessionId} />\n </DropdownMenuRadioItem>\n ))}\n {transientTabs.map((tab) => (\n <DropdownMenuRadioItem\n key={tab.id}\n value={tab.id}\n data-testid={`mobile-tab-${tab.id}`}\n className={MENU_TAB_CLASS}\n >\n <span className=\"min-w-0 truncate\">{tab.label}</span>\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n {onLock ? (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem data-testid=\"mobile-agent-lock\" onSelect={onLock} className=\"min-h-11 pl-2\">\n <LockIcon aria-hidden className=\"size-4 shrink-0\" />\n Lock agent\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\nexport function TopBar({\n view = 'conversation',\n mcpEnabled = false,\n onShowSessions,\n onShowActivity,\n navigationToggle = 'mobile',\n activityToggle = 'mobile',\n}: {\n view?: string;\n mcpEnabled?: boolean;\n onShowSessions?: () => void;\n onShowActivity?: () => void;\n navigationToggle?: 'mobile' | 'always';\n activityToggle?: 'mobile' | 'always';\n}) {\n const meta = useActiveSessionMeta();\n const session = useActiveSession((state) => state);\n const activeId = useStore(sessionsStore, (state) => state.activeId);\n const noSessions = useNoSessions();\n const ownerId = useStore(sessionsStore, (state) => state.tabOwnerId ?? state.activeId);\n const transientTabs = useTransientTabs(ownerId);\n const title = meta?.summary.name || session.agent?.sessionName || 'Untitled';\n const [renaming, setRenaming] = useDashboardState(`session-title/${activeId}/open`, false);\n const [draft, setDraft] = useDashboardState(`session-title/${activeId}/draft`, '');\n const backgroundWorkActive = useActivityGroups(session.statuses, session.widgets, activeId).some(\n (group) => group.active,\n );\n // The name lives in the agent, so the rail's rename and this one are the\n // same act through the same door; the new name arrives back on the socket.\n const commitRename = (): void => {\n const next = draft.trim();\n if (next && next !== title) renameSession(next, activeId);\n setRenaming(false);\n };\n const lockAgent =\n activeId !== null && meta?.summary.dormant !== true && !session.agentLocked\n ? () => setAgentLock(true, activeId)\n : undefined;\n\n return (\n <header\n data-testid=\"top-bar\"\n className=\"flex min-h-16 shrink-0 items-center justify-between gap-2 border-b border-po-border-soft px-3 py-2 sm:gap-3 sm:px-6\"\n >\n <div data-testid=\"session-switcher\" className=\"flex min-w-0 flex-1 items-center gap-2 sm:gap-3\">\n {onShowSessions ? (\n <Button\n variant=\"ghost\"\n size=\"icon-md\"\n data-testid=\"mobile-sessions-open\"\n title=\"Show sessions\"\n aria-label=\"Show sessions\"\n onClick={onShowSessions}\n className={`shrink-0 text-po-dim ${navigationToggle === 'mobile' ? 'md:hidden' : ''}`}\n >\n <FolderIcon className=\"size-4\" />\n </Button>\n ) : null}\n {/* With no session there is no name to show, so the bar does not report\n 'Untitled' for something that was never started. */}\n {noSessions ? null : renaming && activeId !== null ? (\n <Input\n data-testid=\"session-title-input\"\n value={draft}\n autoFocus\n aria-label=\"Session name\"\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') commitRename();\n if (event.key === 'Escape') setRenaming(false);\n }}\n onBlur={commitRename}\n className=\"h-9 w-56 shrink-0 border-po-blue/60 px-1.5 text-lg font-semibold\"\n />\n ) : (\n <Button\n variant=\"ghost\"\n data-testid=\"session-title\"\n title={activeId === null ? undefined : 'Rename this session'}\n disabled={activeId === null}\n onClick={() => {\n setDraft(title);\n setRenaming(true);\n }}\n className=\"block h-9 min-w-0 shrink truncate px-1.5 text-lg leading-9 font-semibold text-po-hi sm:max-w-80 sm:shrink-0\"\n >\n {title}\n </Button>\n )}\n {ownerId !== null ? (\n // Tabs never shrink; past the width the badges leave them, the strip\n // scrolls sideways (scrollbar hidden) instead of running under them.\n // On a phone only the shown tab stays; the rest are in the tabs menu.\n <nav\n aria-label=\"Conversation views\"\n className=\"flex min-w-0 items-center gap-1 overflow-x-auto [scrollbar-width:none] max-sm:shrink-0 max-sm:[&>*:not([data-active=true])]:hidden [&::-webkit-scrollbar]:hidden [&>*]:min-h-8 [&>*]:rounded-md [&>*]:px-3\"\n >\n <NavTab asChild active={view === 'conversation'}>\n <Link\n to=\"/session/$sessionId\"\n params={{ sessionId: ownerId }}\n data-testid=\"tab-conversation\"\n className=\"shrink-0 gap-1.5\"\n >\n <MessageIcon aria-hidden className=\"size-4\" />\n Conversation\n </Link>\n </NavTab>\n {mcpEnabled ? (\n <NavTab asChild active={view === 'mcp'}>\n <Link\n to=\"/session/$sessionId/$tabId\"\n params={{ sessionId: ownerId, tabId: 'mcp' }}\n data-testid=\"tab-mcp\"\n className=\"shrink-0\"\n >\n MCP\n </Link>\n </NavTab>\n ) : null}\n {webTabs(ownerId).map((tab) => (\n <PluginTab key={tab.id} tab={tab} sessionId={ownerId} active={view === tab.id} />\n ))}\n {transientTabs.map((tab) => (\n <TransientTabChip key={tab.id} tab={tab} sessionId={ownerId} active={view === tab.id} />\n ))}\n </nav>\n ) : null}\n </div>\n\n <div className=\"flex shrink-0 items-center\">\n {lockAgent ? (\n <Button\n variant=\"ghost\"\n data-testid=\"agent-lock\"\n size=\"lg\"\n title=\"Stop the local agent and keep it from starting turns\"\n onClick={lockAgent}\n className=\"hidden shrink-0 gap-2 text-po-text sm:inline-flex\"\n >\n <LockIcon className=\"size-4\" />\n Lock agent\n </Button>\n ) : null}\n {ownerId !== null ? (\n <MobileTabsMenu\n sessionId={ownerId}\n view={view}\n mcpEnabled={mcpEnabled}\n transientTabs={transientTabs}\n onLock={lockAgent}\n />\n ) : null}\n {onShowActivity ? (\n <Button\n variant=\"ghost\"\n size=\"icon-md\"\n data-testid=\"mobile-activity-open\"\n data-active={backgroundWorkActive}\n title={backgroundWorkActive ? 'Background work is running' : 'Show activity'}\n aria-label=\"Show activity\"\n onClick={onShowActivity}\n className={`shrink-0 ${activityToggle === 'mobile' ? 'lg:hidden' : ''} ${backgroundWorkActive ? 'text-po-yellow' : 'text-po-dim'}`}\n >\n <ActivityIcon className=\"size-4\" />\n </Button>\n ) : null}\n </div>\n </header>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TopBar.cjs","names":["NavTab","Link","NavTabBadge","Button","closeTransientTab","CloseIcon","useNavigate","webTabs","DropdownMenu","DropdownMenuTrigger","GridIcon","DropdownMenuContent","DropdownMenuLabel","DropdownMenuRadioGroup","DropdownMenuRadioItem","DropdownMenuSeparator","DropdownMenuItem","LockIcon","useActiveSessionMeta","useActiveSession","useStore","sessionsStore","useNoSessions","useTransientTabs","sessionStoreFor","useDashboardState","useActivityGroups","setAgentLock","FolderIcon","Input","MessageIcon","ActivityIcon"],"sources":["../../../../src/web/features/status/TopBar.tsx"],"sourcesContent":["import type { TabContribution, TransientTab } from '@agimon-ai/po-core/web';\nimport {\n ActivityIcon,\n Button,\n CloseIcon,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n FolderIcon,\n GridIcon,\n Input,\n LockIcon,\n MessageIcon,\n NavTab,\n NavTabBadge,\n} from '@agimon-ai/po-web-components';\nimport { Link, useNavigate } from '@tanstack/react-router';\nimport { useStore } from '@tanstack/react-store';\n\nimport { useActivityGroups } from '../../lib/composition';\nimport { webTabs } from '../../lib/pluginRegistry';\nimport { useDashboardState } from '../../stores/dashboardStateStore';\nimport { sessionsStore, useActiveSessionMeta, useNoSessions } from '../../stores/sessionsStore';\nimport { renameSession, sessionStoreFor, setAgentLock, useActiveSession } from '../../stores/sessionStore';\nimport { closeTransientTab, useTransientTabs } from '../../stores/transientTabsStore';\n/** One registry tab: the badge hook is stable per tab, so the call is unconditional. */\nfunction PluginTab({ tab, sessionId, active }: { tab: TabContribution; sessionId: string; active: boolean }) {\n const useBadge = tab.useBadge ?? noBadge;\n const count = useBadge(sessionId);\n return (\n <NavTab asChild active={active}>\n <Link\n to=\"/session/$sessionId/$tabId\"\n params={{ sessionId, tabId: tab.id }}\n data-testid={`tab-${tab.id}`}\n className=\"shrink-0\"\n >\n {tab.label}\n {count > 0 ? (\n <NavTabBadge active={active} data-testid={`tab-${tab.id}-count`}>\n {count}\n </NavTabBadge>\n ) : null}\n </Link>\n </NavTab>\n );\n}\n\nfunction noBadge(): number {\n return 0;\n}\n\n/**\n * A tab a plugin opened at runtime: its link with the close beside it, since\n * a button cannot live inside a link. Closing the shown tab needs no\n * navigation of its own; the page falls back to the conversation once the\n * id is gone.\n */\nfunction TransientTabChip({ tab, sessionId, active }: { tab: TransientTab; sessionId: string; active: boolean }) {\n return (\n <span data-testid={`tab-${tab.id}-chip`} data-active={active} className=\"flex shrink-0 items-center gap-0.5\">\n <NavTab asChild active={active}>\n <Link\n to=\"/session/$sessionId/$tabId\"\n params={{ sessionId, tabId: tab.id }}\n data-testid={`tab-${tab.id}`}\n title={tab.label}\n className=\"block min-w-0 max-w-[160px] truncate\"\n >\n {tab.label}\n </Link>\n </NavTab>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n data-testid={`tab-${tab.id}-close`}\n title=\"close this tab\"\n onClick={() => closeTransientTab(sessionId, tab.id)}\n className=\"h-5 w-5 shrink-0\"\n >\n <CloseIcon className=\"h-2.5 w-2.5\" />\n </Button>\n </span>\n );\n}\n\n/** A plugin tab's count inside the menu; one component per tab keeps the badge hook unconditional. */\nfunction MenuBadge({ tab, sessionId }: { tab: TabContribution; sessionId: string }) {\n const count = (tab.useBadge ?? noBadge)(sessionId);\n return count > 0 ? <NavTabBadge className=\"ml-auto\">{count}</NavTabBadge> : null;\n}\n\n// The shown tab reads jade like the strip's active tab; 44px rows for touch.\nconst MENU_TAB_CLASS = 'min-h-11 data-[state=checked]:font-semibold data-[state=checked]:text-po-green';\n/**\n * Below sm the strip keeps only the shown tab, so the title, that tab and the\n * actions share one row; every tab, and the lock, are one tap away here.\n */\nfunction MobileTabsMenu({\n sessionId,\n view,\n mcpEnabled,\n transientTabs,\n onLock,\n}: {\n sessionId: string;\n view: string;\n mcpEnabled: boolean;\n transientTabs: readonly TransientTab[];\n onLock?: () => void;\n}) {\n const navigate = useNavigate();\n const pluginTabs = webTabs(sessionId);\n const open = (tabId: string): void => {\n if (tabId === 'conversation') void navigate({ to: '/session/$sessionId', params: { sessionId } });\n else void navigate({ to: '/session/$sessionId/$tabId', params: { sessionId, tabId } });\n };\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon-md\"\n data-testid=\"mobile-tabs-open\"\n title=\"Show tabs\"\n aria-label=\"Show tabs\"\n className=\"shrink-0 text-po-dim sm:hidden\"\n >\n <GridIcon className=\"size-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent data-testid=\"mobile-tabs-menu\" className=\"min-w-56 max-w-[calc(100vw-24px)]\">\n <DropdownMenuLabel>Tabs</DropdownMenuLabel>\n <DropdownMenuRadioGroup value={view} onValueChange={open}>\n <DropdownMenuRadioItem value=\"conversation\" data-testid=\"mobile-tab-conversation\" className={MENU_TAB_CLASS}>\n Conversation\n </DropdownMenuRadioItem>\n {mcpEnabled ? (\n <DropdownMenuRadioItem value=\"mcp\" data-testid=\"mobile-tab-mcp\" className={MENU_TAB_CLASS}>\n MCP\n </DropdownMenuRadioItem>\n ) : null}\n {pluginTabs.map((tab) => (\n <DropdownMenuRadioItem\n key={tab.id}\n value={tab.id}\n data-testid={`mobile-tab-${tab.id}`}\n className={MENU_TAB_CLASS}\n >\n <span className=\"min-w-0 truncate\">{tab.label}</span>\n <MenuBadge tab={tab} sessionId={sessionId} />\n </DropdownMenuRadioItem>\n ))}\n {transientTabs.map((tab) => (\n <DropdownMenuRadioItem\n key={tab.id}\n value={tab.id}\n data-testid={`mobile-tab-${tab.id}`}\n className={MENU_TAB_CLASS}\n >\n <span className=\"min-w-0 truncate\">{tab.label}</span>\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n {onLock ? (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem data-testid=\"mobile-agent-lock\" onSelect={onLock} className=\"min-h-11 pl-2\">\n <LockIcon aria-hidden className=\"size-4 shrink-0\" />\n Lock agent\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\nexport function TopBar({\n view = 'conversation',\n mcpEnabled = false,\n onShowSessions,\n onShowActivity,\n navigationToggle = 'mobile',\n activityToggle = 'mobile',\n}: {\n view?: string;\n mcpEnabled?: boolean;\n onShowSessions?: () => void;\n onShowActivity?: () => void;\n navigationToggle?: 'mobile' | 'always';\n activityToggle?: 'mobile' | 'always';\n}) {\n const meta = useActiveSessionMeta();\n const session = useActiveSession((state) => state);\n const activeId = useStore(sessionsStore, (state) => state.activeId);\n const noSessions = useNoSessions();\n const ownerId = useStore(sessionsStore, (state) => state.tabOwnerId ?? state.activeId);\n const transientTabs = useTransientTabs(ownerId);\n const ownerName = useStore(sessionsStore, (state) =>\n ownerId === null ? undefined : state.byId[ownerId]?.summary.name,\n );\n const ownerAgentName = useStore(sessionStoreFor(ownerId), (state) => state.agent?.sessionName);\n const title = ownerName || ownerAgentName || 'Untitled';\n const [renaming, setRenaming] = useDashboardState(`session-title/${ownerId}/open`, false);\n const [draft, setDraft] = useDashboardState(`session-title/${ownerId}/draft`, '');\n const backgroundWorkActive = useActivityGroups(session.statuses, session.widgets, activeId).some(\n (group) => group.active,\n );\n // The name lives in the agent, so the rail's rename and this one are the\n // same act through the same door; the new name arrives back on the socket.\n const commitRename = (): void => {\n const next = draft.trim();\n if (next && next !== title) renameSession(next, ownerId);\n setRenaming(false);\n };\n const lockAgent =\n activeId !== null && meta?.summary.dormant !== true && !session.agentLocked\n ? () => setAgentLock(true, activeId)\n : undefined;\n\n return (\n <header\n data-testid=\"top-bar\"\n className=\"flex min-h-16 shrink-0 items-center justify-between gap-2 border-b border-po-border-soft px-3 py-2 sm:gap-3 sm:px-6\"\n >\n <div data-testid=\"session-switcher\" className=\"flex min-w-0 flex-1 items-center gap-2 sm:gap-3\">\n {onShowSessions ? (\n <Button\n variant=\"ghost\"\n size=\"icon-md\"\n data-testid=\"mobile-sessions-open\"\n title=\"Show sessions\"\n aria-label=\"Show sessions\"\n onClick={onShowSessions}\n className={`shrink-0 text-po-dim ${navigationToggle === 'mobile' ? 'md:hidden' : ''}`}\n >\n <FolderIcon className=\"size-4\" />\n </Button>\n ) : null}\n {/* With no session there is no name to show, so the bar does not report\n 'Untitled' for something that was never started. */}\n {noSessions ? null : renaming && ownerId !== null ? (\n <Input\n data-testid=\"session-title-input\"\n value={draft}\n autoFocus\n aria-label=\"Session name\"\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') commitRename();\n if (event.key === 'Escape') setRenaming(false);\n }}\n onBlur={commitRename}\n className=\"h-9 w-56 shrink-0 border-po-blue/60 px-1.5 text-lg font-semibold\"\n />\n ) : (\n <Button\n variant=\"ghost\"\n data-testid=\"session-title\"\n title={ownerId === null ? undefined : 'Rename this session'}\n disabled={ownerId === null}\n onClick={() => {\n setDraft(title);\n setRenaming(true);\n }}\n className=\"block h-9 min-w-0 shrink truncate px-1.5 text-lg leading-9 font-semibold text-po-hi sm:max-w-80 sm:shrink-0\"\n >\n {title}\n </Button>\n )}\n {ownerId !== null ? (\n // Tabs never shrink; past the width the badges leave them, the strip\n // scrolls sideways (scrollbar hidden) instead of running under them.\n // On a phone only the shown tab stays; the rest are in the tabs menu.\n <nav\n aria-label=\"Conversation views\"\n className=\"flex min-w-0 items-center gap-1 overflow-x-auto [scrollbar-width:none] max-sm:shrink-0 max-sm:[&>*:not([data-active=true])]:hidden [&::-webkit-scrollbar]:hidden [&>*]:min-h-8 [&>*]:rounded-md [&>*]:px-3\"\n >\n <NavTab asChild active={view === 'conversation'}>\n <Link\n to=\"/session/$sessionId\"\n params={{ sessionId: ownerId }}\n data-testid=\"tab-conversation\"\n className=\"shrink-0 gap-1.5\"\n >\n <MessageIcon aria-hidden className=\"size-4\" />\n Conversation\n </Link>\n </NavTab>\n {mcpEnabled ? (\n <NavTab asChild active={view === 'mcp'}>\n <Link\n to=\"/session/$sessionId/$tabId\"\n params={{ sessionId: ownerId, tabId: 'mcp' }}\n data-testid=\"tab-mcp\"\n className=\"shrink-0\"\n >\n MCP\n </Link>\n </NavTab>\n ) : null}\n {webTabs(ownerId).map((tab) => (\n <PluginTab key={tab.id} tab={tab} sessionId={ownerId} active={view === tab.id} />\n ))}\n {transientTabs.map((tab) => (\n <TransientTabChip key={tab.id} tab={tab} sessionId={ownerId} active={view === tab.id} />\n ))}\n </nav>\n ) : null}\n </div>\n\n <div className=\"flex shrink-0 items-center\">\n {lockAgent ? (\n <Button\n variant=\"ghost\"\n data-testid=\"agent-lock\"\n size=\"lg\"\n title=\"Stop the local agent and keep it from starting turns\"\n onClick={lockAgent}\n className=\"hidden shrink-0 gap-2 text-po-text sm:inline-flex\"\n >\n <LockIcon className=\"size-4\" />\n Lock agent\n </Button>\n ) : null}\n {ownerId !== null ? (\n <MobileTabsMenu\n sessionId={ownerId}\n view={view}\n mcpEnabled={mcpEnabled}\n transientTabs={transientTabs}\n onLock={lockAgent}\n />\n ) : null}\n {onShowActivity ? (\n <Button\n variant=\"ghost\"\n size=\"icon-md\"\n data-testid=\"mobile-activity-open\"\n data-active={backgroundWorkActive}\n title={backgroundWorkActive ? 'Background work is running' : 'Show activity'}\n aria-label=\"Show activity\"\n onClick={onShowActivity}\n className={`shrink-0 ${activityToggle === 'mobile' ? 'lg:hidden' : ''} ${backgroundWorkActive ? 'text-po-yellow' : 'text-po-dim'}`}\n >\n <ActivityIcon className=\"size-4\" />\n </Button>\n ) : null}\n </div>\n </header>\n );\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { webTabs } from "../../lib/pluginRegistry.mjs";
2
2
  import { useActivityGroups } from "../../lib/composition.mjs";
3
3
  import { sessionsStore, useActiveSessionMeta, useNoSessions } from "../../stores/sessionsStore.mjs";
4
- import { renameSession, setAgentLock, useActiveSession } from "../../stores/sessionStore.mjs";
4
+ import { renameSession, sessionStoreFor, setAgentLock, useActiveSession } from "../../stores/sessionStore.mjs";
5
5
  import { closeTransientTab, useTransientTabs } from "../../stores/transientTabsStore.mjs";
6
6
  import { useDashboardState } from "../../stores/dashboardStateStore.mjs";
7
7
  import { Link, useNavigate } from "@tanstack/react-router";
@@ -173,13 +173,15 @@ function TopBar({ view = "conversation", mcpEnabled = false, onShowSessions, onS
173
173
  const noSessions = useNoSessions();
174
174
  const ownerId = useStore(sessionsStore, (state) => state.tabOwnerId ?? state.activeId);
175
175
  const transientTabs = useTransientTabs(ownerId);
176
- const title = meta?.summary.name || session.agent?.sessionName || "Untitled";
177
- const [renaming, setRenaming] = useDashboardState(`session-title/${activeId}/open`, false);
178
- const [draft, setDraft] = useDashboardState(`session-title/${activeId}/draft`, "");
176
+ const ownerName = useStore(sessionsStore, (state) => ownerId === null ? void 0 : state.byId[ownerId]?.summary.name);
177
+ const ownerAgentName = useStore(sessionStoreFor(ownerId), (state) => state.agent?.sessionName);
178
+ const title = ownerName || ownerAgentName || "Untitled";
179
+ const [renaming, setRenaming] = useDashboardState(`session-title/${ownerId}/open`, false);
180
+ const [draft, setDraft] = useDashboardState(`session-title/${ownerId}/draft`, "");
179
181
  const backgroundWorkActive = useActivityGroups(session.statuses, session.widgets, activeId).some((group) => group.active);
180
182
  const commitRename = () => {
181
183
  const next = draft.trim();
182
- if (next && next !== title) renameSession(next, activeId);
184
+ if (next && next !== title) renameSession(next, ownerId);
183
185
  setRenaming(false);
184
186
  };
185
187
  const lockAgent = activeId !== null && meta?.summary.dormant !== true && !session.agentLocked ? () => setAgentLock(true, activeId) : void 0;
@@ -200,7 +202,7 @@ function TopBar({ view = "conversation", mcpEnabled = false, onShowSessions, onS
200
202
  className: `shrink-0 text-po-dim ${navigationToggle === "mobile" ? "md:hidden" : ""}`,
201
203
  children: /* @__PURE__ */ jsx(FolderIcon, { className: "size-4" })
202
204
  }) : null,
203
- noSessions ? null : renaming && activeId !== null ? /* @__PURE__ */ jsx(Input, {
205
+ noSessions ? null : renaming && ownerId !== null ? /* @__PURE__ */ jsx(Input, {
204
206
  "data-testid": "session-title-input",
205
207
  value: draft,
206
208
  autoFocus: true,
@@ -215,8 +217,8 @@ function TopBar({ view = "conversation", mcpEnabled = false, onShowSessions, onS
215
217
  }) : /* @__PURE__ */ jsx(Button, {
216
218
  variant: "ghost",
217
219
  "data-testid": "session-title",
218
- title: activeId === null ? void 0 : "Rename this session",
219
- disabled: activeId === null,
220
+ title: ownerId === null ? void 0 : "Rename this session",
221
+ disabled: ownerId === null,
220
222
  onClick: () => {
221
223
  setDraft(title);
222
224
  setRenaming(true);
@@ -1 +1 @@
1
- {"version":3,"file":"TopBar.mjs","names":[],"sources":["../../../../src/web/features/status/TopBar.tsx"],"sourcesContent":["import type { TabContribution, TransientTab } from '@agimon-ai/po-core/web';\nimport {\n ActivityIcon,\n Button,\n CloseIcon,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n FolderIcon,\n GridIcon,\n Input,\n LockIcon,\n MessageIcon,\n NavTab,\n NavTabBadge,\n} from '@agimon-ai/po-web-components';\nimport { Link, useNavigate } from '@tanstack/react-router';\nimport { useStore } from '@tanstack/react-store';\n\nimport { useActivityGroups } from '../../lib/composition';\nimport { webTabs } from '../../lib/pluginRegistry';\nimport { useDashboardState } from '../../stores/dashboardStateStore';\nimport { sessionsStore, useActiveSessionMeta, useNoSessions } from '../../stores/sessionsStore';\nimport { renameSession, setAgentLock, useActiveSession } from '../../stores/sessionStore';\nimport { closeTransientTab, useTransientTabs } from '../../stores/transientTabsStore';\n/** One registry tab: the badge hook is stable per tab, so the call is unconditional. */\nfunction PluginTab({ tab, sessionId, active }: { tab: TabContribution; sessionId: string; active: boolean }) {\n const useBadge = tab.useBadge ?? noBadge;\n const count = useBadge(sessionId);\n return (\n <NavTab asChild active={active}>\n <Link\n to=\"/session/$sessionId/$tabId\"\n params={{ sessionId, tabId: tab.id }}\n data-testid={`tab-${tab.id}`}\n className=\"shrink-0\"\n >\n {tab.label}\n {count > 0 ? (\n <NavTabBadge active={active} data-testid={`tab-${tab.id}-count`}>\n {count}\n </NavTabBadge>\n ) : null}\n </Link>\n </NavTab>\n );\n}\n\nfunction noBadge(): number {\n return 0;\n}\n\n/**\n * A tab a plugin opened at runtime: its link with the close beside it, since\n * a button cannot live inside a link. Closing the shown tab needs no\n * navigation of its own; the page falls back to the conversation once the\n * id is gone.\n */\nfunction TransientTabChip({ tab, sessionId, active }: { tab: TransientTab; sessionId: string; active: boolean }) {\n return (\n <span data-testid={`tab-${tab.id}-chip`} data-active={active} className=\"flex shrink-0 items-center gap-0.5\">\n <NavTab asChild active={active}>\n <Link\n to=\"/session/$sessionId/$tabId\"\n params={{ sessionId, tabId: tab.id }}\n data-testid={`tab-${tab.id}`}\n title={tab.label}\n className=\"block min-w-0 max-w-[160px] truncate\"\n >\n {tab.label}\n </Link>\n </NavTab>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n data-testid={`tab-${tab.id}-close`}\n title=\"close this tab\"\n onClick={() => closeTransientTab(sessionId, tab.id)}\n className=\"h-5 w-5 shrink-0\"\n >\n <CloseIcon className=\"h-2.5 w-2.5\" />\n </Button>\n </span>\n );\n}\n\n/** A plugin tab's count inside the menu; one component per tab keeps the badge hook unconditional. */\nfunction MenuBadge({ tab, sessionId }: { tab: TabContribution; sessionId: string }) {\n const count = (tab.useBadge ?? noBadge)(sessionId);\n return count > 0 ? <NavTabBadge className=\"ml-auto\">{count}</NavTabBadge> : null;\n}\n\n// The shown tab reads jade like the strip's active tab; 44px rows for touch.\nconst MENU_TAB_CLASS = 'min-h-11 data-[state=checked]:font-semibold data-[state=checked]:text-po-green';\n/**\n * Below sm the strip keeps only the shown tab, so the title, that tab and the\n * actions share one row; every tab, and the lock, are one tap away here.\n */\nfunction MobileTabsMenu({\n sessionId,\n view,\n mcpEnabled,\n transientTabs,\n onLock,\n}: {\n sessionId: string;\n view: string;\n mcpEnabled: boolean;\n transientTabs: readonly TransientTab[];\n onLock?: () => void;\n}) {\n const navigate = useNavigate();\n const pluginTabs = webTabs(sessionId);\n const open = (tabId: string): void => {\n if (tabId === 'conversation') void navigate({ to: '/session/$sessionId', params: { sessionId } });\n else void navigate({ to: '/session/$sessionId/$tabId', params: { sessionId, tabId } });\n };\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon-md\"\n data-testid=\"mobile-tabs-open\"\n title=\"Show tabs\"\n aria-label=\"Show tabs\"\n className=\"shrink-0 text-po-dim sm:hidden\"\n >\n <GridIcon className=\"size-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent data-testid=\"mobile-tabs-menu\" className=\"min-w-56 max-w-[calc(100vw-24px)]\">\n <DropdownMenuLabel>Tabs</DropdownMenuLabel>\n <DropdownMenuRadioGroup value={view} onValueChange={open}>\n <DropdownMenuRadioItem value=\"conversation\" data-testid=\"mobile-tab-conversation\" className={MENU_TAB_CLASS}>\n Conversation\n </DropdownMenuRadioItem>\n {mcpEnabled ? (\n <DropdownMenuRadioItem value=\"mcp\" data-testid=\"mobile-tab-mcp\" className={MENU_TAB_CLASS}>\n MCP\n </DropdownMenuRadioItem>\n ) : null}\n {pluginTabs.map((tab) => (\n <DropdownMenuRadioItem\n key={tab.id}\n value={tab.id}\n data-testid={`mobile-tab-${tab.id}`}\n className={MENU_TAB_CLASS}\n >\n <span className=\"min-w-0 truncate\">{tab.label}</span>\n <MenuBadge tab={tab} sessionId={sessionId} />\n </DropdownMenuRadioItem>\n ))}\n {transientTabs.map((tab) => (\n <DropdownMenuRadioItem\n key={tab.id}\n value={tab.id}\n data-testid={`mobile-tab-${tab.id}`}\n className={MENU_TAB_CLASS}\n >\n <span className=\"min-w-0 truncate\">{tab.label}</span>\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n {onLock ? (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem data-testid=\"mobile-agent-lock\" onSelect={onLock} className=\"min-h-11 pl-2\">\n <LockIcon aria-hidden className=\"size-4 shrink-0\" />\n Lock agent\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\nexport function TopBar({\n view = 'conversation',\n mcpEnabled = false,\n onShowSessions,\n onShowActivity,\n navigationToggle = 'mobile',\n activityToggle = 'mobile',\n}: {\n view?: string;\n mcpEnabled?: boolean;\n onShowSessions?: () => void;\n onShowActivity?: () => void;\n navigationToggle?: 'mobile' | 'always';\n activityToggle?: 'mobile' | 'always';\n}) {\n const meta = useActiveSessionMeta();\n const session = useActiveSession((state) => state);\n const activeId = useStore(sessionsStore, (state) => state.activeId);\n const noSessions = useNoSessions();\n const ownerId = useStore(sessionsStore, (state) => state.tabOwnerId ?? state.activeId);\n const transientTabs = useTransientTabs(ownerId);\n const title = meta?.summary.name || session.agent?.sessionName || 'Untitled';\n const [renaming, setRenaming] = useDashboardState(`session-title/${activeId}/open`, false);\n const [draft, setDraft] = useDashboardState(`session-title/${activeId}/draft`, '');\n const backgroundWorkActive = useActivityGroups(session.statuses, session.widgets, activeId).some(\n (group) => group.active,\n );\n // The name lives in the agent, so the rail's rename and this one are the\n // same act through the same door; the new name arrives back on the socket.\n const commitRename = (): void => {\n const next = draft.trim();\n if (next && next !== title) renameSession(next, activeId);\n setRenaming(false);\n };\n const lockAgent =\n activeId !== null && meta?.summary.dormant !== true && !session.agentLocked\n ? () => setAgentLock(true, activeId)\n : undefined;\n\n return (\n <header\n data-testid=\"top-bar\"\n className=\"flex min-h-16 shrink-0 items-center justify-between gap-2 border-b border-po-border-soft px-3 py-2 sm:gap-3 sm:px-6\"\n >\n <div data-testid=\"session-switcher\" className=\"flex min-w-0 flex-1 items-center gap-2 sm:gap-3\">\n {onShowSessions ? (\n <Button\n variant=\"ghost\"\n size=\"icon-md\"\n data-testid=\"mobile-sessions-open\"\n title=\"Show sessions\"\n aria-label=\"Show sessions\"\n onClick={onShowSessions}\n className={`shrink-0 text-po-dim ${navigationToggle === 'mobile' ? 'md:hidden' : ''}`}\n >\n <FolderIcon className=\"size-4\" />\n </Button>\n ) : null}\n {/* With no session there is no name to show, so the bar does not report\n 'Untitled' for something that was never started. */}\n {noSessions ? null : renaming && activeId !== null ? (\n <Input\n data-testid=\"session-title-input\"\n value={draft}\n autoFocus\n aria-label=\"Session name\"\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') commitRename();\n if (event.key === 'Escape') setRenaming(false);\n }}\n onBlur={commitRename}\n className=\"h-9 w-56 shrink-0 border-po-blue/60 px-1.5 text-lg font-semibold\"\n />\n ) : (\n <Button\n variant=\"ghost\"\n data-testid=\"session-title\"\n title={activeId === null ? undefined : 'Rename this session'}\n disabled={activeId === null}\n onClick={() => {\n setDraft(title);\n setRenaming(true);\n }}\n className=\"block h-9 min-w-0 shrink truncate px-1.5 text-lg leading-9 font-semibold text-po-hi sm:max-w-80 sm:shrink-0\"\n >\n {title}\n </Button>\n )}\n {ownerId !== null ? (\n // Tabs never shrink; past the width the badges leave them, the strip\n // scrolls sideways (scrollbar hidden) instead of running under them.\n // On a phone only the shown tab stays; the rest are in the tabs menu.\n <nav\n aria-label=\"Conversation views\"\n className=\"flex min-w-0 items-center gap-1 overflow-x-auto [scrollbar-width:none] max-sm:shrink-0 max-sm:[&>*:not([data-active=true])]:hidden [&::-webkit-scrollbar]:hidden [&>*]:min-h-8 [&>*]:rounded-md [&>*]:px-3\"\n >\n <NavTab asChild active={view === 'conversation'}>\n <Link\n to=\"/session/$sessionId\"\n params={{ sessionId: ownerId }}\n data-testid=\"tab-conversation\"\n className=\"shrink-0 gap-1.5\"\n >\n <MessageIcon aria-hidden className=\"size-4\" />\n Conversation\n </Link>\n </NavTab>\n {mcpEnabled ? (\n <NavTab asChild active={view === 'mcp'}>\n <Link\n to=\"/session/$sessionId/$tabId\"\n params={{ sessionId: ownerId, tabId: 'mcp' }}\n data-testid=\"tab-mcp\"\n className=\"shrink-0\"\n >\n MCP\n </Link>\n </NavTab>\n ) : null}\n {webTabs(ownerId).map((tab) => (\n <PluginTab key={tab.id} tab={tab} sessionId={ownerId} active={view === tab.id} />\n ))}\n {transientTabs.map((tab) => (\n <TransientTabChip key={tab.id} tab={tab} sessionId={ownerId} active={view === tab.id} />\n ))}\n </nav>\n ) : null}\n </div>\n\n <div className=\"flex shrink-0 items-center\">\n {lockAgent ? (\n <Button\n variant=\"ghost\"\n data-testid=\"agent-lock\"\n size=\"lg\"\n title=\"Stop the local agent and keep it from starting turns\"\n onClick={lockAgent}\n className=\"hidden shrink-0 gap-2 text-po-text sm:inline-flex\"\n >\n <LockIcon className=\"size-4\" />\n Lock agent\n </Button>\n ) : null}\n {ownerId !== null ? (\n <MobileTabsMenu\n sessionId={ownerId}\n view={view}\n mcpEnabled={mcpEnabled}\n transientTabs={transientTabs}\n onLock={lockAgent}\n />\n ) : null}\n {onShowActivity ? (\n <Button\n variant=\"ghost\"\n size=\"icon-md\"\n data-testid=\"mobile-activity-open\"\n data-active={backgroundWorkActive}\n title={backgroundWorkActive ? 'Background work is running' : 'Show activity'}\n aria-label=\"Show activity\"\n onClick={onShowActivity}\n className={`shrink-0 ${activityToggle === 'mobile' ? 'lg:hidden' : ''} ${backgroundWorkActive ? 'text-po-yellow' : 'text-po-dim'}`}\n >\n <ActivityIcon className=\"size-4\" />\n </Button>\n ) : null}\n </div>\n </header>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TopBar.mjs","names":[],"sources":["../../../../src/web/features/status/TopBar.tsx"],"sourcesContent":["import type { TabContribution, TransientTab } from '@agimon-ai/po-core/web';\nimport {\n ActivityIcon,\n Button,\n CloseIcon,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n FolderIcon,\n GridIcon,\n Input,\n LockIcon,\n MessageIcon,\n NavTab,\n NavTabBadge,\n} from '@agimon-ai/po-web-components';\nimport { Link, useNavigate } from '@tanstack/react-router';\nimport { useStore } from '@tanstack/react-store';\n\nimport { useActivityGroups } from '../../lib/composition';\nimport { webTabs } from '../../lib/pluginRegistry';\nimport { useDashboardState } from '../../stores/dashboardStateStore';\nimport { sessionsStore, useActiveSessionMeta, useNoSessions } from '../../stores/sessionsStore';\nimport { renameSession, sessionStoreFor, setAgentLock, useActiveSession } from '../../stores/sessionStore';\nimport { closeTransientTab, useTransientTabs } from '../../stores/transientTabsStore';\n/** One registry tab: the badge hook is stable per tab, so the call is unconditional. */\nfunction PluginTab({ tab, sessionId, active }: { tab: TabContribution; sessionId: string; active: boolean }) {\n const useBadge = tab.useBadge ?? noBadge;\n const count = useBadge(sessionId);\n return (\n <NavTab asChild active={active}>\n <Link\n to=\"/session/$sessionId/$tabId\"\n params={{ sessionId, tabId: tab.id }}\n data-testid={`tab-${tab.id}`}\n className=\"shrink-0\"\n >\n {tab.label}\n {count > 0 ? (\n <NavTabBadge active={active} data-testid={`tab-${tab.id}-count`}>\n {count}\n </NavTabBadge>\n ) : null}\n </Link>\n </NavTab>\n );\n}\n\nfunction noBadge(): number {\n return 0;\n}\n\n/**\n * A tab a plugin opened at runtime: its link with the close beside it, since\n * a button cannot live inside a link. Closing the shown tab needs no\n * navigation of its own; the page falls back to the conversation once the\n * id is gone.\n */\nfunction TransientTabChip({ tab, sessionId, active }: { tab: TransientTab; sessionId: string; active: boolean }) {\n return (\n <span data-testid={`tab-${tab.id}-chip`} data-active={active} className=\"flex shrink-0 items-center gap-0.5\">\n <NavTab asChild active={active}>\n <Link\n to=\"/session/$sessionId/$tabId\"\n params={{ sessionId, tabId: tab.id }}\n data-testid={`tab-${tab.id}`}\n title={tab.label}\n className=\"block min-w-0 max-w-[160px] truncate\"\n >\n {tab.label}\n </Link>\n </NavTab>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n data-testid={`tab-${tab.id}-close`}\n title=\"close this tab\"\n onClick={() => closeTransientTab(sessionId, tab.id)}\n className=\"h-5 w-5 shrink-0\"\n >\n <CloseIcon className=\"h-2.5 w-2.5\" />\n </Button>\n </span>\n );\n}\n\n/** A plugin tab's count inside the menu; one component per tab keeps the badge hook unconditional. */\nfunction MenuBadge({ tab, sessionId }: { tab: TabContribution; sessionId: string }) {\n const count = (tab.useBadge ?? noBadge)(sessionId);\n return count > 0 ? <NavTabBadge className=\"ml-auto\">{count}</NavTabBadge> : null;\n}\n\n// The shown tab reads jade like the strip's active tab; 44px rows for touch.\nconst MENU_TAB_CLASS = 'min-h-11 data-[state=checked]:font-semibold data-[state=checked]:text-po-green';\n/**\n * Below sm the strip keeps only the shown tab, so the title, that tab and the\n * actions share one row; every tab, and the lock, are one tap away here.\n */\nfunction MobileTabsMenu({\n sessionId,\n view,\n mcpEnabled,\n transientTabs,\n onLock,\n}: {\n sessionId: string;\n view: string;\n mcpEnabled: boolean;\n transientTabs: readonly TransientTab[];\n onLock?: () => void;\n}) {\n const navigate = useNavigate();\n const pluginTabs = webTabs(sessionId);\n const open = (tabId: string): void => {\n if (tabId === 'conversation') void navigate({ to: '/session/$sessionId', params: { sessionId } });\n else void navigate({ to: '/session/$sessionId/$tabId', params: { sessionId, tabId } });\n };\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon-md\"\n data-testid=\"mobile-tabs-open\"\n title=\"Show tabs\"\n aria-label=\"Show tabs\"\n className=\"shrink-0 text-po-dim sm:hidden\"\n >\n <GridIcon className=\"size-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent data-testid=\"mobile-tabs-menu\" className=\"min-w-56 max-w-[calc(100vw-24px)]\">\n <DropdownMenuLabel>Tabs</DropdownMenuLabel>\n <DropdownMenuRadioGroup value={view} onValueChange={open}>\n <DropdownMenuRadioItem value=\"conversation\" data-testid=\"mobile-tab-conversation\" className={MENU_TAB_CLASS}>\n Conversation\n </DropdownMenuRadioItem>\n {mcpEnabled ? (\n <DropdownMenuRadioItem value=\"mcp\" data-testid=\"mobile-tab-mcp\" className={MENU_TAB_CLASS}>\n MCP\n </DropdownMenuRadioItem>\n ) : null}\n {pluginTabs.map((tab) => (\n <DropdownMenuRadioItem\n key={tab.id}\n value={tab.id}\n data-testid={`mobile-tab-${tab.id}`}\n className={MENU_TAB_CLASS}\n >\n <span className=\"min-w-0 truncate\">{tab.label}</span>\n <MenuBadge tab={tab} sessionId={sessionId} />\n </DropdownMenuRadioItem>\n ))}\n {transientTabs.map((tab) => (\n <DropdownMenuRadioItem\n key={tab.id}\n value={tab.id}\n data-testid={`mobile-tab-${tab.id}`}\n className={MENU_TAB_CLASS}\n >\n <span className=\"min-w-0 truncate\">{tab.label}</span>\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n {onLock ? (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem data-testid=\"mobile-agent-lock\" onSelect={onLock} className=\"min-h-11 pl-2\">\n <LockIcon aria-hidden className=\"size-4 shrink-0\" />\n Lock agent\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\nexport function TopBar({\n view = 'conversation',\n mcpEnabled = false,\n onShowSessions,\n onShowActivity,\n navigationToggle = 'mobile',\n activityToggle = 'mobile',\n}: {\n view?: string;\n mcpEnabled?: boolean;\n onShowSessions?: () => void;\n onShowActivity?: () => void;\n navigationToggle?: 'mobile' | 'always';\n activityToggle?: 'mobile' | 'always';\n}) {\n const meta = useActiveSessionMeta();\n const session = useActiveSession((state) => state);\n const activeId = useStore(sessionsStore, (state) => state.activeId);\n const noSessions = useNoSessions();\n const ownerId = useStore(sessionsStore, (state) => state.tabOwnerId ?? state.activeId);\n const transientTabs = useTransientTabs(ownerId);\n const ownerName = useStore(sessionsStore, (state) =>\n ownerId === null ? undefined : state.byId[ownerId]?.summary.name,\n );\n const ownerAgentName = useStore(sessionStoreFor(ownerId), (state) => state.agent?.sessionName);\n const title = ownerName || ownerAgentName || 'Untitled';\n const [renaming, setRenaming] = useDashboardState(`session-title/${ownerId}/open`, false);\n const [draft, setDraft] = useDashboardState(`session-title/${ownerId}/draft`, '');\n const backgroundWorkActive = useActivityGroups(session.statuses, session.widgets, activeId).some(\n (group) => group.active,\n );\n // The name lives in the agent, so the rail's rename and this one are the\n // same act through the same door; the new name arrives back on the socket.\n const commitRename = (): void => {\n const next = draft.trim();\n if (next && next !== title) renameSession(next, ownerId);\n setRenaming(false);\n };\n const lockAgent =\n activeId !== null && meta?.summary.dormant !== true && !session.agentLocked\n ? () => setAgentLock(true, activeId)\n : undefined;\n\n return (\n <header\n data-testid=\"top-bar\"\n className=\"flex min-h-16 shrink-0 items-center justify-between gap-2 border-b border-po-border-soft px-3 py-2 sm:gap-3 sm:px-6\"\n >\n <div data-testid=\"session-switcher\" className=\"flex min-w-0 flex-1 items-center gap-2 sm:gap-3\">\n {onShowSessions ? (\n <Button\n variant=\"ghost\"\n size=\"icon-md\"\n data-testid=\"mobile-sessions-open\"\n title=\"Show sessions\"\n aria-label=\"Show sessions\"\n onClick={onShowSessions}\n className={`shrink-0 text-po-dim ${navigationToggle === 'mobile' ? 'md:hidden' : ''}`}\n >\n <FolderIcon className=\"size-4\" />\n </Button>\n ) : null}\n {/* With no session there is no name to show, so the bar does not report\n 'Untitled' for something that was never started. */}\n {noSessions ? null : renaming && ownerId !== null ? (\n <Input\n data-testid=\"session-title-input\"\n value={draft}\n autoFocus\n aria-label=\"Session name\"\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') commitRename();\n if (event.key === 'Escape') setRenaming(false);\n }}\n onBlur={commitRename}\n className=\"h-9 w-56 shrink-0 border-po-blue/60 px-1.5 text-lg font-semibold\"\n />\n ) : (\n <Button\n variant=\"ghost\"\n data-testid=\"session-title\"\n title={ownerId === null ? undefined : 'Rename this session'}\n disabled={ownerId === null}\n onClick={() => {\n setDraft(title);\n setRenaming(true);\n }}\n className=\"block h-9 min-w-0 shrink truncate px-1.5 text-lg leading-9 font-semibold text-po-hi sm:max-w-80 sm:shrink-0\"\n >\n {title}\n </Button>\n )}\n {ownerId !== null ? (\n // Tabs never shrink; past the width the badges leave them, the strip\n // scrolls sideways (scrollbar hidden) instead of running under them.\n // On a phone only the shown tab stays; the rest are in the tabs menu.\n <nav\n aria-label=\"Conversation views\"\n className=\"flex min-w-0 items-center gap-1 overflow-x-auto [scrollbar-width:none] max-sm:shrink-0 max-sm:[&>*:not([data-active=true])]:hidden [&::-webkit-scrollbar]:hidden [&>*]:min-h-8 [&>*]:rounded-md [&>*]:px-3\"\n >\n <NavTab asChild active={view === 'conversation'}>\n <Link\n to=\"/session/$sessionId\"\n params={{ sessionId: ownerId }}\n data-testid=\"tab-conversation\"\n className=\"shrink-0 gap-1.5\"\n >\n <MessageIcon aria-hidden className=\"size-4\" />\n Conversation\n </Link>\n </NavTab>\n {mcpEnabled ? (\n <NavTab asChild active={view === 'mcp'}>\n <Link\n to=\"/session/$sessionId/$tabId\"\n params={{ sessionId: ownerId, tabId: 'mcp' }}\n data-testid=\"tab-mcp\"\n className=\"shrink-0\"\n >\n MCP\n </Link>\n </NavTab>\n ) : null}\n {webTabs(ownerId).map((tab) => (\n <PluginTab key={tab.id} tab={tab} sessionId={ownerId} active={view === tab.id} />\n ))}\n {transientTabs.map((tab) => (\n <TransientTabChip key={tab.id} tab={tab} sessionId={ownerId} active={view === tab.id} />\n ))}\n </nav>\n ) : null}\n </div>\n\n <div className=\"flex shrink-0 items-center\">\n {lockAgent ? (\n <Button\n variant=\"ghost\"\n data-testid=\"agent-lock\"\n size=\"lg\"\n title=\"Stop the local agent and keep it from starting turns\"\n onClick={lockAgent}\n className=\"hidden shrink-0 gap-2 text-po-text sm:inline-flex\"\n >\n <LockIcon className=\"size-4\" />\n Lock agent\n </Button>\n ) : null}\n {ownerId !== null ? (\n <MobileTabsMenu\n sessionId={ownerId}\n view={view}\n mcpEnabled={mcpEnabled}\n transientTabs={transientTabs}\n onLock={lockAgent}\n />\n ) : null}\n {onShowActivity ? (\n <Button\n variant=\"ghost\"\n size=\"icon-md\"\n data-testid=\"mobile-activity-open\"\n data-active={backgroundWorkActive}\n title={backgroundWorkActive ? 'Background work is running' : 'Show activity'}\n aria-label=\"Show activity\"\n onClick={onShowActivity}\n className={`shrink-0 ${activityToggle === 'mobile' ? 'lg:hidden' : ''} ${backgroundWorkActive ? 'text-po-yellow' : 'text-po-dim'}`}\n >\n <ActivityIcon className=\"size-4\" />\n </Button>\n ) : null}\n </div>\n </header>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/po-web-dashboard",
3
- "version": "0.0.0-alpha.78",
3
+ "version": "0.0.0-alpha.79",
4
4
  "description": "Shared browser state, actions and optional views for Po dashboard templates.",
5
5
  "keywords": [
6
6
  "ai",
@@ -61,9 +61,9 @@
61
61
  "access": "public"
62
62
  },
63
63
  "dependencies": {
64
- "@agimon-ai/po-core": "0.0.0-alpha.78",
65
- "@agimon-ai/po-web-components": "0.0.0-alpha.78",
66
- "@agimon-ai/po-web-security": "0.0.0-alpha.78",
64
+ "@agimon-ai/po-core": "0.0.0-alpha.79",
65
+ "@agimon-ai/po-web-components": "0.0.0-alpha.79",
66
+ "@agimon-ai/po-web-security": "0.0.0-alpha.79",
67
67
  "@earendil-works/chord": "1.4.0",
68
68
  "@simplewebauthn/browser": "^14.0.0",
69
69
  "@tanstack/react-router": "1.170.45",
@@ -73,7 +73,7 @@
73
73
  "qrcode-generator": "^2.0.4"
74
74
  },
75
75
  "devDependencies": {
76
- "@agimon-ai/vibe-lint-plugin-po-web": "0.0.0-alpha.78",
76
+ "@agimon-ai/vibe-lint-plugin-po-web": "0.0.0-alpha.79",
77
77
  "@types/node": "26.6.5",
78
78
  "@types/react": "19.3.0",
79
79
  "@types/react-dom": "19.3.0",