@kortix/ui 0.14.6 → 0.14.8

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 (186) hide show
  1. package/dist/_vendor/kortix-shared.js +490 -382
  2. package/dist/_vendor/shared/runtime-versions.d.ts +4 -0
  3. package/dist/access/access/access-dialog-share.js +4 -7
  4. package/dist/access/access/access-dialog.js +2 -1
  5. package/dist/access/access/principal-picker.js +6 -5
  6. package/dist/customize/agents/agent-authority-card.js +1 -2
  7. package/dist/customize/agents/agent-cover-card.d.ts +17 -0
  8. package/dist/customize/agents/agent-cover-card.js +13 -0
  9. package/dist/customize/agents/agent-detail-aside.js +15 -14
  10. package/dist/customize/agents/agent-drives-section.js +5 -6
  11. package/dist/customize/agents/agent-grant-pages.js +68 -70
  12. package/dist/customize/agents/agent-page.js +27 -19
  13. package/dist/customize/agents/agent-people-section.js +18 -17
  14. package/dist/customize/agents/agent-triggers-section.js +6 -5
  15. package/dist/customize/agents/agents-page.js +43 -49
  16. package/dist/customize/connectors/catalog/catalog-entry.d.ts +14 -0
  17. package/dist/customize/connectors/catalog/catalog-entry.js +18 -1
  18. package/dist/customize/connectors/catalog/connector-browse.d.ts +28 -0
  19. package/dist/customize/connectors/catalog/connector-browse.js +18 -5
  20. package/dist/customize/connectors/detail/app-page.js +19 -5
  21. package/dist/customize/connectors/detail/connector-modal.d.ts +10 -13
  22. package/dist/customize/connectors/detail/connector-modal.js +18 -101
  23. package/dist/customize/connectors/detail/connector-notice.js +6 -2
  24. package/dist/customize/connectors/detail/connector-page.d.ts +30 -0
  25. package/dist/customize/connectors/detail/connector-page.js +26 -13
  26. package/dist/customize/migrate-to-v2/migration-prompt.d.ts +1 -0
  27. package/dist/customize/migrate-to-v2/migration-prompt.js +1 -0
  28. package/dist/customize/sections/llm-provider/custom-provider-form.js +24 -7
  29. package/dist/customize/sections/llm-provider/custom-provider-panel.js +44 -8
  30. package/dist/customize/sections/llm-provider/types.d.ts +3 -0
  31. package/dist/customize/sections/llm-provider/utils.js +2 -2
  32. package/dist/customize/sections/view/agent-editor-access-fields.d.ts +9 -16
  33. package/dist/customize/sections/view/agent-editor-access-fields.js +30 -30
  34. package/dist/customize/sections/view/agent-editor-basics-fields.js +3 -10
  35. package/dist/customize/sections/view/agent-editor-primitives.d.ts +28 -10
  36. package/dist/customize/sections/view/agent-editor-primitives.js +32 -19
  37. package/dist/customize/sections/view/agent-editor.js +3 -3
  38. package/dist/customize/sections/view/channels-view.js +2 -6
  39. package/dist/customize/sections/view/gateway/gateway-routing.js +3 -3
  40. package/dist/customize/sections/view/grant-mode-field.d.ts +2 -5
  41. package/dist/customize/sections/view/grant-mode-field.js +10 -18
  42. package/dist/customize/sections/view/permission-editor.js +11 -8
  43. package/dist/customize/sections/view/secrets-view.js +2 -1
  44. package/dist/customize/settings/tabs/general-tab.d.ts +9 -2
  45. package/dist/customize/settings/tabs/general-tab.js +33 -11
  46. package/dist/customize/settings/tabs/transfer-workspace-dialog.d.ts +4 -0
  47. package/dist/customize/settings/tabs/transfer-workspace-dialog.js +82 -0
  48. package/dist/customize/shared/new-entity-menu.js +4 -1
  49. package/dist/customize/skills/skills-page.js +4 -5
  50. package/dist/customize/triggers/schedule/then-fields.js +1 -5
  51. package/dist/customize/triggers/schedule-view.js +4 -2
  52. package/dist/files/project/api/runtime-files.js +3 -2
  53. package/dist/files/project/file-source.js +18 -1
  54. package/dist/files/project/hooks/use-file-content.js +2 -0
  55. package/dist/files/viewer/file-header.js +1 -1
  56. package/dist/files/viewer/file-preview-modal.d.ts +1 -1
  57. package/dist/files/viewer/file-preview-modal.js +12 -2
  58. package/dist/files/viewer/file-source.d.ts +3 -1
  59. package/dist/files/viewer/use-file-content-state.js +26 -7
  60. package/dist/files/viewer/viewer-actions.js +2 -2
  61. package/dist/i18n/en.d.ts +393 -12
  62. package/dist/i18n/en.js +400 -19
  63. package/dist/internal/components/agent-mark.d.ts +38 -0
  64. package/dist/internal/components/agent-mark.js +38 -0
  65. package/dist/internal/files/types.d.ts +2 -0
  66. package/dist/internal/goals/goal-meta.d.ts +30 -0
  67. package/dist/internal/goals/goal-meta.js +55 -0
  68. package/dist/internal/lib/pixel-mark.d.ts +33 -0
  69. package/dist/internal/lib/pixel-mark.js +152 -0
  70. package/dist/internal/lib/query-client-singleton.d.ts +9 -3
  71. package/dist/internal/lib/query-client-singleton.js +18 -12
  72. package/dist/internal/lib/use-project-feature-flags.js +3 -3
  73. package/dist/internal/share/session-shares-modal.js +3 -7
  74. package/dist/internal/stores/kortix-computer-store.js +14 -2
  75. package/dist/internal/stores/session-hover-store.d.ts +32 -0
  76. package/dist/internal/stores/session-hover-store.js +38 -0
  77. package/dist/internal/stores/sidebar-nav-store.d.ts +1 -1
  78. package/dist/internal/stores/sidebar-nav-store.js +3 -1
  79. package/dist/lib/routes.d.ts +2 -0
  80. package/dist/lib/routes.js +1 -0
  81. package/dist/markdown/genui/components/inline.d.ts +1 -1
  82. package/dist/markdown/setup-links/app-credentials-form.d.ts +12 -0
  83. package/dist/markdown/setup-links/app-credentials-form.js +115 -0
  84. package/dist/markdown/setup-links/secret-intake-form.d.ts +10 -0
  85. package/dist/markdown/setup-links/secret-intake-form.js +1 -1
  86. package/dist/primitives/entity-avatar.d.ts +4 -1
  87. package/dist/primitives/entity-avatar.js +4 -2
  88. package/dist/primitives/settings-section-header.d.ts +1 -1
  89. package/dist/primitives/user-avatar.d.ts +3 -1
  90. package/dist/primitives/user-avatar.js +4 -2
  91. package/dist/providers/ui-provider.d.ts +1 -1
  92. package/dist/session/composer/agent-selector.d.ts +7 -1
  93. package/dist/session/composer/agent-selector.js +20 -10
  94. package/dist/session/composer/attachment-tile.d.ts +0 -2
  95. package/dist/session/composer/attachment-tile.js +0 -8
  96. package/dist/session/composer/clipboard-files.d.ts +2 -1
  97. package/dist/session/composer/composer-draft-recovery.d.ts +1 -2
  98. package/dist/session/composer/composer-draft-recovery.js +0 -13
  99. package/dist/session/composer/context-ring.d.ts +1 -1
  100. package/dist/session/composer/context-ring.js +2 -2
  101. package/dist/session/composer/context-usage.d.ts +8 -6
  102. package/dist/session/composer/context-usage.js +9 -7
  103. package/dist/session/composer/inbox-row-claims.d.ts +2 -2
  104. package/dist/session/composer/inbox-row-claims.js +1 -1
  105. package/dist/session/composer/queue-projection.d.ts +3 -2
  106. package/dist/session/composer/queued-prompt-edit.d.ts +1 -1
  107. package/dist/session/composer/reasoning-effort-selector.d.ts +2 -1
  108. package/dist/session/composer/session-composer-readiness.d.ts +7 -22
  109. package/dist/session/composer/session-composer-readiness.js +12 -40
  110. package/dist/session/composer/token-progress.d.ts +1 -1
  111. package/dist/session/composer/token-progress.js +6 -6
  112. package/dist/session/header/export-transcript-modal.d.ts +3 -2
  113. package/dist/session/header/export-transcript-modal.js +6 -3
  114. package/dist/session/header/session-goal-indicator.d.ts +1 -0
  115. package/dist/session/header/session-goal-indicator.js +41 -0
  116. package/dist/session/header/session-site-header.js +2 -1
  117. package/dist/session/models/model-selector.d.ts +3 -1
  118. package/dist/session/models/model-selector.js +3 -2
  119. package/dist/session/panel/easy/plan-card.d.ts +2 -0
  120. package/dist/session/panel/easy/plan-card.js +2 -0
  121. package/dist/session/panel/session-action-panel-column.js +11 -1
  122. package/dist/session/panel/session-audit-shared.d.ts +0 -4
  123. package/dist/session/panel/session-audit-shared.js +1 -1
  124. package/dist/session/panel/session-changes-shared.d.ts +2 -1
  125. package/dist/session/prompts/session-permission-prompt.js +6 -3
  126. package/dist/session/status/provisioning-failure.d.ts +2 -1
  127. package/dist/session/status/session-error-banner.js +8 -6
  128. package/dist/session/status/session-starting-loader.d.ts +24 -31
  129. package/dist/session/status/session-starting-loader.js +32 -81
  130. package/dist/session/thread/chat-minimap-items.d.ts +4 -4
  131. package/dist/session/thread/first-prompt-handover.d.ts +3 -2
  132. package/dist/session/thread/message-parsing.d.ts +3 -3
  133. package/dist/session/thread/plan-surface.d.ts +1 -0
  134. package/dist/session/thread/plan-surface.js +1 -0
  135. package/dist/session/thread/session-chat.js +5 -12
  136. package/dist/session/thread/session-context-metrics.d.ts +15 -0
  137. package/dist/session/thread/session-context-metrics.js +10 -1
  138. package/dist/session/thread/session-context-modal.d.ts +4 -2
  139. package/dist/session/thread/session-context-modal.js +16 -9
  140. package/dist/session/thread/session-history-scroll.d.ts +2 -1
  141. package/dist/session/thread/session-thread.js +1 -1
  142. package/dist/session/thread/transcript.d.ts +4 -0
  143. package/dist/session/thread/transcript.js +18 -5
  144. package/dist/session/thread/turn-share.d.ts +16 -0
  145. package/dist/session/thread/turn-share.js +162 -0
  146. package/dist/session/tool/tools/apply-patch-tool.d.ts +1 -0
  147. package/dist/session/tool/tools/apply-patch-tool.js +1 -0
  148. package/dist/session/tool/tools/pty-kill-tool.d.ts +1 -0
  149. package/dist/session/tool/tools/pty-kill-tool.js +1 -0
  150. package/dist/session/tool/tools/pty-read-tool.d.ts +2 -0
  151. package/dist/session/tool/tools/pty-read-tool.js +2 -0
  152. package/dist/session/tool/tools/pty-spawn-tool.d.ts +1 -0
  153. package/dist/session/tool/tools/pty-spawn-tool.js +1 -0
  154. package/dist/session/tool/tools/pty-write-tool.d.ts +1 -0
  155. package/dist/session/tool/tools/pty-write-tool.js +1 -0
  156. package/dist/session/tool/tools/todo-write-tool.d.ts +1 -0
  157. package/dist/session/tool/tools/todo-write-tool.js +1 -0
  158. package/dist/session/turn/message-author-label.js +6 -2
  159. package/dist/session/turn/plan-anchor.d.ts +3 -0
  160. package/dist/session/turn/plan-anchor.js +3 -0
  161. package/dist/session/turn/plan-card.d.ts +7 -0
  162. package/dist/session/turn/plan-card.js +7 -0
  163. package/dist/styles.css +1 -1
  164. package/dist/terminal/pty-connection.d.ts +5 -4
  165. package/dist/terminal/session-terminal-connect-bar.js +2 -1
  166. package/dist/workspace/index.d.ts +1 -0
  167. package/dist/workspace/index.js +1 -0
  168. package/dist/workspace/saved-session-skeleton-shape.d.ts +4 -3
  169. package/dist/workspace/saved-session-skeleton.d.ts +5 -1
  170. package/dist/workspace/saved-session-skeleton.js +5 -4
  171. package/dist/workspace/session-workspace.js +4 -5
  172. package/dist/workspace/sidebar/footer/project-goals-nav.d.ts +6 -0
  173. package/dist/workspace/sidebar/footer/project-goals-nav.js +26 -0
  174. package/dist/workspace/sidebar/modal/session-embed-section.d.ts +25 -0
  175. package/dist/workspace/sidebar/modal/session-embed-section.js +71 -0
  176. package/dist/workspace/sidebar/modal/share-session-modal.js +2 -2
  177. package/dist/workspace/sidebar/nav-row-meta.js +14 -2
  178. package/dist/workspace/sidebar/project-nav-items.js +3 -0
  179. package/dist/workspace/sidebar/project-session-list.js +12 -3
  180. package/dist/workspace/sidebar/session-brief-hover-card.js +33 -9
  181. package/package.json +12 -4
  182. package/skills/kortix-ui/references/modules.md +2 -0
  183. package/dist/session/status/use-readiness-settling.d.ts +0 -18
  184. package/dist/session/status/use-readiness-settling.js +0 -43
  185. package/dist/workspace/sidebar/modal/session-public-link-section.d.ts +0 -13
  186. package/dist/workspace/sidebar/modal/session-public-link-section.js +0 -66
@@ -9,7 +9,7 @@ export type PtyCloseAction = 'ended' | 'reconnect' | 'replace';
9
9
  * terminal asks `GET /kortix/pty`, which passes the same control-plane gate and
10
10
  * returns a readable body. That GET never wakes a box.
11
11
  */
12
- export type PtyAttachProbe = 'reachable' | 'not-ready' | 'unreachable';
12
+ type PtyAttachProbe = 'reachable' | 'not-ready' | 'unreachable';
13
13
  /** `null` = the PTY list answered. Anything else is the error it threw. */
14
14
  export declare function classifyPtyAttachProbe(error: unknown): PtyAttachProbe;
15
15
  /**
@@ -36,10 +36,10 @@ export declare const PTY_WAKE_RETRY_MS = 2000;
36
36
  export declare const PTY_WAKE_DEADLINE_MS: number;
37
37
  /** Consecutive non-wake failures before the terminal stops and offers Retry. */
38
38
  export declare const PTY_MAX_ATTACH_FAILURES = 5;
39
- export type PtyAttachPause = 'asleep' | 'failed';
39
+ type PtyAttachPause = 'asleep' | 'failed';
40
40
  /** What the attach overlay shows. `null` means the shell is live. */
41
41
  export type AttachPhase = 'connecting' | 'waking' | 'reconnecting' | PtyAttachPause | null;
42
- export type PtyAttachStep = {
42
+ type PtyAttachStep = {
43
43
  kind: 'retry';
44
44
  delayMs: number;
45
45
  phase: 'waking' | 'reconnecting';
@@ -56,7 +56,7 @@ export declare function nextPtyAttachStep(input: {
56
56
  /** How long the box has reported not-ready while a wake was armed. */
57
57
  wakingForMs: number;
58
58
  }): PtyAttachStep;
59
- export type TerminalPanelState = 'connecting' | 'error' | 'empty' | 'terminal';
59
+ type TerminalPanelState = 'connecting' | 'error' | 'empty' | 'terminal';
60
60
  export declare function deriveTerminalPanelState(input: {
61
61
  hasServerUrl: boolean;
62
62
  serverWaitExpired: boolean;
@@ -81,3 +81,4 @@ export declare function classifyPtyClose(input: {
81
81
  }): PtyCloseAction;
82
82
  /** Prevent a bad runtime from creating terminals forever in a replacement loop. */
83
83
  export declare function shouldAutoReplaceTerminal(replacementAttempt: number): boolean;
84
+ export {};
@@ -25,7 +25,8 @@ export function SessionTerminalConnectBar({ projectSessionId }) {
25
25
  export function SessionTerminalConnectSteps({ id, projectSessionId, }) {
26
26
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
27
27
  const installCmd = useDeploymentCliInstallCommand(undefined);
28
- const connectCmd = `kortix sessions connect ${projectSessionId}`;
28
+ // A shell through the Kortix PTY: it works on every harness. `sessions connect` attaches OpenCode's own TUI and fails on pi.
29
+ const connectCmd = `kortix sessions shell ${projectSessionId}`;
29
30
  return (_jsxs("ol", { id: id, className: "space-y-3 pr-1.5 pb-3 pl-3", children: [_jsx(CommandStep, { label: tI18nComplete.raw('textd236e9cd730b'), command: installCmd }), _jsx(CommandStep, { label: tI18nComplete.raw('text842bad27af42'), command: connectCmd })] }));
30
31
  }
31
32
  function CommandStep({ label, command }) {
@@ -23,3 +23,4 @@ export { ProjectSessionsView } from './sessions/project-sessions-view.js';
23
23
  export { ProjectSessionList } from './sidebar/project-session-list.js';
24
24
  export { ProjectSidebar } from './sidebar/project-sidebar.js';
25
25
  export { ShareSessionModal } from './sidebar/modal/share-session-modal.js';
26
+ export { formatGoalTime, GoalHealthDot, goalHref, goalNeedsYou, goalOpenAsks, goalProgress, GoalStatusBadge, useGoalClock, useGoalIssueText, } from '../internal/goals/goal-meta.js';
@@ -23,3 +23,4 @@ export { ProjectSessionsView } from './sessions/project-sessions-view.js';
23
23
  export { ProjectSessionList } from './sidebar/project-session-list.js';
24
24
  export { ProjectSidebar } from './sidebar/project-sidebar.js';
25
25
  export { ShareSessionModal } from './sidebar/modal/share-session-modal.js';
26
+ export { formatGoalTime, GoalHealthDot, goalHref, goalNeedsYou, goalOpenAsks, goalProgress, GoalStatusBadge, useGoalClock, useGoalIssueText, } from '../internal/goals/goal-meta.js';
@@ -23,12 +23,12 @@ export declare const LINE_WIDTHS: readonly ["w-full", "w-11/12", "w-5/6"];
23
23
  export declare const LAST_LINE_WIDTHS: readonly ["w-1/3", "w-2/5", "w-1/2", "w-3/5", "w-2/3", "w-3/4"];
24
24
  /** Tailwind's `animate-pulse` period, which the `Skeleton` primitive runs. */
25
25
  export declare const PULSE_PERIOD_MS = 2000;
26
- export interface SkeletonLine {
26
+ interface SkeletonLine {
27
27
  width: string;
28
28
  /** The row's place in the pulse wave; see `pulseDelayMs`. */
29
29
  phase: number;
30
30
  }
31
- export type SkeletonParagraph = {
31
+ type SkeletonParagraph = {
32
32
  kind: 'text';
33
33
  lines: SkeletonLine[];
34
34
  }
@@ -37,7 +37,7 @@ export type SkeletonParagraph = {
37
37
  kind: 'block';
38
38
  phase: number;
39
39
  };
40
- export interface SkeletonTurn {
40
+ interface SkeletonTurn {
41
41
  prompt: SkeletonLine & {
42
42
  tall: boolean;
43
43
  };
@@ -63,3 +63,4 @@ export declare function savedSessionSkeletonShape(sessionId: string): SkeletonSh
63
63
  * its cycle, so every row is on screen and moving from the first frame.
64
64
  */
65
65
  export declare function pulseDelayMs(phase: number, phases: number): number;
66
+ export {};
@@ -1,7 +1,11 @@
1
1
  import type { SessionStartStage } from '@kortix/sdk';
2
- export declare function SavedSessionSkeleton({ projectId, sessionId, stage, }: {
2
+ import { type ReactNode } from 'react';
3
+ export declare function SavedSessionSkeleton({ projectId, sessionId, stage, status, }: {
3
4
  projectId: string;
4
5
  /** The route's session id. */
5
6
  sessionId: string;
6
7
  stage: SessionStartStage;
8
+ /** Drawn in place of the skeleton rows: there is no saved conversation to
9
+ * stand in for, only the wake's status. */
10
+ status?: ReactNode;
7
11
  }): import("react").JSX.Element;
@@ -15,8 +15,9 @@ import { useMemo } from 'react';
15
15
  * The control plane keeps a saved copy of every session's conversation and
16
16
  * answers in one round trip; the computer takes 5-240 s to wake. So a session
17
17
  * that has a saved copy opens on this — its header, skeleton turns, and the
18
- * composer's outline — and the conversation replaces it in one step. The boot
19
- * screen is only for a session with nothing to read (`resolveResumeOverlay`).
18
+ * composer's outline — and the conversation replaces it in one step. A session
19
+ * with nothing to read (`resolveResumeOverlay`) gets the same frame with a
20
+ * `status` in place of the rows.
20
21
  *
21
22
  * Built from the geometry the real chat uses (`SessionLayout`, `SessionBodyRow`,
22
23
  * `SESSION_TRANSCRIPT_CLASS`, `COMPOSER_SHELL_CLASS`), so the crossfade into
@@ -36,8 +37,8 @@ import { useMemo } from 'react';
36
37
  * open waits 0.3-2.3 s for the server's copy, so its rows come 200 ms later.
37
38
  */
38
39
  const APPEAR_AFTER_A_MOMENT = 'animate-in fade-in fill-mode-both duration-normal delay-[var(--duration-moderate)]';
39
- export function SavedSessionSkeleton({ projectId, sessionId, stage, }) {
40
+ export function SavedSessionSkeleton({ projectId, sessionId, stage, status, }) {
40
41
  const t = useTranslations('sessionPage');
41
42
  const shape = useMemo(() => savedSessionSkeletonShape(sessionId), [sessionId]);
42
- return (_jsx(SessionLayout, { sessionId: sessionId, projectId: projectId, projectSessionId: sessionId, transient: true, bootStage: stage === 'ready' ? null : stage, children: _jsxs("div", { className: "bg-background relative flex h-full flex-col", children: [_jsx(SessionSiteHeader, { sessionId: sessionId, sessionTitle: "" }), _jsxs(SessionBodyRow, { transient: true, children: [_jsx("div", { className: "relative z-10 min-h-0 flex-1 overflow-hidden", children: _jsx("div", { role: "status", "aria-busy": "true", "aria-label": t('loadingConversation'), "data-testid": "saved-session-skeleton", className: cn(SESSION_TRANSCRIPT_CLASS, APPEAR_AFTER_A_MOMENT), children: _jsx(SavedSessionSkeletonRows, { shape: shape }) }) }), _jsx("div", { "aria-hidden": true, className: cn(COMPOSER_SHELL_CLASS, 'pb-8', APPEAR_AFTER_A_MOMENT), children: _jsx(SkeletonBar, { phase: shape.composerPhase, phases: shape.phases, className: "h-28 w-full rounded-xl" }) })] })] }) }));
43
+ return (_jsx(SessionLayout, { sessionId: sessionId, projectId: projectId, projectSessionId: sessionId, transient: true, bootStage: stage === 'ready' ? null : stage, children: _jsxs("div", { className: "bg-background relative flex h-full flex-col", children: [_jsx(SessionSiteHeader, { sessionId: sessionId, sessionTitle: "" }), _jsxs(SessionBodyRow, { transient: true, children: [_jsx("div", { className: "relative z-10 min-h-0 flex-1 overflow-hidden", children: status ?? (_jsx("div", { role: "status", "aria-busy": "true", "aria-label": t('loadingConversation'), "data-testid": "saved-session-skeleton", className: cn(SESSION_TRANSCRIPT_CLASS, APPEAR_AFTER_A_MOMENT), children: _jsx(SavedSessionSkeletonRows, { shape: shape }) })) }), _jsx("div", { "aria-hidden": true, className: cn(COMPOSER_SHELL_CLASS, 'pb-8', APPEAR_AFTER_A_MOMENT), children: _jsx(SkeletonBar, { phase: shape.composerPhase, phases: shape.phases, className: "h-28 w-full rounded-xl" }) })] })] }) }));
43
44
  }
@@ -106,10 +106,9 @@ function useSessionPanelSurfaces({ sessionId, projectId, projectSessionId, bootS
106
106
  setIsSidePanelOpen(!isSidePanelOpen);
107
107
  }, [isSidePanelOpen, setIsSidePanelOpen]);
108
108
  const panelHeader = (_jsx(PanelHeaderSwitcher, { view: effectiveView, onChangeView: (v) => setPanelView(sessionId, v), isSidePanelOpen: isSidePanelOpen, onTogglePanel: handleTogglePanel, auditBadge: auditPendingCount, onToggleMode: togglePanelMode }));
109
- // While booting, the panel is JUST the dead-center "Kortix Computer is
110
- // starting" loader — no header bar (the loader has its own heading, so a panel
111
- // title would be redundant), filling the whole card so it's perfectly
112
- // centered. The runtime-coupled views (Actions/Files/Terminal/Browser) need a
109
+ // While booting, the panel is JUST the wake status — empty unless the wake
110
+ // needs attention, and then dead-center in the card — with no header bar.
111
+ // The runtime-coupled views (Actions/Files/Terminal/Browser) need a
113
112
  // live sandbox, so they only render once booted.
114
113
  //
115
114
  // Easy mode has no header either: it is the three cards and nothing else. No
@@ -120,7 +119,7 @@ function useSessionPanelSurfaces({ sessionId, projectId, projectSessionId, bootS
120
119
  // own close DISCARDS the detail (it is done with), while ⌘I only puts it
121
120
  // away — press it again and the same detail comes back.
122
121
  const effectivePanelHeader = booting || isEasy ? null : panelHeader;
123
- const effectivePanelBody = booting ? (_jsx(SessionStartingLoader, { stage: bootStage ?? 'provisioning', delayMs: 0, projectId: projectId, sessionId: projectSessionId, variant: "stepper" })) : (panelBody);
122
+ const effectivePanelBody = booting ? (_jsx(SessionStartingLoader, { projectId: projectId, sessionId: projectSessionId })) : (panelBody);
124
123
  return { effectivePanelHeader, effectivePanelBody };
125
124
  }
126
125
  function useSessionPanelBody({ sessionId, projectId, projectSessionId, isEasy, effectiveView, }) {
@@ -0,0 +1,6 @@
1
+ /** The Goals row shows only with the `goals` flag on. */
2
+ export declare function useGoalsRowVisible(projectId: string | undefined): boolean;
3
+ /** The Goals row, with the count of goals that wait on a person or need attention. */
4
+ export declare function ProjectGoalsNavItem({ projectId }: {
5
+ projectId: string;
6
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,26 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { uiRoutes } from '../../../lib/routes.js';
4
+ import { ProjectNavRow } from '../../../internal/layout/project-nav-row.js';
5
+ import { CountBadge } from '../../../primitives/count-badge.js';
6
+ import { useTranslations } from '../../../i18n/index.js';
7
+ import { useFeatureFlag, useProjectGoals } from '@kortix/sdk/react';
8
+ import { TargetIcon } from '@phosphor-icons/react';
9
+ import { useUiPathname } from '../../../providers/ui-provider.js';
10
+ import { goalNeedsYou } from '../../../internal/goals/goal-meta.js';
11
+ /** The Goals row shows only with the `goals` flag on. */
12
+ export function useGoalsRowVisible(projectId) {
13
+ return useFeatureFlag(projectId, 'goals').enabled;
14
+ }
15
+ /** The Goals row, with the count of goals that wait on a person or need attention. */
16
+ export function ProjectGoalsNavItem({ projectId }) {
17
+ const t = useTranslations('sidebar');
18
+ const pathname = useUiPathname();
19
+ const visible = useGoalsRowVisible(projectId);
20
+ const goals = useProjectGoals(visible ? projectId : null);
21
+ const needsYou = (goals.data ?? []).filter(goalNeedsYou).length;
22
+ const href = projectId ? uiRoutes().goals(projectId) : null;
23
+ if (!href || !visible)
24
+ return null;
25
+ return (_jsxs(ProjectNavRow, { active: pathname?.startsWith(href) === true, label: t('goals'), href: href, prefetch: true, children: [_jsx("span", { className: "shrink-0", children: _jsx(TargetIcon, {}) }), t('goals'), needsYou > 0 ? (_jsx("span", { className: "ml-auto flex shrink-0 items-center gap-1", children: _jsx(CountBadge, { count: needsYou, tone: "waiting" }) })) : null] }));
26
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The iframe snippet a host page pastes: the ACTUAL session page
3
+ * (`/projects/:projectId/sessions/:sessionId`), not a separate widget or
4
+ * viewer. Pure, so the test can pin the exact HTML without a browser.
5
+ *
6
+ * The snippet is static HTML by construction: the URL is built from ids, and
7
+ * the `title` attribute is a fixed string. Never interpolate a session title
8
+ * into the attribute — an unescaped quote would break out of it on the HOST
9
+ * page, which does not share this app's CSP.
10
+ */
11
+ export declare function sessionEmbedSnippet(projectId: string, sessionId: string, origin: string): string | null;
12
+ /**
13
+ * "Embed" in the Share dialog: one copy button for the iframe snippet above.
14
+ * Framing the session page is allowed by the CSP `frame-ancestors` rule on
15
+ * that route (next.config.ts); the viewer inside the frame is the app's own
16
+ * auth. Same-site embeds require an existing signed-in session; cross-site
17
+ * embeds cannot sign in inside the frame (SameSite=Lax and /auth's CSP).
18
+ *
19
+ * Mounted inside `ModalContent`, so `window` is read at render time of an open
20
+ * dialog, never during SSR.
21
+ */
22
+ export declare function SessionEmbedSection({ projectId, sessionId, }: {
23
+ projectId: string;
24
+ sessionId: string;
25
+ }): import("react").JSX.Element;
@@ -0,0 +1,71 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Button } from '../../../primitives/button.js';
4
+ import { Label } from '../../../primitives/label.js';
5
+ import { errorToast, successToast } from '../../../primitives/toast.js';
6
+ import { useTranslations } from '../../../i18n/index.js';
7
+ import { CheckIcon, CodeIcon } from '@phosphor-icons/react';
8
+ import { useEffect, useRef, useState } from 'react';
9
+ /**
10
+ * The iframe snippet a host page pastes: the ACTUAL session page
11
+ * (`/projects/:projectId/sessions/:sessionId`), not a separate widget or
12
+ * viewer. Pure, so the test can pin the exact HTML without a browser.
13
+ *
14
+ * The snippet is static HTML by construction: the URL is built from ids, and
15
+ * the `title` attribute is a fixed string. Never interpolate a session title
16
+ * into the attribute — an unescaped quote would break out of it on the HOST
17
+ * page, which does not share this app's CSP.
18
+ */
19
+ export function sessionEmbedSnippet(projectId, sessionId, origin) {
20
+ if (!projectId || !sessionId || !origin)
21
+ return null;
22
+ const src = `${origin}/projects/${encodeURIComponent(projectId)}/sessions/${encodeURIComponent(sessionId)}`;
23
+ return `<iframe src="${src}" style="height:640px;width:100%;border:0;border-radius:12px" title="Kortix session" loading="lazy"></iframe>`;
24
+ }
25
+ /**
26
+ * "Embed" in the Share dialog: one copy button for the iframe snippet above.
27
+ * Framing the session page is allowed by the CSP `frame-ancestors` rule on
28
+ * that route (next.config.ts); the viewer inside the frame is the app's own
29
+ * auth. Same-site embeds require an existing signed-in session; cross-site
30
+ * embeds cannot sign in inside the frame (SameSite=Lax and /auth's CSP).
31
+ *
32
+ * Mounted inside `ModalContent`, so `window` is read at render time of an open
33
+ * dialog, never during SSR.
34
+ */
35
+ export function SessionEmbedSection({ projectId, sessionId, }) {
36
+ const t = useTranslations('hardcodedUi.sessionEmbed');
37
+ const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
38
+ const [copied, setCopied] = useState(false);
39
+ const copiedTimer = useRef(null);
40
+ const mounted = useRef(false);
41
+ useEffect(() => {
42
+ mounted.current = true;
43
+ return () => {
44
+ mounted.current = false;
45
+ if (copiedTimer.current)
46
+ clearTimeout(copiedTimer.current);
47
+ };
48
+ }, []);
49
+ const origin = typeof window === 'undefined' ? null : window.location.origin;
50
+ const snippet = origin ? sessionEmbedSnippet(projectId, sessionId, origin) : null;
51
+ const copy = async () => {
52
+ if (!snippet)
53
+ return;
54
+ try {
55
+ await navigator.clipboard.writeText(snippet);
56
+ if (!mounted.current)
57
+ return;
58
+ setCopied(true);
59
+ successToast(t('copied'));
60
+ if (copiedTimer.current)
61
+ clearTimeout(copiedTimer.current);
62
+ copiedTimer.current = setTimeout(() => setCopied(false), 2000);
63
+ }
64
+ catch {
65
+ if (!mounted.current)
66
+ return;
67
+ errorToast(tI18nComplete.raw('text167c96824acb'));
68
+ }
69
+ };
70
+ return (_jsxs("section", { className: "space-y-2", "data-testid": "session-embed", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: t('sectionTitle') }), _jsx("p", { className: "text-muted-foreground text-xs", children: t('sectionDescription') })] }), _jsxs(Button, { variant: "outline", size: "sm", disabled: !snippet, onClick: copy, children: [copied ? (_jsx(CheckIcon, { className: "text-kortix-green size-4 shrink-0" })) : (_jsx(CodeIcon, { className: "size-4 shrink-0" })), t('copy')] })] }));
71
+ }
@@ -13,7 +13,7 @@ import { sessionOversightQueryKey } from '../../../access/iam/session-oversight-
13
13
  import { ShieldCheckIcon } from '@phosphor-icons/react';
14
14
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
15
15
  import { useEffect, useState } from 'react';
16
- import { SessionPublicLinkSection } from './session-public-link-section.js';
16
+ import { SessionEmbedSection } from './session-embed-section.js';
17
17
  import { sessionAccessSummary, sessionAccessView } from './share-session-access.js';
18
18
  import { refreshAfterShare } from './share-session-cache.js';
19
19
  /**
@@ -105,5 +105,5 @@ export function ShareSessionModal({ projectId, session, open, onOpenChange, onSa
105
105
  // Read-only, not hidden: a person a session was shared with should
106
106
  // still be able to see who else is in it. Withholding that is what
107
107
  // sent people to the editable dialog in the first place.
108
- _jsx("p", { className: "text-foreground text-sm", "data-testid": "session-access-summary", children: session ? sessionAccessSummary(session) : null })), oversightOn ? (_jsx(InfoBanner, { tone: "neutral", icon: ShieldCheckIcon, "data-testid": "session-oversight-disclosure", children: tOversight.raw('shareDisclosure') })) : null, view.canEdit && session && publicSharesEnabled ? (_jsx(SessionPublicLinkSection, { projectId: projectId, sessionId: session.session_id })) : null] }), _jsxs(ModalFooter, { className: "sm:justify-between", children: [_jsx(Button, { variant: "outline-ghost", size: "sm", className: "w-full sm:w-auto", onClick: () => onOpenChange(false), disabled: save.isPending, children: view.canEdit ? 'Cancel' : 'Close' }), view.canEdit && (_jsxs(Button, { size: "sm", onClick: () => save.mutate(), disabled: save.isPending || !isSharingComplete(sharing) || blockedSelection, className: "w-full sm:w-auto", children: [save.isPending && _jsx(Loading, {}), tI18nHardcoded.raw('i18nComplete.text1509f561f241')] }))] })] }) }));
108
+ _jsx("p", { className: "text-foreground text-sm", "data-testid": "session-access-summary", children: session ? sessionAccessSummary(session) : null })), oversightOn ? (_jsx(InfoBanner, { tone: "neutral", icon: ShieldCheckIcon, "data-testid": "session-oversight-disclosure", children: tOversight.raw('shareDisclosure') })) : null, view.canEdit && session && publicSharesEnabled ? (_jsx(SessionEmbedSection, { projectId: projectId, sessionId: session.session_id })) : null] }), _jsxs(ModalFooter, { className: "sm:justify-between", children: [_jsx(Button, { variant: "outline-ghost", size: "sm", className: "w-full sm:w-auto", onClick: () => onOpenChange(false), disabled: save.isPending, children: view.canEdit ? 'Cancel' : 'Close' }), view.canEdit && (_jsxs(Button, { size: "sm", onClick: () => save.mutate(), disabled: save.isPending || !isSharingComplete(sharing) || blockedSelection, className: "w-full sm:w-auto", children: [save.isPending && _jsx(Loading, {}), tI18nHardcoded.raw('i18nComplete.text1509f561f241')] }))] })] }) }));
109
109
  }
@@ -3,11 +3,12 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { uiRoutes } from '../../lib/routes.js';
4
4
  import { capabilityTabHref } from '../../internal/lib/capability-tab-routes.js';
5
5
  import { useAppsRowVisible } from './footer/project-apps-nav.js';
6
+ import { useGoalsRowVisible } from './footer/project-goals-nav.js';
6
7
  import { useRoomsRowVisible } from './footer/project-rooms-nav.js';
7
8
  import { filesRow } from '../../internal/layout/files-nav-label.js';
8
9
  import { CustomizeGlyph, useProjectNavRows, } from '../../internal/layout/project-settings-nav.js';
9
10
  import { useTranslations } from '../../i18n/index.js';
10
- import { AppWindowIcon, ChatsTeardropIcon, FoldersIcon, HardDrivesIcon } from '@phosphor-icons/react';
11
+ import { AppWindowIcon, ChatsTeardropIcon, FoldersIcon, HardDrivesIcon, TargetIcon } from '@phosphor-icons/react';
11
12
  import { useMemo } from 'react';
12
13
  /**
13
14
  * Label, glyph, destination and availability of every customizable nav row,
@@ -21,12 +22,14 @@ export function useNavRowMeta(projectId) {
21
22
  const { customize: customizeTab, files: filesTarget } = useProjectNavRows(projectId);
22
23
  const appsVisible = useAppsRowVisible(projectId);
23
24
  const roomsVisible = useRoomsRowVisible(projectId);
25
+ const goalsVisible = useGoalsRowVisible(projectId);
24
26
  // Memoized on the gates, not rebuilt per render: the Customize sidebar
25
27
  // modal memoizes its rows on this object, and a fresh one per render would
26
28
  // re-render every row on every drag frame.
27
29
  return useMemo(() => {
28
30
  const appsHref = uiRoutes().apps(projectId);
29
31
  const roomsHref = uiRoutes().rooms(projectId);
32
+ const goalsHref = uiRoutes().goals(projectId);
30
33
  const files = filesTarget ? filesRow(projectId, filesTarget) : null;
31
34
  return {
32
35
  ...(customizeTab
@@ -56,6 +59,15 @@ export function useNavRowMeta(projectId) {
56
59
  },
57
60
  }
58
61
  : {}),
62
+ ...(goalsVisible && goalsHref
63
+ ? {
64
+ goals: {
65
+ label: t('goals'),
66
+ icon: _jsx(TargetIcon, {}),
67
+ href: goalsHref,
68
+ },
69
+ }
70
+ : {}),
59
71
  ...(files
60
72
  ? {
61
73
  files: {
@@ -66,5 +78,5 @@ export function useNavRowMeta(projectId) {
66
78
  }
67
79
  : {}),
68
80
  };
69
- }, [t, tI18nComplete, projectId, customizeTab, appsVisible, roomsVisible, filesTarget]);
81
+ }, [t, tI18nComplete, projectId, customizeTab, appsVisible, roomsVisible, goalsVisible, filesTarget]);
70
82
  }
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { ProjectAppsNavItem } from './footer/project-apps-nav.js';
4
+ import { ProjectGoalsNavItem } from './footer/project-goals-nav.js';
4
5
  import { ProjectRoomsNavItem } from './footer/project-rooms-nav.js';
5
6
  import { ProjectFilesNavItem } from './footer/project-files-nav.js';
6
7
  import { ProjectCustomizeNavItem } from '../../internal/layout/project-settings-nav.js';
@@ -95,5 +96,7 @@ function NavItem({ entry, projectId }) {
95
96
  return _jsx(ProjectFilesNavItem, { projectId: projectId });
96
97
  case 'rooms':
97
98
  return _jsx(ProjectRoomsNavItem, { projectId: projectId });
99
+ case 'goals':
100
+ return _jsx(ProjectGoalsNavItem, { projectId: projectId });
98
101
  }
99
102
  }
@@ -39,8 +39,8 @@ import { selectExpandedIds, useSessionExpandedStore } from '../../internal/store
39
39
  import { selectCollapsedSections, selectGroupMode, selectHiddenSections, selectLabelFilters, selectOrderMode, selectSourceFilters, selectStatusFilters, useSessionFilterStore, } from '../../internal/stores/session-filter-store.js';
40
40
  import { shouldBeginSessionSwitch, useSessionSwitchStore } from '../../internal/stores/session-switch-store.js';
41
41
  import { directSubsessions, createProjectSession, listChangeRequests, restartProjectSession, sessionParentId, stopProjectSession, } from '@kortix/sdk';
42
- import { qk, useProjectSession, useProjectSessions, useRuntimeSupports, useSessionChildren, } from '@kortix/sdk/react';
43
- import { ArchiveIcon, ArrowsSplitIcon, CaretRightIcon, DotsThreeIcon, PushPinIcon, PushPinSlashIcon, RobotIcon, GitForkIcon, PencilSimpleIcon, ArrowCounterClockwiseIcon as RotateCcw, ShareIcon as Share, SparkleIcon, SquareIcon as Square, TagIcon, TrashIcon, TreeViewIcon, } from '@phosphor-icons/react';
42
+ import { qk, useProjectSession, useProjectSessions, useRuntimeSupports, useSessionChildren, useFeatureFlag, useSessionGoal, } from '@kortix/sdk/react';
43
+ import { ArchiveIcon, ArrowsSplitIcon, CaretRightIcon, DotsThreeIcon, PushPinIcon, PushPinSlashIcon, RobotIcon, GitForkIcon, PencilSimpleIcon, ArrowCounterClockwiseIcon as RotateCcw, ShareIcon as Share, SparkleIcon, TargetIcon, SquareIcon as Square, TagIcon, TrashIcon, TreeViewIcon, } from '@phosphor-icons/react';
44
44
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
45
45
  import { formatDistanceToNowStrict } from 'date-fns';
46
46
  import { useUiPathname, useUiRouter, useUiSearchParams, useUiViewerId } from '../../providers/ui-provider.js';
@@ -549,7 +549,7 @@ function ProjectSessionRow({ session, href, isActive, isSwitching, onNavigate, d
549
549
  // same `reviewCount` prop, so they agree by construction rather than by luck.
550
550
  const displayStatus = sessionDisplayStatus(session, reviewCount);
551
551
  const waitingOn = awaitingAnswer ? 'answer' : 'review';
552
- const sessionLink = (_jsxs(HoverPrefetchLink, { href: href, prefetch: true, onClick: onNavigate, ...openIntent, "aria-busy": isSwitching || undefined, "aria-current": isActive ? 'page' : undefined, "aria-describedby": descriptionId, className: "focus-visible:ring-kortix-base flex min-w-0 flex-1 items-center gap-2 self-stretch rounded-md py-1 focus-visible:ring-[0.6px] focus-visible:outline-none", children: [_jsx("div", { className: "size-4 shrink-0", children: spawned ? (_jsx(ArrowsSplitIcon, { "aria-hidden": true, className: "text-muted-foreground size-4" })) : (_jsx(SessionStatusDot, { session: session, reviewCount: reviewCount, waitingOn: waitingOn, changeRequests: changeRequests })) }), isMeta && (_jsx(Hint, { side: "top", label: t('metaCoordinator'), children: _jsx("span", { className: "text-muted-foreground flex size-4 shrink-0 items-center justify-center", children: _jsx(SparkleIcon, { className: "size-3.5", weight: "fill" }) }) })), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx(SessionTitle, { title: displayTitle, className: cn('min-w-0', isActive && 'font-medium') }), _jsx("span", { className: "hidden max-md:block pointer-coarse:block [@media(hover:none)]:block", "aria-hidden": true, "data-session-mobile-created-at": "true", children: _jsx(MobileSessionCreatedTime, { createdAt: session.created_at }) })] }), subagentCount + spawnedCount > 0 && onToggleChildRuns && (_jsx(ChildRunsToggle, { count: subagentCount + spawnedCount, open: childRunsOpen, onToggle: onToggleChildRuns, clearOfMenu: !hasIndicators })), hasIndicators && (_jsxs("div", { className: cn('flex shrink-0 items-center gap-0 transition-none', 'max-md:hidden pointer-coarse:hidden [@media(hover:none)]:hidden', 'group-hover/session-list:pointer-events-none group-hover/session-list:opacity-0', 'group-has-data-[state=open]/session-list:pointer-events-none group-has-data-[state=open]/session-list:opacity-0'), "data-session-indicators": "true", children: [showStarter && (_jsx("span", { className: "flex size-5 shrink-0 items-center justify-center", "data-session-source": "true", "data-session-starter": starter.type, children: _jsx(Hint, { side: "top", label: t('startedByLabel', { name: starter.label }), children: _jsx("span", { className: "text-muted-foreground flex size-5 items-center justify-center", children: _jsx(SessionStarterMark, { session: session, starter: starter, iconClassName: "size-3", avatarClassName: "size-5" }) }) }) })), !inSharedGroup && _jsx(SessionSharedIcon, { session: session })] }))] }));
552
+ const sessionLink = (_jsxs(HoverPrefetchLink, { href: href, prefetch: true, onClick: onNavigate, ...openIntent, "aria-busy": isSwitching || undefined, "aria-current": isActive ? 'page' : undefined, "aria-describedby": descriptionId, className: "focus-visible:ring-kortix-base flex min-w-0 flex-1 items-center gap-2 self-stretch rounded-md py-1 focus-visible:ring-[0.6px] focus-visible:outline-none", children: [_jsx("div", { className: "size-4 shrink-0", children: spawned ? (_jsx(ArrowsSplitIcon, { "aria-hidden": true, className: "text-muted-foreground size-4" })) : (_jsx(SessionStatusDot, { session: session, reviewCount: reviewCount, waitingOn: waitingOn, changeRequests: changeRequests })) }), _jsx(SessionGoalMark, { projectId: session.project_id, sessionId: session.session_id }), isMeta && (_jsx(Hint, { side: "top", label: t('metaCoordinator'), children: _jsx("span", { className: "text-muted-foreground flex size-4 shrink-0 items-center justify-center", children: _jsx(SparkleIcon, { className: "size-3.5", weight: "fill" }) }) })), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx(SessionTitle, { title: displayTitle, className: cn('min-w-0', isActive && 'font-medium') }), _jsx("span", { className: "hidden max-md:block pointer-coarse:block [@media(hover:none)]:block", "aria-hidden": true, "data-session-mobile-created-at": "true", children: _jsx(MobileSessionCreatedTime, { createdAt: session.created_at }) })] }), subagentCount + spawnedCount > 0 && onToggleChildRuns && (_jsx(ChildRunsToggle, { count: subagentCount + spawnedCount, open: childRunsOpen, onToggle: onToggleChildRuns, clearOfMenu: !hasIndicators })), hasIndicators && (_jsxs("div", { className: cn('flex shrink-0 items-center gap-0 transition-none', 'max-md:hidden pointer-coarse:hidden [@media(hover:none)]:hidden', 'group-hover/session-list:pointer-events-none group-hover/session-list:opacity-0', 'group-has-data-[state=open]/session-list:pointer-events-none group-has-data-[state=open]/session-list:opacity-0'), "data-session-indicators": "true", children: [showStarter && (_jsx("span", { className: "flex size-5 shrink-0 items-center justify-center", "data-session-source": "true", "data-session-starter": starter.type, children: _jsx(Hint, { side: "top", label: t('startedByLabel', { name: starter.label }), children: _jsx("span", { className: "text-muted-foreground flex size-5 items-center justify-center", children: _jsx(SessionStarterMark, { session: session, starter: starter, iconClassName: "size-3", avatarClassName: "size-5" }) }) }) })), !inSharedGroup && _jsx(SessionSharedIcon, { session: session })] }))] }));
553
553
  return (_jsx("div", { className: "group/session-list block", children: _jsxs("div", { className: cn(
554
554
  // --session-row-surface paints the row AND the title fade in the same
555
555
  // style pass. Do not transition background — transition-colors made the
@@ -602,6 +602,15 @@ function ProjectSubsessionRow({ id, title, href, isActive, createdAt, updatedAt,
602
602
  : '';
603
603
  return (_jsx(SubagentHoverCard, { id: id, title: title, createdAt: createdAt, updatedAt: updatedAt, children: _jsx(HoverPrefetchLink, { href: href, className: "block", children: _jsxs("div", { className: cn('flex h-8 cursor-pointer items-center gap-2 rounded-lg px-2 text-sm', isActive ? 'bg-sidebar-accent text-sidebar-foreground font-medium' : ''), children: [_jsx(RobotIcon, { "aria-hidden": true, className: "text-muted-foreground size-4 shrink-0" }), _jsx("span", { className: cn('flex-1 truncate', isActive && 'font-medium'), children: title }), relative && _jsx("span", { className: SESSION_RELATIVE_TIME_CLASS, children: relative })] }) }) }));
604
604
  }
605
+ /** A target beside a session that runs a goal (flag `goals`); the hint names the goal. */
606
+ function SessionGoalMark({ projectId, sessionId }) {
607
+ const tGoals = useTranslations('goals');
608
+ const flag = useFeatureFlag(projectId, 'goals');
609
+ const { goal, role } = useSessionGoal(flag.enabled ? projectId : null, sessionId);
610
+ if (!goal || role !== 'coordinator')
611
+ return null;
612
+ return (_jsx(Hint, { side: "top", label: `${tGoals('sessionChip')}: ${goal.goal?.objective ?? goal.slug}`, children: _jsx("span", { className: "text-muted-foreground flex size-4 shrink-0 items-center justify-center", "data-testid": "session-goal-mark", children: _jsx(TargetIcon, { className: "size-3.5" }) }) }));
613
+ }
605
614
  function SessionStatusDot({ session, reviewCount = 0, waitingOn, changeRequests, }) {
606
615
  const display = sessionDisplayStatus(session, reviewCount);
607
616
  return (_jsx("div", { className: "flex size-4 shrink-0 items-center justify-center", children: _jsx(SessionStatusMark, { status: display, waitingOn: waitingOn, overdue: isReviewOverdue(changeRequests) }) }));
@@ -11,7 +11,7 @@ import { menuRow } from '../../primitives/menu-recipe.js';
11
11
  import { CR_ID_PREFIX } from '../review/review-actions.js';
12
12
  import { reviewHref } from '../../internal/lib/capability-tab-routes.js';
13
13
  import { useTranslations } from '../../i18n/index.js';
14
- import { useSessionHoverStore } from '../../internal/stores/session-hover-store.js';
14
+ import { sessionRowHoverHandlers, useSessionHoverStore } from '../../internal/stores/session-hover-store.js';
15
15
  import { ArrowsSplitIcon, GitDiffIcon, RobotIcon } from '@phosphor-icons/react';
16
16
  import { formatDistanceToNowStrict } from 'date-fns';
17
17
  import { Slot } from 'radix-ui';
@@ -150,6 +150,33 @@ export function SessionBriefCardHost() {
150
150
  el.style.transform = `translate(${rect.right}px, ${top}px)`;
151
151
  shown.current = true;
152
152
  }, [visible, activeSessionId, anchor, via]);
153
+ // Native listeners, not React's `onPointerEnter/Leave`. The card's content is
154
+ // portaled in from the row, so in React's tree it is the row's child, not
155
+ // this wrapper's: React fired the wrapper's leave the moment the pointer
156
+ // crossed from the gap onto the content, and the card closed under a pointer
157
+ // on its way to the Review link. The DOM tree is the one the pointer is in.
158
+ useEffect(() => {
159
+ const el = wrapperRef.current;
160
+ if (!el)
161
+ return;
162
+ const active = () => useSessionHoverStore.getState().activeSessionId;
163
+ const onEnter = () => {
164
+ const id = active();
165
+ if (id)
166
+ openSession(id);
167
+ };
168
+ const onLeave = () => {
169
+ const id = active();
170
+ if (id)
171
+ closeSession(id);
172
+ };
173
+ el.addEventListener('pointerenter', onEnter);
174
+ el.addEventListener('pointerleave', onLeave);
175
+ return () => {
176
+ el.removeEventListener('pointerenter', onEnter);
177
+ el.removeEventListener('pointerleave', onLeave);
178
+ };
179
+ }, [openSession, closeSession]);
153
180
  // WCAG 1.4.13: dismissible without moving the pointer. A scroll moves the
154
181
  // row away from the card, so it closes too, unless it scrolls the card itself.
155
182
  useEffect(() => {
@@ -169,7 +196,7 @@ export function SessionBriefCardHost() {
169
196
  window.removeEventListener('resize', dismiss);
170
197
  };
171
198
  }, [visible, dismiss]);
172
- return createPortal(_jsx("div", { ref: wrapperRef, "data-slot": "session-brief-card", className: cn('fixed top-0 left-0 pl-3.5 starting:opacity-0', !visible && 'hidden'), style: { zIndex: floatingZ(depth) }, onPointerEnter: () => activeSessionId && openSession(activeSessionId), onPointerLeave: () => activeSessionId && closeSession(activeSessionId), children: _jsx("div", { ref: setSlot, className: cn(FLOATING_PANEL_SURFACE, 'w-72 overflow-hidden rounded-md shadow-xs') }) }), container ?? document.body);
199
+ return createPortal(_jsx("div", { ref: wrapperRef, "data-slot": "session-brief-card", className: cn('fixed top-0 left-0 pl-3.5 starting:opacity-0', !visible && 'hidden'), style: { zIndex: floatingZ(depth) }, children: _jsx("div", { ref: setSlot, className: cn(FLOATING_PANEL_SURFACE, 'w-72 overflow-hidden rounded-md shadow-xs') }) }), container ?? document.body);
173
200
  }
174
201
  /**
175
202
  * A row's trigger plus its brief, rendered into the shared card while the row
@@ -180,20 +207,17 @@ export function SessionBriefCardHost() {
180
207
  function SessionBriefCard({ id, children, content, }) {
181
208
  const open = useSessionHoverStore((state) => state.activeSessionId === id);
182
209
  const slot = useSessionHoverStore((state) => state.slot);
183
- const openSession = useSessionHoverStore((state) => state.openSession);
184
- const closeSession = useSessionHoverStore((state) => state.closeSession);
185
210
  // A row can be filtered out, or the sidebar unmounted, while its card shows.
186
211
  // Without this the store keeps naming a row that no longer exists and the
187
- // group is still warm the next time the list mounts.
212
+ // group is still warm the next time the list mounts. A row unmounted under
213
+ // the pointer gets no pointerleave, so its click's suppression ends here too.
188
214
  useEffect(() => () => {
189
215
  const store = useSessionHoverStore.getState();
190
216
  if (store.activeSessionId === id)
191
217
  store.dismiss();
218
+ store.release(id);
192
219
  }, [id]);
193
- return (_jsxs(_Fragment, { children: [_jsx(Slot.Root, { "data-state": open ? 'open' : 'closed', onPointerEnter: (event) => {
194
- if (event.pointerType !== 'touch')
195
- openSession(id, event.currentTarget, 'pointer');
196
- }, onPointerLeave: () => closeSession(id), onFocus: (event) => openSession(id, event.currentTarget, 'focus'), onBlur: () => closeSession(id), children: children }), open && slot ? createPortal(content, slot) : null] }));
220
+ return (_jsxs(_Fragment, { children: [_jsx(Slot.Root, { "data-state": open ? 'open' : 'closed', ...sessionRowHoverHandlers(id), children: children }), open && slot ? createPortal(content, slot) : null] }));
197
221
  }
198
222
  export function SessionBriefHoverCard({ sessionId, children, ...brief }) {
199
223
  const dismiss = useSessionHoverStore((state) => state.dismiss);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kortix/ui",
3
- "version": "0.14.6",
3
+ "version": "0.14.8",
4
4
  "description": "Kortix UI: the shared React component library for apps/web, Kortix Apps and SDK hosts. Tailwind v4 classes, themed by CSS variables.",
5
5
  "type": "module",
6
6
  "license": "Elastic-2.0",
@@ -911,6 +911,10 @@
911
911
  "types": "./dist/internal/capture/use-capture-area.d.ts",
912
912
  "import": "./dist/internal/capture/use-capture-area.js"
913
913
  },
914
+ "./internal/components/agent-mark": {
915
+ "types": "./dist/internal/components/agent-mark.d.ts",
916
+ "import": "./dist/internal/components/agent-mark.js"
917
+ },
914
918
  "./internal/components/hover-prefetch-link": {
915
919
  "types": "./dist/internal/components/hover-prefetch-link.d.ts",
916
920
  "import": "./dist/internal/components/hover-prefetch-link.js"
@@ -1463,6 +1467,10 @@
1463
1467
  "types": "./dist/markdown/setup-links/api-base.d.ts",
1464
1468
  "import": "./dist/markdown/setup-links/api-base.js"
1465
1469
  },
1470
+ "./markdown/setup-links/app-credentials-form": {
1471
+ "types": "./dist/markdown/setup-links/app-credentials-form.d.ts",
1472
+ "import": "./dist/markdown/setup-links/app-credentials-form.js"
1473
+ },
1466
1474
  "./markdown/setup-links/approval-decision": {
1467
1475
  "types": "./dist/markdown/setup-links/approval-decision.d.ts",
1468
1476
  "import": "./dist/markdown/setup-links/approval-decision.js"
@@ -2157,7 +2165,7 @@
2157
2165
  "@extend-ai/react-docx": "^0.7.5",
2158
2166
  "@extend-ai/react-xlsx": "0.13.4",
2159
2167
  "@glideapps/glide-data-grid": "6.0.4-alpha24",
2160
- "@kortix/llm-catalog": "0.14.6",
2168
+ "@kortix/llm-catalog": "0.14.8",
2161
2169
  "@lezer/highlight": "1.2.3",
2162
2170
  "@paper-design/shaders-react": "^0.0.76",
2163
2171
  "@phosphor-icons/react": "2.1.10",
@@ -2214,7 +2222,7 @@
2214
2222
  "heic2any": "0.0.4",
2215
2223
  "i18next": "^26.3.6",
2216
2224
  "jszip": "^3.10.1",
2217
- "katex": "^0.16.22",
2225
+ "katex": "^0.18.7",
2218
2226
  "maplibre-gl": "6.12.0",
2219
2227
  "mermaid": "^11.12.0",
2220
2228
  "motion": "^12.40.0",
@@ -2246,7 +2254,7 @@
2246
2254
  "zustand": "^5.0.3"
2247
2255
  },
2248
2256
  "peerDependencies": {
2249
- "@kortix/sdk": "^0.14.6",
2257
+ "@kortix/sdk": "^0.14.8",
2250
2258
  "@tanstack/react-query": "^5.75.2",
2251
2259
  "@types/react": ">=18",
2252
2260
  "react": ">=19",
@@ -373,6 +373,8 @@ export function Sessions({ projectId }: { projectId: string }) {
373
373
  }
374
374
  ```
375
375
 
376
+ Goals (behind the `goals` flag): `GoalStatusBadge` (a goal's status), `GoalHealthDot` (green running, orange needs attention, grey closed), `useGoalIssueText` (one sentence per health issue), `goalProgress` (met and total criteria), `goalOpenAsks` (asks waiting on a person), `goalNeedsYou` (an open ask or a health issue), `formatGoalTime` (the reminders time format), `goalHref` (the goal's page, or null), `useGoalClock` (the current time, re-read every 30 s).
377
+
376
378
  Names: `ProjectPageHeader`, `useCapabilityAccess`, `useProjectNavRows`, `SidebarToggle`, `activeCapabilityTab`, `agentHref`, `capabilityTabHref`, `channelsHref`, `remindersHref`, `reviewHref`, `detectManifestVersion`, `manifestFilename`, `manifestNeedsUpgrade`, `useProjectManifestVersion`, `ManifestVersion`, `CUSTOMIZE_SECTION_ACCESS`, `CUSTOMIZE_SECTION_GATE_ACTIONS`, `PROJECT_ACTIONS`, `isCustomizeSectionVisible`, `CustomizeSection`, `ProjectAction`, `usePermission`, `usePermissions`, `usePermissionsFor`, `PROJECT_PAGE_ACTIONS`, `useProjectCan`, `useProjectCans`, `useProjectPageCans`, `CanResult`, `formatFireTime`, `nearestFire`, `reminderTitle`, `scheduleLabel`, `ProjectHomeWallpaper`, `ProjectHomeWelcomeBody`, `InstantSessionShell`, `ProjectFilesView`, `ProjectHome`, `ProjectHomeSendOptions`, `useProjectHomeSend`, `UseProjectHomeSendOptions`, `SavedSessionSkeleton`, `SessionLayout`, `SessionWorkspace`, `SessionLayoutProps`, `SessionWorkspaceProps`, `ProjectSessionsView`, `ProjectSessionList`, `ProjectSidebar`, `ShareSessionModal`
377
379
 
378
380
  ## `@kortix/ui/access`
@@ -1,18 +0,0 @@
1
- /**
2
- * How long a freshly mounted session may say NOTHING about its runtime.
3
- *
4
- * Long enough to cover the first health probe + `GET .../turn` round trip on a
5
- * healthy session (both answer well inside a second locally and on dev), short
6
- * enough that a genuinely parked box still announces its wake promptly. The
7
- * cost of being wrong in one direction is a false "your session dropped" flash
8
- * on every page load; in the other, a real wake notice arrives ~1.5s later.
9
- */
10
- export declare const READINESS_SETTLE_MS = 1500;
11
- /**
12
- * True while the runtime has not been contacted yet AND the mount is young.
13
- *
14
- * Feeds `sessionComposerReadiness({ settling })`, whose fallback would
15
- * otherwise assert "Waking this session up…" the instant a page loads — the
16
- * one claim nothing had checked. See that input's docstring.
17
- */
18
- export declare function useReadinessSettling(uncontacted: boolean, windowMs?: number): boolean;