@kortix/ui 0.14.4 → 0.14.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/dist/access/access/access-dialog.js +8 -5
- package/dist/access/access/copy-row.js +2 -2
- package/dist/access/sharing-picker.js +2 -1
- package/dist/browser/app-in-browser.js +5 -3
- package/dist/browser/browser-panel.js +6 -6
- package/dist/browser/sandbox-browser-chrome.d.ts +4 -1
- package/dist/browser/sandbox-browser-chrome.js +4 -3
- package/dist/customize/agents/agent-access.d.ts +20 -0
- package/dist/customize/agents/agent-access.js +17 -0
- package/dist/customize/agents/agent-grant-pages.js +5 -6
- package/dist/customize/agents/agent-page.js +2 -1
- package/dist/customize/agents/agents-page.js +18 -1
- package/dist/customize/connectors/add-mcp/add-mcp-server.js +4 -2
- package/dist/customize/connectors/connector-routes.d.ts +3 -1
- package/dist/customize/connectors/connector-routes.js +18 -0
- package/dist/customize/connectors/connectors-page.js +23 -4
- package/dist/customize/connectors/detail/connector-advanced-tools.js +4 -6
- package/dist/customize/providers/provider-accordion.js +2 -1
- package/dist/customize/providers/provider-connect.js +2 -1
- package/dist/customize/sections/component/telegram-channel-row.js +1 -1
- package/dist/customize/sections/connector-connection-modal.js +11 -9
- package/dist/customize/sections/connectors-view.js +11 -13
- package/dist/customize/sections/llm-provider/constants.d.ts +0 -1
- package/dist/customize/sections/llm-provider/constants.js +0 -2
- package/dist/customize/sections/llm-provider/models-tab-group.d.ts +1 -1
- package/dist/customize/sections/llm-provider/models-tab-group.js +7 -4
- package/dist/customize/sections/llm-provider/models-tab-row.d.ts +2 -18
- package/dist/customize/sections/llm-provider/models-tab-row.js +6 -30
- package/dist/customize/sections/llm-provider/models-tab.d.ts +0 -1
- package/dist/customize/sections/llm-provider/models-tab.js +1 -10
- package/dist/customize/sections/llm-provider/use-connected-providers.js +2 -2
- package/dist/customize/sections/llm-provider/utils.d.ts +1 -1
- package/dist/customize/sections/llm-provider/utils.js +7 -14
- package/dist/customize/sections/view/gateway/_shared.js +1 -2
- package/dist/customize/sections/view/gateway/gateway-routing.js +18 -20
- package/dist/customize/sections/view/sandbox-provider-coverage.d.ts +3 -18
- package/dist/customize/sections/view/sandbox-provider-coverage.js +7 -27
- package/dist/customize/settings/rail-copy-context.js +5 -3
- package/dist/customize/settings/tabs/sandbox-tab.d.ts +2 -27
- package/dist/customize/settings/tabs/sandbox-tab.js +13 -112
- package/dist/customize/settings/tabs/sandbox-template-form.js +9 -12
- package/dist/customize/shared/capability-tabs.js +2 -1
- package/dist/customize/shared/entity/entity-modal.d.ts +5 -1
- package/dist/customize/shared/entity/entity-modal.js +7 -4
- package/dist/customize/skills/remove-skill-button.d.ts +16 -0
- package/dist/customize/skills/remove-skill-button.js +42 -0
- package/dist/customize/skills/skills-page.js +4 -6
- package/dist/customize/triggers/schedule/event-trigger-copy.js +3 -2
- package/dist/customize/triggers/schedule/schedule-builder.js +7 -4
- package/dist/customize/triggers/schedule/trigger-composer.js +5 -2
- package/dist/customize/triggers/schedule/trigger-list.d.ts +0 -2
- package/dist/customize/triggers/schedule/trigger-list.js +1 -18
- package/dist/customize/triggers/schedule/trigger-tile.d.ts +1 -32
- package/dist/customize/triggers/schedule/trigger-tile.js +1 -16
- package/dist/customize/triggers/schedule/when-event.js +1 -1
- package/dist/diff/diff-view.js +9 -10
- package/dist/files/editor/code-editor.js +2 -3
- package/dist/files/project/components/change-request-detail-dialog.d.ts +3 -1
- package/dist/files/project/components/change-request-detail-dialog.js +3 -5
- package/dist/files/project/components/change-request-status-badge.d.ts +12 -0
- package/dist/files/project/components/change-request-status-badge.js +27 -0
- package/dist/files/project/components/change-requests-panel.js +1 -1
- package/dist/files/project/components/drive-explorer.js +2 -1
- package/dist/files/project/components/drive-grid-view.js +11 -7
- package/dist/files/project/components/drive-list-view.js +4 -4
- package/dist/files/project/components/file-breadcrumbs.js +1 -1
- package/dist/files/project/components/file-explorer-page.js +1 -1
- package/dist/files/project/components/file-history-popover.js +1 -6
- package/dist/files/project/components/file-search.js +6 -1
- package/dist/files/project/components/open-change-request-dialog.js +2 -1
- package/dist/files/project/components/version-selector-logic.d.ts +11 -0
- package/dist/files/project/components/version-selector-logic.js +26 -0
- package/dist/files/project/components/version-selector.js +11 -20
- package/dist/files/project/hooks/use-change-requests.d.ts +14 -1
- package/dist/files/project/hooks/use-change-requests.js +44 -25
- package/dist/files/project/hooks/use-server-health.js +17 -15
- package/dist/files/renderers/docx/docx-viewer.js +4 -6
- package/dist/files/renderers/image-renderer.js +6 -24
- package/dist/files/renderers/pdf/pdf-renderer.d.ts +3 -1
- package/dist/files/renderers/pdf/pdf-renderer.js +2 -2
- package/dist/files/renderers/pdf/pdf-viewer.d.ts +3 -0
- package/dist/files/renderers/pdf/pdf-viewer.js +16 -7
- package/dist/files/renderers/presentation/FullScreenPresentationViewer.js +2 -11
- package/dist/files/renderers/show-content-renderer.js +1 -1
- package/dist/files/renderers/sqlite-renderer.js +8 -3
- package/dist/files/renderers/xlsx/xlsx-viewer.js +15 -16
- package/dist/files/tree/components/file-tree-item.js +1 -1
- package/dist/files/viewer/file-content-renderer.d.ts +4 -1
- package/dist/files/viewer/file-content-renderer.js +2 -2
- package/dist/files/viewer/file-preview-modal.js +1 -1
- package/dist/files/viewer/use-file-content-state.js +1 -1
- package/dist/i18n/en.d.ts +36 -35
- package/dist/i18n/en.js +45 -44
- package/dist/index.d.ts +1 -1
- package/dist/internal/desktop/desktop-back-button.js +5 -3
- package/dist/internal/hooks/use-auto-scroll.js +5 -3
- package/dist/internal/hooks/use-notification-center.js +21 -15
- package/dist/internal/hooks/use-review-session-summary.d.ts +6 -0
- package/dist/internal/hooks/use-review-session-summary.js +11 -0
- package/dist/internal/lib/browser-error-noise.d.ts +1 -1
- package/dist/internal/lib/browser-error-noise.js +1 -1
- package/dist/internal/lib/browser-noise/rules/rejections.d.ts +10 -0
- package/dist/internal/lib/browser-noise/rules/rejections.js +27 -1
- package/dist/internal/lib/provider-branding.js +14 -3
- package/dist/internal/lib/trigger-clock.d.ts +42 -0
- package/dist/internal/lib/trigger-clock.js +34 -0
- package/dist/internal/lib/web-notifications.d.ts +6 -0
- package/dist/internal/lib/web-notifications.js +57 -7
- package/dist/internal/notifications/notification-bell.d.ts +13 -0
- package/dist/internal/notifications/notification-bell.js +1 -1
- package/dist/internal/notifications/notification-rows.js +2 -1
- package/dist/internal/outcomes/outcome-types.d.ts +5 -0
- package/dist/internal/reminders/reminder-filter.d.ts +12 -0
- package/dist/internal/reminders/reminder-filter.js +34 -0
- package/dist/internal/reminders/reminder-format.d.ts +6 -0
- package/dist/internal/reminders/reminder-format.js +20 -2
- package/dist/internal/stores/kortix-computer-store.d.ts +1 -1
- package/dist/internal/stores/session-hover-store.d.ts +20 -5
- package/dist/internal/stores/session-hover-store.js +31 -13
- package/dist/internal/stores/session-panel-memory-store.js +2 -0
- package/dist/markdown/code/inline-code.js +8 -0
- package/dist/markdown/code/output-paths-context.d.ts +18 -0
- package/dist/markdown/code/output-paths-context.js +35 -0
- package/dist/markdown/genui/components/charts.js +6 -5
- package/dist/primitives/count-badge.d.ts +3 -1
- package/dist/primitives/count-badge.js +3 -3
- package/dist/primitives/glyph-paths-source.js +1 -1
- package/dist/primitives/glyph-paths.generated.js +1 -1
- package/dist/primitives/map.js +2 -1
- package/dist/primitives/preview-image.js +4 -2
- package/dist/primitives/textarea.js +1 -1
- package/dist/primitives/typed-title.js +12 -14
- package/dist/primitives/typing-animation.js +4 -2
- package/dist/providers/ui-provider.d.ts +57 -0
- package/dist/providers/ui-provider.js +4 -0
- package/dist/session/composer/ComposerCard.d.ts +2 -0
- package/dist/session/composer/ComposerCard.js +5 -1
- package/dist/session/composer/agent-selector.js +17 -5
- package/dist/session/composer/composer-chat-input.js +5 -3
- package/dist/session/composer/composer-logic.js +8 -7
- package/dist/session/composer/composer-toolbar.js +4 -2
- package/dist/session/composer/composer-underbar.js +3 -1
- package/dist/session/composer/composer.js +34 -5
- package/dist/session/composer/draft/composer-draft.js +4 -4
- package/dist/session/composer/menus/slash-actions.d.ts +22 -2
- package/dist/session/composer/menus/slash-actions.js +20 -0
- package/dist/session/composer/menus/slash-items.js +19 -3
- package/dist/session/composer/pasted-text.js +3 -2
- package/dist/session/composer/stores/queued-draft-store.js +3 -2
- package/dist/session/header/reminder-parts.d.ts +294 -0
- package/dist/session/header/reminder-parts.js +120 -0
- package/dist/session/header/session-reminders-indicator.d.ts +13 -0
- package/dist/session/header/session-reminders-indicator.js +142 -35
- package/dist/session/media/sandbox-url-detector.js +0 -7
- package/dist/session/models/model-availability.d.ts +23 -0
- package/dist/session/models/model-availability.js +20 -0
- package/dist/session/models/model-grouping.js +1 -4
- package/dist/session/models/model-selector.js +23 -14
- package/dist/session/models/use-model-connection-gate.js +4 -1
- package/dist/session/outcomes/change-request-outcomes.js +24 -3
- package/dist/session/outcomes/session-outcomes-provider.d.ts +3 -1
- package/dist/session/outcomes/session-outcomes-provider.js +31 -3
- package/dist/session/overrides/provider-secret-pool-editor.js +2 -1
- package/dist/session/overrides/session-overrides-composer.d.ts +1 -1
- package/dist/session/overrides/session-overrides-composer.js +5 -1
- package/dist/session/panel/advanced/advanced-panel.js +3 -7
- package/dist/session/panel/easy/app-preview.js +12 -2
- package/dist/session/panel/easy/easy-panel-logic.d.ts +1 -1
- package/dist/session/panel/easy/easy-panel-logic.js +1 -1
- package/dist/session/panel/easy/outputs-card.js +2 -1
- package/dist/session/panel/easy/panel-card.js +5 -3
- package/dist/session/panel/easy/preview-load.d.ts +14 -0
- package/dist/session/panel/easy/preview-load.js +15 -0
- package/dist/session/panel/easy/step-detail-body.js +2 -7
- package/dist/session/panel/mobile-tool-drawer.js +4 -2
- package/dist/session/panel/open-session-quick-view.js +2 -1
- package/dist/session/panel/panel-fallback.js +8 -4
- package/dist/session/panel/session-panel-provider.d.ts +1 -0
- package/dist/session/panel/session-panel-provider.js +19 -3
- package/dist/session/panel/session-reminders-panel.d.ts +4 -0
- package/dist/session/panel/session-reminders-panel.js +109 -0
- package/dist/session/panel/shared/use-panel-derivations.js +6 -4
- package/dist/session/panel/shared/use-panel-handoffs.d.ts +2 -2
- package/dist/session/panel/shared/use-panel-handoffs.js +5 -1
- package/dist/session/panel/use-session-panel-layout.js +7 -5
- package/dist/session/print/session-print-header.d.ts +0 -10
- package/dist/session/print/session-print-header.js +25 -9
- package/dist/session/status/session-surface.js +1 -1
- package/dist/session/thread/optimistic-turn.d.ts +5 -1
- package/dist/session/thread/optimistic-turn.js +8 -6
- package/dist/session/thread/session-chat.d.ts +6 -1
- package/dist/session/thread/session-chat.js +34 -12
- package/dist/session/thread/session-history.js +3 -1
- package/dist/session/thread/session-thread.d.ts +10 -1
- package/dist/session/thread/session-thread.js +2 -2
- package/dist/session/thread/transcript.js +13 -7
- package/dist/session/tool/shared/file-list.js +19 -2
- package/dist/session/tool/shared/infrastructure-contexts.d.ts +7 -0
- package/dist/session/tool/shared/infrastructure-contexts.js +7 -0
- package/dist/session/tool/shared/infrastructure-output.js +9 -5
- package/dist/session/tool/shared/infrastructure-row-header.js +9 -1
- package/dist/session/tool/shared/infrastructure-rows.js +7 -5
- package/dist/session/tool/tools/bash-tool.js +10 -6
- package/dist/session/tool/tools/web-search-tool.js +25 -3
- package/dist/session/turn/burst-step-body.js +4 -0
- package/dist/session/turn/recovered-failures.d.ts +8 -0
- package/dist/session/turn/recovered-failures.js +32 -0
- package/dist/session/turn/streaming-cadence.js +5 -8
- package/dist/styles.css +1 -1
- package/dist/terminal/session-terminal-panel.js +3 -5
- package/dist/theme/theme.js +2 -1
- package/dist/workspace/projects/modal/github-import-pickers.js +7 -6
- package/dist/workspace/review/review-center.js +13 -10
- package/dist/workspace/session-workspace.js +5 -9
- package/dist/workspace/sessions/deleted-session-row.js +5 -2
- package/dist/workspace/sidebar/footer/project-computer-nav.js +6 -4
- package/dist/workspace/sidebar/footer/project-mcp-nav.js +6 -2
- package/dist/workspace/sidebar/modal/rename-session-modal.js +8 -3
- package/dist/workspace/sidebar/project-session-list.js +52 -55
- package/dist/workspace/sidebar/session-brief-hover-card.d.ts +35 -18
- package/dist/workspace/sidebar/session-brief-hover-card.js +114 -37
- package/dist/workspace/sidebar/session-status-mark.d.ts +15 -1
- package/dist/workspace/sidebar/session-status-mark.js +63 -17
- package/package.json +7 -3
- package/skills/kortix-ui/SKILL.md +1 -1
- package/dist/customize/sections/view/sandbox-provider-result.d.ts +0 -47
- package/dist/customize/sections/view/sandbox-provider-result.js +0 -79
|
@@ -31,7 +31,7 @@ import { createElement } from 'react';
|
|
|
31
31
|
import { cachedNotificationCenter, cachedNotificationCenterAnswer, } from '../hooks/use-notification-center.js';
|
|
32
32
|
import { hasWebPushSubscription } from '../notifications/web-push.js';
|
|
33
33
|
import { customToast, dismissToast, ToastCard, toastIcon, errorToast, infoToast, successToast, warningToast, } from '../../primitives/toast.js';
|
|
34
|
-
import { ClockIcon } from '@phosphor-icons/react';
|
|
34
|
+
import { ClockIcon, GitPullRequestIcon } from '@phosphor-icons/react';
|
|
35
35
|
import { SessionFailedCard, SessionStackCard, } from '../notifications/session-toast-cards.js';
|
|
36
36
|
import { logger } from './logger.js';
|
|
37
37
|
import { softNavigate } from './navigation/router-bridge.js';
|
|
@@ -42,7 +42,7 @@ import { playSound } from './sounds.js';
|
|
|
42
42
|
import { openTabAndNavigate, useTabStore } from '../stores/tab-store.js';
|
|
43
43
|
import { useTurnAttentionStore } from '../stores/turn-attention-store.js';
|
|
44
44
|
import { useWebNotificationStore } from '../stores/web-notification-store.js';
|
|
45
|
-
import { normalizeAppPathname } from '@kortix/sdk';
|
|
45
|
+
import { listChangeRequests, normalizeAppPathname, } from '@kortix/sdk';
|
|
46
46
|
// ============================================================================
|
|
47
47
|
// Server push preference
|
|
48
48
|
// ============================================================================
|
|
@@ -138,7 +138,7 @@ function navigateToSession(sessionId, sessionTitle, opts) {
|
|
|
138
138
|
const projectId = opts?.projectId ?? currentProjectId();
|
|
139
139
|
if (!projectId)
|
|
140
140
|
return;
|
|
141
|
-
const href = projectSessionHref(projectId, sessionId);
|
|
141
|
+
const href = projectSessionHref(projectId, sessionId) + (opts?.search ?? '');
|
|
142
142
|
try {
|
|
143
143
|
// Open/activate the tab in the tab store + pushState
|
|
144
144
|
openTabAndNavigate({
|
|
@@ -497,6 +497,15 @@ function showSessionToast(payload, sessionId) {
|
|
|
497
497
|
// focus) names nothing and opens nothing: the person is already there.
|
|
498
498
|
// Read at render, so it follows where the person is now.
|
|
499
499
|
const elsewhere = () => !isViewingSession(sessionId);
|
|
500
|
+
const cr = payload.changeRequest;
|
|
501
|
+
// `?cr=` opens the change request's review dialog on the session page.
|
|
502
|
+
const review = () => {
|
|
503
|
+
navigateToSession(sessionId, payload.body, {
|
|
504
|
+
projectId: payload.projectId,
|
|
505
|
+
search: cr ? `?cr=${encodeURIComponent(cr.crId)}` : undefined,
|
|
506
|
+
});
|
|
507
|
+
payload.onClick?.();
|
|
508
|
+
};
|
|
500
509
|
if (item.failed) {
|
|
501
510
|
customToast((dismiss) => createElement(SessionFailedCard, {
|
|
502
511
|
title: payload.inAppTitle ?? payload.title,
|
|
@@ -512,12 +521,23 @@ function showSessionToast(payload, sessionId) {
|
|
|
512
521
|
message: payload.inAppTitle ?? payload.title,
|
|
513
522
|
icon: toastIcon('success'),
|
|
514
523
|
variant: 'success',
|
|
524
|
+
// On the session itself the change request card is in the transcript,
|
|
525
|
+
// so the toast only says it is there.
|
|
515
526
|
options: elsewhere()
|
|
516
527
|
? {
|
|
517
|
-
entity: {
|
|
518
|
-
|
|
528
|
+
entity: {
|
|
529
|
+
icon: cr ? createElement(GitPullRequestIcon, { className: 'size-3.5' }) : undefined,
|
|
530
|
+
label: item.label,
|
|
531
|
+
},
|
|
532
|
+
description: cr?.detail,
|
|
533
|
+
actions: [
|
|
534
|
+
...(cr ? [{ label: cr.reviewLabel, onClick: review }] : []),
|
|
535
|
+
{ label: payload.openSessionLabel ?? openLabel, onClick: () => open(item) },
|
|
536
|
+
],
|
|
519
537
|
}
|
|
520
|
-
:
|
|
538
|
+
: cr
|
|
539
|
+
? { description: cr.detail }
|
|
540
|
+
: undefined,
|
|
521
541
|
onDismiss: dismiss,
|
|
522
542
|
}), { id, duration: NOTIFICATION_VISIBLE_MS, onClose });
|
|
523
543
|
}
|
|
@@ -576,7 +596,7 @@ export function notifyTaskCompleteFor({ sessionId, sessionTitle, projectId, noti
|
|
|
576
596
|
noteTurnCompleteShown(sessionId);
|
|
577
597
|
const name = sessionDisplayName(sessionTitle, sessionId);
|
|
578
598
|
const label = sessionTitle ? `"${name.slice(0, 60)}"` : name;
|
|
579
|
-
|
|
599
|
+
const payload = {
|
|
580
600
|
type: 'completion',
|
|
581
601
|
entityLabel: name,
|
|
582
602
|
openSessionLabel: tI18nComplete.raw('textb205bb47f81a'),
|
|
@@ -591,8 +611,38 @@ export function notifyTaskCompleteFor({ sessionId, sessionTitle, projectId, noti
|
|
|
591
611
|
// deep link (the same rule the click-time path documents above).
|
|
592
612
|
projectId,
|
|
593
613
|
notificationCenter,
|
|
614
|
+
};
|
|
615
|
+
if (!projectId) {
|
|
616
|
+
sendWebNotification(payload);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
void openChangeRequestOf(projectId, sessionId).then((cr) => {
|
|
620
|
+
if (!cr) {
|
|
621
|
+
sendWebNotification(payload);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
const detail = tI18nComplete('text57f57edb6f16', { number: cr.number });
|
|
625
|
+
sendWebNotification({
|
|
626
|
+
...payload,
|
|
627
|
+
body: `${payload.body} ${detail}`,
|
|
628
|
+
changeRequest: { crId: cr.cr_id, detail, reviewLabel: tI18nComplete.raw('textaff0766a5290') },
|
|
629
|
+
});
|
|
594
630
|
});
|
|
595
631
|
}
|
|
632
|
+
/** How long the finished-turn toast waits for the change request lookup. */
|
|
633
|
+
const CHANGE_REQUEST_LOOKUP_MS = 2_500;
|
|
634
|
+
/**
|
|
635
|
+
* The newest open change request the session opened, or null. Server-vouched
|
|
636
|
+
* by `origin_session_id`, the same rule as the transcript's change request card.
|
|
637
|
+
* A slow or failed lookup answers null, so the toast never waits on it for long.
|
|
638
|
+
*/
|
|
639
|
+
function openChangeRequestOf(projectId, sessionId) {
|
|
640
|
+
const lookup = listChangeRequests(projectId, 'open', { originSessionId: sessionId, limit: 1 })
|
|
641
|
+
.then((page) => page.change_requests[0] ?? null)
|
|
642
|
+
.catch(() => null);
|
|
643
|
+
const timeout = new Promise((resolve) => setTimeout(() => resolve(null), CHANGE_REQUEST_LOOKUP_MS));
|
|
644
|
+
return Promise.race([lookup, timeout]);
|
|
645
|
+
}
|
|
596
646
|
/**
|
|
597
647
|
* Notify that a session encountered an error.
|
|
598
648
|
*/
|
|
@@ -14,6 +14,19 @@ export interface NotificationPanelProps {
|
|
|
14
14
|
}
|
|
15
15
|
/** Props only: the header, the All / Needs you / Done tabs, the rows, and the footer offer. */
|
|
16
16
|
export declare function NotificationPanel({ rows, state, unreadCount, prompt, onOpenRow, onMarkAllRead, onRetry, onTurnOn, onDismissPrompt, }: NotificationPanelProps): import("react").JSX.Element;
|
|
17
|
+
/**
|
|
18
|
+
* The bell (Paper B12 in the toast's badge-glow style): the glyph muted, then
|
|
19
|
+
* again in the badge's colour under `BELL_REFLECTION_MASK`, so the
|
|
20
|
+
* colour reaches in from the corner the count sits on, as `EntityTile` in
|
|
21
|
+
* `components/ui/toast.tsx` and `TriggerTile` draw it. Yellow when something
|
|
22
|
+
* waits on the person (the Needs you tab), blue otherwise.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* The toast's `REFLECTION_MASK` scaled from its 20px icon to the 16px bell
|
|
26
|
+
* (16px → 12px), centred on the glyph's bottom-right corner where the count
|
|
27
|
+
* sits. At full size it would tint the whole bell.
|
|
28
|
+
*/
|
|
29
|
+
export declare const BELL_REFLECTION_MASK = "radial-gradient(circle 12px at 100% 100%, #000 35%, transparent 100%)";
|
|
17
30
|
export declare function BellMark({ count, needsYou, surface, }: {
|
|
18
31
|
count: number;
|
|
19
32
|
needsYou: boolean;
|
|
@@ -78,7 +78,7 @@ export function NotificationPanel({ rows, state, unreadCount, prompt, onOpenRow,
|
|
|
78
78
|
* (16px → 12px), centred on the glyph's bottom-right corner where the count
|
|
79
79
|
* sits. At full size it would tint the whole bell.
|
|
80
80
|
*/
|
|
81
|
-
const BELL_REFLECTION_MASK = 'radial-gradient(circle 12px at 100% 100%, #000 35%, transparent 100%)';
|
|
81
|
+
export const BELL_REFLECTION_MASK = 'radial-gradient(circle 12px at 100% 100%, #000 35%, transparent 100%)';
|
|
82
82
|
export function BellMark({ count, needsYou, surface = 'bg-background', }) {
|
|
83
83
|
const tone = needsYou
|
|
84
84
|
? { reflection: 'text-kortix-yellow', disc: 'bg-kortix-yellow' }
|
|
@@ -44,10 +44,11 @@ export function newArrivals(seen, rows) {
|
|
|
44
44
|
* is not.
|
|
45
45
|
*/
|
|
46
46
|
export function planArrivals(fresh, context) {
|
|
47
|
+
const unseen = new Set(context.unseen);
|
|
47
48
|
const announce = fresh
|
|
48
49
|
.filter((row) => !row.session_id ||
|
|
49
50
|
(!context.onScreen(row.session_id) &&
|
|
50
|
-
!(row.kind === 'turn_done' &&
|
|
51
|
+
!(row.kind === 'turn_done' && unseen.has(row.session_id))))
|
|
51
52
|
.slice(0, MAX_ARRIVAL_ALERTS);
|
|
52
53
|
return context.os ? { toast: [], os: announce } : { toast: announce, os: [] };
|
|
53
54
|
}
|
|
@@ -49,6 +49,11 @@ export interface OutcomeAction {
|
|
|
49
49
|
intent: 'open' | 'link';
|
|
50
50
|
/** Required when `intent` is `link`; ignored otherwise. */
|
|
51
51
|
href?: string;
|
|
52
|
+
/**
|
|
53
|
+
* For `open`: the source id the modal opens when it is not this outcome's
|
|
54
|
+
* own. A superseded change request opens its replacement (KRTX-3031).
|
|
55
|
+
*/
|
|
56
|
+
target?: string;
|
|
52
57
|
}
|
|
53
58
|
export interface Outcome {
|
|
54
59
|
/**
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** @internal Kortix apps only. No semver: it may change in any release. */
|
|
2
|
+
import type { SessionReminder } from '@kortix/sdk';
|
|
3
|
+
export type ReminderStatusFilter = 'all' | 'active' | 'paused' | 'failed';
|
|
4
|
+
/** By next run: within the hour, the day, the week, or later than a week. */
|
|
5
|
+
export type ReminderTimeFilter = 'any' | 'hour' | 'day' | 'week' | 'later';
|
|
6
|
+
export declare const REMINDER_STATUS_FILTERS: readonly ReminderStatusFilter[];
|
|
7
|
+
export declare const REMINDER_TIME_FILTERS: readonly ReminderTimeFilter[];
|
|
8
|
+
export declare function matchesStatus(reminder: SessionReminder, status: ReminderStatusFilter): boolean;
|
|
9
|
+
/** A time filter other than `any` keeps only active reminders: a paused one has no next run. */
|
|
10
|
+
export declare function matchesTime(reminder: SessionReminder, time: ReminderTimeFilter, now: number): boolean;
|
|
11
|
+
/** Every word of the query appears in the title, the prompt or the schedule label. */
|
|
12
|
+
export declare function matchesQuery(reminder: SessionReminder, query: string, schedule: string): boolean;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const REMINDER_STATUS_FILTERS = ['all', 'active', 'paused', 'failed'];
|
|
2
|
+
export const REMINDER_TIME_FILTERS = ['any', 'hour', 'day', 'week', 'later'];
|
|
3
|
+
const HOUR = 60 * 60 * 1000;
|
|
4
|
+
const WINDOW_MS = {
|
|
5
|
+
hour: HOUR,
|
|
6
|
+
day: 24 * HOUR,
|
|
7
|
+
week: 7 * 24 * HOUR,
|
|
8
|
+
};
|
|
9
|
+
export function matchesStatus(reminder, status) {
|
|
10
|
+
if (status === 'all')
|
|
11
|
+
return true;
|
|
12
|
+
if (status === 'failed')
|
|
13
|
+
return reminder.last_status === 'failed';
|
|
14
|
+
return status === 'active' ? reminder.state === 'active' : reminder.state !== 'active';
|
|
15
|
+
}
|
|
16
|
+
/** A time filter other than `any` keeps only active reminders: a paused one has no next run. */
|
|
17
|
+
export function matchesTime(reminder, time, now) {
|
|
18
|
+
if (time === 'any')
|
|
19
|
+
return true;
|
|
20
|
+
const next = reminder.state === 'active' && reminder.next_fire_at ? Date.parse(reminder.next_fire_at) : NaN;
|
|
21
|
+
if (!Number.isFinite(next))
|
|
22
|
+
return false;
|
|
23
|
+
if (time === 'later')
|
|
24
|
+
return next - now > WINDOW_MS.week;
|
|
25
|
+
return next - now <= WINDOW_MS[time];
|
|
26
|
+
}
|
|
27
|
+
/** Every word of the query appears in the title, the prompt or the schedule label. */
|
|
28
|
+
export function matchesQuery(reminder, query, schedule) {
|
|
29
|
+
const words = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
30
|
+
if (words.length === 0)
|
|
31
|
+
return true;
|
|
32
|
+
const haystack = [reminder.name, reminder.prompt, schedule].filter(Boolean).join(' ').toLowerCase();
|
|
33
|
+
return words.every((word) => haystack.includes(word));
|
|
34
|
+
}
|
|
@@ -14,6 +14,12 @@ export declare function formatFireTime(iso: string, locale: string, now: number)
|
|
|
14
14
|
*/
|
|
15
15
|
export declare function nearestFire(reminders: readonly Pick<SessionReminder, 'state' | 'next_fire_at' | 'last_fired_at' | 'at'>[]): number | null;
|
|
16
16
|
export declare function soonestFire(reminders: readonly Pick<SessionReminder, 'state' | 'next_fire_at'>[]): number | null;
|
|
17
|
+
/**
|
|
18
|
+
* How much of the wait before the next fire has passed, 0..1: the interval of
|
|
19
|
+
* an `every` reminder, else the span since the last fire (or creation). Null
|
|
20
|
+
* when there is no next fire to count toward.
|
|
21
|
+
*/
|
|
22
|
+
export declare function elapsedShare(reminder: Pick<SessionReminder, 'next_fire_at' | 'every_seconds' | 'last_fired_at' | 'created_at'>, now: number): number | null;
|
|
17
23
|
/** The first line of the reminder text, for a one-line row title. */
|
|
18
24
|
export declare function reminderTitle(reminder: Pick<SessionReminder, 'name' | 'prompt'>): string;
|
|
19
25
|
/** "Cron 0 9 * * * Europe/Berlin", "Every 1h" or "Once"; `t` is the `reminders` translator. */
|
|
@@ -10,10 +10,12 @@ export function formatFireTime(iso, locale, now) {
|
|
|
10
10
|
const deltaMin = Math.round((at - now) / 60_000);
|
|
11
11
|
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto', style: 'short' });
|
|
12
12
|
const abs = Math.abs(deltaMin);
|
|
13
|
+
// `short` abbreviates with a period ("in 32 min."); a row's trailing label reads cleaner without it.
|
|
14
|
+
const relative = (value, unit) => rtf.format(value, unit).replace(/\.(?=\s|$)/g, '');
|
|
13
15
|
if (abs < 60)
|
|
14
|
-
return
|
|
16
|
+
return relative(deltaMin, 'minute');
|
|
15
17
|
if (abs < 24 * 60)
|
|
16
|
-
return
|
|
18
|
+
return relative(Math.round(deltaMin / 60), 'hour');
|
|
17
19
|
const date = new Date(at);
|
|
18
20
|
if (deltaMin > 0 && deltaMin < 7 * 24 * 60) {
|
|
19
21
|
return date.toLocaleString(locale, { weekday: 'short', hour: '2-digit', minute: '2-digit' });
|
|
@@ -60,6 +62,22 @@ export function soonestFire(reminders) {
|
|
|
60
62
|
}
|
|
61
63
|
return soonest;
|
|
62
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* How much of the wait before the next fire has passed, 0..1: the interval of
|
|
67
|
+
* an `every` reminder, else the span since the last fire (or creation). Null
|
|
68
|
+
* when there is no next fire to count toward.
|
|
69
|
+
*/
|
|
70
|
+
export function elapsedShare(reminder, now) {
|
|
71
|
+
const next = reminder.next_fire_at ? Date.parse(reminder.next_fire_at) : NaN;
|
|
72
|
+
if (!Number.isFinite(next))
|
|
73
|
+
return null;
|
|
74
|
+
const start = reminder.every_seconds
|
|
75
|
+
? next - reminder.every_seconds * 1000
|
|
76
|
+
: Date.parse(reminder.last_fired_at ?? reminder.created_at ?? '');
|
|
77
|
+
if (!Number.isFinite(start) || next <= start)
|
|
78
|
+
return null;
|
|
79
|
+
return Math.min(1, Math.max(0, (now - start) / (next - start)));
|
|
80
|
+
}
|
|
63
81
|
/** The first line of the reminder text, for a one-line row title. */
|
|
64
82
|
export function reminderTitle(reminder) {
|
|
65
83
|
return reminder.name ?? reminder.prompt.split('\n')[0].trim();
|
|
@@ -12,7 +12,7 @@ export interface ReadyChipState {
|
|
|
12
12
|
primaryName?: string;
|
|
13
13
|
}
|
|
14
14
|
/** The panel surfaces reachable from outside the panel. */
|
|
15
|
-
export type QuickView = 'terminal' | 'audit' | 'browser' | 'files';
|
|
15
|
+
export type QuickView = 'terminal' | 'audit' | 'browser' | 'files' | 'reminders';
|
|
16
16
|
/**
|
|
17
17
|
* Extra aim for a quick-view request — "open the browser" vs "open the browser
|
|
18
18
|
* ON THIS URL".
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
* the active row is what is left, and it is also the cheaper shape: exactly one
|
|
12
12
|
* card is mounted at any time, by construction rather than by timing luck.
|
|
13
13
|
*
|
|
14
|
+
* The store also holds the active row's element and the one card's content
|
|
15
|
+
* slot (`SessionBriefCardHost`): rows render their brief into that slot, and
|
|
16
|
+
* the host glides to the row instead of a new card appearing at it.
|
|
17
|
+
*
|
|
14
18
|
* The delays live here rather than on the primitive for a performance reason.
|
|
15
19
|
* If `openDelay` were a prop derived from `warm`, every row in the sidebar would
|
|
16
20
|
* re-render each time the group warmed or cooled. Nothing selects `warm`, so it
|
|
@@ -20,15 +24,19 @@
|
|
|
20
24
|
/** The first card costs a delay, so a pointer crossing the list on its way
|
|
21
25
|
* somewhere else does not flash open every row it passes over. */
|
|
22
26
|
export declare const HOVER_OPEN_DELAY_MS = 200;
|
|
27
|
+
/** While a card is open, a move to another row waits this long, and a newer
|
|
28
|
+
* row restarts the wait: a pointer sweeping down the list moves the card once,
|
|
29
|
+
* to where it stops, instead of re-rendering it for every row it crosses. */
|
|
30
|
+
export declare const HOVER_MOVE_DEBOUNCE_MS = 50;
|
|
23
31
|
/** Long enough for the pointer to cross the `sideOffset` gap into the card. */
|
|
24
32
|
export declare const HOVER_CLOSE_DELAY_MS = 100;
|
|
25
33
|
/** How long the group stays warm after its last card closes. */
|
|
26
34
|
export declare const HOVER_WARM_GRACE_MS = 300;
|
|
27
35
|
/**
|
|
28
|
-
* Once one card is open the group is warm and the next row
|
|
29
|
-
* card reads as moving to the row under the pointer instead of
|
|
30
|
-
* reopening. This is the rule the design system already states for
|
|
31
|
-
* delay before the first, none for the rest
|
|
36
|
+
* Once one card is open the group is warm and the next row costs only the move
|
|
37
|
+
* debounce, so the card reads as moving to the row under the pointer instead of
|
|
38
|
+
* closing and reopening. This is the rule the design system already states for
|
|
39
|
+
* tooltips: delay before the first, (almost) none for the rest.
|
|
32
40
|
*/
|
|
33
41
|
export declare function hoverOpenDelayMs(warm: boolean): number;
|
|
34
42
|
/**
|
|
@@ -38,9 +46,16 @@ export declare function hoverOpenDelayMs(warm: boolean): number;
|
|
|
38
46
|
export declare function clearIfActive(current: string | null, sessionId: string): string | null;
|
|
39
47
|
interface SessionHoverState {
|
|
40
48
|
activeSessionId: string | null;
|
|
49
|
+
/** The active row's element: the card lines up with it. */
|
|
50
|
+
anchor: HTMLElement | null;
|
|
51
|
+
/** Keyboard focus moves the card without a glide. */
|
|
52
|
+
via: 'pointer' | 'focus';
|
|
53
|
+
/** The shared card's content slot, from `SessionBriefCardHost`. */
|
|
54
|
+
slot: HTMLElement | null;
|
|
55
|
+
setSlot: (slot: HTMLElement | null) => void;
|
|
41
56
|
/** Deliberately unselected by any component — see the note above. */
|
|
42
57
|
warm: boolean;
|
|
43
|
-
openSession: (sessionId: string) => void;
|
|
58
|
+
openSession: (sessionId: string, anchor?: HTMLElement, via?: 'pointer' | 'focus') => void;
|
|
44
59
|
closeSession: (sessionId: string) => void;
|
|
45
60
|
/** Immediate, for a click that opens something else and for unmount. */
|
|
46
61
|
dismiss: () => void;
|
|
@@ -12,6 +12,10 @@ import { create } from 'zustand';
|
|
|
12
12
|
* the active row is what is left, and it is also the cheaper shape: exactly one
|
|
13
13
|
* card is mounted at any time, by construction rather than by timing luck.
|
|
14
14
|
*
|
|
15
|
+
* The store also holds the active row's element and the one card's content
|
|
16
|
+
* slot (`SessionBriefCardHost`): rows render their brief into that slot, and
|
|
17
|
+
* the host glides to the row instead of a new card appearing at it.
|
|
18
|
+
*
|
|
15
19
|
* The delays live here rather than on the primitive for a performance reason.
|
|
16
20
|
* If `openDelay` were a prop derived from `warm`, every row in the sidebar would
|
|
17
21
|
* re-render each time the group warmed or cooled. Nothing selects `warm`, so it
|
|
@@ -21,18 +25,22 @@ import { create } from 'zustand';
|
|
|
21
25
|
/** The first card costs a delay, so a pointer crossing the list on its way
|
|
22
26
|
* somewhere else does not flash open every row it passes over. */
|
|
23
27
|
export const HOVER_OPEN_DELAY_MS = 200;
|
|
28
|
+
/** While a card is open, a move to another row waits this long, and a newer
|
|
29
|
+
* row restarts the wait: a pointer sweeping down the list moves the card once,
|
|
30
|
+
* to where it stops, instead of re-rendering it for every row it crosses. */
|
|
31
|
+
export const HOVER_MOVE_DEBOUNCE_MS = 50;
|
|
24
32
|
/** Long enough for the pointer to cross the `sideOffset` gap into the card. */
|
|
25
33
|
export const HOVER_CLOSE_DELAY_MS = 100;
|
|
26
34
|
/** How long the group stays warm after its last card closes. */
|
|
27
35
|
export const HOVER_WARM_GRACE_MS = 300;
|
|
28
36
|
/**
|
|
29
|
-
* Once one card is open the group is warm and the next row
|
|
30
|
-
* card reads as moving to the row under the pointer instead of
|
|
31
|
-
* reopening. This is the rule the design system already states for
|
|
32
|
-
* delay before the first, none for the rest
|
|
37
|
+
* Once one card is open the group is warm and the next row costs only the move
|
|
38
|
+
* debounce, so the card reads as moving to the row under the pointer instead of
|
|
39
|
+
* closing and reopening. This is the rule the design system already states for
|
|
40
|
+
* tooltips: delay before the first, (almost) none for the rest.
|
|
33
41
|
*/
|
|
34
42
|
export function hoverOpenDelayMs(warm) {
|
|
35
|
-
return warm ?
|
|
43
|
+
return warm ? HOVER_MOVE_DEBOUNCE_MS : HOVER_OPEN_DELAY_MS;
|
|
36
44
|
}
|
|
37
45
|
/**
|
|
38
46
|
* Compare-and-clear, the same guard `session-switch-store` uses: a close
|
|
@@ -44,6 +52,8 @@ export function clearIfActive(current, sessionId) {
|
|
|
44
52
|
let openTimer = null;
|
|
45
53
|
let closeTimer = null;
|
|
46
54
|
let warmTimer = null;
|
|
55
|
+
/** The row a pending open will show; its close must also close the card still showing. */
|
|
56
|
+
let pendingId = null;
|
|
47
57
|
function cancel(timer) {
|
|
48
58
|
if (timer)
|
|
49
59
|
clearTimeout(timer);
|
|
@@ -51,8 +61,12 @@ function cancel(timer) {
|
|
|
51
61
|
}
|
|
52
62
|
export const useSessionHoverStore = create((set, get) => ({
|
|
53
63
|
activeSessionId: null,
|
|
64
|
+
anchor: null,
|
|
65
|
+
via: 'pointer',
|
|
66
|
+
slot: null,
|
|
54
67
|
warm: false,
|
|
55
|
-
|
|
68
|
+
setSlot: (slot) => set({ slot }),
|
|
69
|
+
openSession: (sessionId, anchor, via = 'pointer') => {
|
|
56
70
|
closeTimer = cancel(closeTimer);
|
|
57
71
|
warmTimer = cancel(warmTimer);
|
|
58
72
|
// A pending open belongs to whichever row the pointer has just left.
|
|
@@ -61,21 +75,24 @@ export const useSessionHoverStore = create((set, get) => ({
|
|
|
61
75
|
// or crossing the gap back from the card would flicker it.
|
|
62
76
|
if (get().activeSessionId === sessionId)
|
|
63
77
|
return;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
78
|
+
const show = { activeSessionId: sessionId, anchor: anchor ?? null, via, warm: true };
|
|
79
|
+
pendingId = sessionId;
|
|
68
80
|
openTimer = setTimeout(() => {
|
|
69
81
|
openTimer = null;
|
|
70
|
-
|
|
71
|
-
|
|
82
|
+
pendingId = null;
|
|
83
|
+
set(show);
|
|
84
|
+
}, hoverOpenDelayMs(get().warm));
|
|
72
85
|
},
|
|
73
86
|
closeSession: (sessionId) => {
|
|
74
87
|
openTimer = cancel(openTimer);
|
|
75
88
|
closeTimer = cancel(closeTimer);
|
|
89
|
+
// Leaving a row whose move had not landed yet: the card still shows the
|
|
90
|
+
// previous row, and that card is the one to close.
|
|
91
|
+
const target = pendingId === sessionId ? get().activeSessionId : sessionId;
|
|
92
|
+
pendingId = null;
|
|
76
93
|
closeTimer = setTimeout(() => {
|
|
77
94
|
closeTimer = null;
|
|
78
|
-
set((state) => ({ activeSessionId: clearIfActive(state.activeSessionId,
|
|
95
|
+
set((state) => ({ activeSessionId: target ? clearIfActive(state.activeSessionId, target) : state.activeSessionId }));
|
|
79
96
|
warmTimer = setTimeout(() => {
|
|
80
97
|
warmTimer = null;
|
|
81
98
|
set({ warm: false });
|
|
@@ -83,6 +100,7 @@ export const useSessionHoverStore = create((set, get) => ({
|
|
|
83
100
|
}, HOVER_CLOSE_DELAY_MS);
|
|
84
101
|
},
|
|
85
102
|
dismiss: () => {
|
|
103
|
+
pendingId = null;
|
|
86
104
|
openTimer = cancel(openTimer);
|
|
87
105
|
closeTimer = cancel(closeTimer);
|
|
88
106
|
warmTimer = cancel(warmTimer);
|
|
@@ -40,6 +40,8 @@ export function rememberedPanelOf({ terminalOpen, detailKey, }) {
|
|
|
40
40
|
return { kind: 'view', view: 'files', target: { changes: true } };
|
|
41
41
|
if (detailKey === 'audit')
|
|
42
42
|
return { kind: 'view', view: 'audit' };
|
|
43
|
+
if (detailKey === 'reminders')
|
|
44
|
+
return { kind: 'view', view: 'reminders' };
|
|
43
45
|
if (detailKey === 'app:landing')
|
|
44
46
|
return { kind: 'view', view: 'browser' };
|
|
45
47
|
if (detailKey?.startsWith('app:'))
|
|
@@ -12,6 +12,7 @@ import { useSessionBrowserStore } from '../../internal/stores/session-browser-st
|
|
|
12
12
|
import { useUiLink } from '../../providers/ui-provider.js';
|
|
13
13
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
14
14
|
import { useInsideLink } from './inside-link-context.js';
|
|
15
|
+
import { outputPathForName, useOutputPaths } from './output-paths-context.js';
|
|
15
16
|
import { childrenToText } from './children-text.js';
|
|
16
17
|
// Server-safe by design — see `inline-chip.tsx`. Re-exported so the client
|
|
17
18
|
// consumers that already import them from here keep working.
|
|
@@ -114,6 +115,7 @@ export function ClickableInlineCode({ children }) {
|
|
|
114
115
|
const { proxyUrl } = useSandboxProxy();
|
|
115
116
|
const openPreview = useOpenPreviewLink();
|
|
116
117
|
const insideLink = useInsideLink();
|
|
118
|
+
const outputPaths = useOutputPaths();
|
|
117
119
|
const text = childrenToText(children).trim();
|
|
118
120
|
// Before the URL and path checks: a hex is neither, and the test is a single
|
|
119
121
|
// anchored regex against a string that is at most nine characters.
|
|
@@ -146,5 +148,11 @@ export function ClickableInlineCode({ children }) {
|
|
|
146
148
|
if (looksLikeFilePath(text)) {
|
|
147
149
|
return _jsx(FilePathCode, { text: text, children: children });
|
|
148
150
|
}
|
|
151
|
+
// A bare filename (`claims.md`) is no path, but it may name one of this
|
|
152
|
+
// session's outputs: open that file, still showing the name.
|
|
153
|
+
const outputPath = outputPathForName(text, outputPaths);
|
|
154
|
+
if (outputPath) {
|
|
155
|
+
return _jsx(FilePathCode, { text: outputPath, children: children });
|
|
156
|
+
}
|
|
149
157
|
return _jsx("code", { className: INLINE_CODE, children: children });
|
|
150
158
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The paths of the files this session produced: its Outputs.
|
|
3
|
+
*
|
|
4
|
+
* A reply names its deliverables by bare filename (`claims.md`), and a bare
|
|
5
|
+
* name is not a path, so it never linked. Opening it took Expand pane →
|
|
6
|
+
* Outputs → the file (KRTX-3023). Inline code that names exactly one of these
|
|
7
|
+
* outputs now opens it.
|
|
8
|
+
*
|
|
9
|
+
* Provider: `SessionPanelProvider`. Empty outside a session: nothing extra links.
|
|
10
|
+
*/
|
|
11
|
+
export declare const OutputPathsContext: import("react").Context<readonly string[]>;
|
|
12
|
+
export declare const useOutputPaths: () => readonly string[];
|
|
13
|
+
/**
|
|
14
|
+
* The output a bare filename names, or null: it is not a filename, no output
|
|
15
|
+
* has that name, or two different outputs share it and the reply does not say
|
|
16
|
+
* which.
|
|
17
|
+
*/
|
|
18
|
+
export declare function outputPathForName(text: string, paths: readonly string[]): string | null;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* The paths of the files this session produced: its Outputs.
|
|
5
|
+
*
|
|
6
|
+
* A reply names its deliverables by bare filename (`claims.md`), and a bare
|
|
7
|
+
* name is not a path, so it never linked. Opening it took Expand pane →
|
|
8
|
+
* Outputs → the file (KRTX-3023). Inline code that names exactly one of these
|
|
9
|
+
* outputs now opens it.
|
|
10
|
+
*
|
|
11
|
+
* Provider: `SessionPanelProvider`. Empty outside a session: nothing extra links.
|
|
12
|
+
*/
|
|
13
|
+
export const OutputPathsContext = createContext([]);
|
|
14
|
+
export const useOutputPaths = () => useContext(OutputPathsContext);
|
|
15
|
+
const BARE_FILENAME = /^[\w@][\w@.-]*\.[A-Za-z0-9]+$/;
|
|
16
|
+
/** `/workspace/a/b.md` and `a/b.md` are one file. */
|
|
17
|
+
const sameFileKey = (path) => path.replace(/^\/workspace\//, '');
|
|
18
|
+
/**
|
|
19
|
+
* The output a bare filename names, or null: it is not a filename, no output
|
|
20
|
+
* has that name, or two different outputs share it and the reply does not say
|
|
21
|
+
* which.
|
|
22
|
+
*/
|
|
23
|
+
export function outputPathForName(text, paths) {
|
|
24
|
+
if (!BARE_FILENAME.test(text))
|
|
25
|
+
return null;
|
|
26
|
+
let found = null;
|
|
27
|
+
for (const path of paths) {
|
|
28
|
+
if (path.slice(path.lastIndexOf('/') + 1) !== text)
|
|
29
|
+
continue;
|
|
30
|
+
if (found !== null && sameFileKey(found) !== sameFileKey(path))
|
|
31
|
+
return null;
|
|
32
|
+
found ??= path;
|
|
33
|
+
}
|
|
34
|
+
return found;
|
|
35
|
+
}
|
|
@@ -25,7 +25,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
25
25
|
*/
|
|
26
26
|
// eslint-disable-next-line no-restricted-imports -- screen-reader text for the figure; this module is reached only through lazy()
|
|
27
27
|
import { genuiA11yText } from '@kortix/sdk/genui';
|
|
28
|
-
import { useState } from 'react';
|
|
28
|
+
import { useMemo, useState } from 'react';
|
|
29
29
|
import { Bar, BarChart, CartesianGrid, Cell, Line, LineChart, Pie, PieChart, XAxis, YAxis } from 'recharts';
|
|
30
30
|
import { ChartContainer, ChartTooltip, ChartTooltipContent } from '../../../primitives/chart.js';
|
|
31
31
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '../../../primitives/dialog.js';
|
|
@@ -95,7 +95,7 @@ function PieFigure({ variant, slices, total, unit, format, tooltip, focus, }) {
|
|
|
95
95
|
const restTotal = rest.reduce((sum, slice) => sum + slice.value, 0);
|
|
96
96
|
const block = (slice, i) => {
|
|
97
97
|
const small = i > 0 && slice.value / restTotal < 0.12;
|
|
98
|
-
return (_jsxs(HoverCard, { openDelay: 80, closeDelay: 40, children: [_jsx(HoverCardTrigger, { asChild: true, children: _jsx("
|
|
98
|
+
return (_jsxs(HoverCard, { openDelay: 80, closeDelay: 40, children: [_jsx(HoverCardTrigger, { asChild: true, children: _jsx("button", { type: "button", onMouseEnter: () => focus.hover(slice.key), onMouseLeave: () => focus.hover(null), onClick: () => focus.toggle(slice.key), "aria-label": `${slice.label}: ${format(slice.value)} (${slice.percent}%)`, className: cn('focus-visible:ring-ring duration-fast flex min-h-1.5 min-w-0 flex-col justify-between overflow-hidden rounded-md text-left transition-opacity outline-none focus-visible:ring-2', small ? 'p-0' : 'p-3', i < 2 ? 'text-background' : 'text-foreground'), style: { backgroundColor: PIE_RAMP[i], flexGrow: slice.value, flexBasis: 0, opacity: dimmed(focus, slice.key) }, children: small ? null : (_jsxs(_Fragment, { children: [_jsx("span", { className: "truncate text-xs", children: slice.label }), _jsxs("span", { className: cn('truncate tabular-nums', i === 0 ? 'text-2xl font-medium' : 'text-sm'), children: [slice.percent, "%"] })] })) }) }), _jsxs(HoverCardContent, { side: "top", className: "flex w-auto min-w-40 flex-col gap-1 p-3", children: [_jsxs("span", { className: "text-foreground flex items-center gap-2 text-sm font-medium", children: [dot(slice.fill), slice.label] }), _jsxs("span", { className: "text-muted-foreground font-mono text-xs tabular-nums", children: [unit ? `${format(slice.value)} ${unit}` : format(slice.value), " \u00B7 ", slice.percent, "%"] })] })] }, slice.key));
|
|
99
99
|
};
|
|
100
100
|
return (_jsxs("div", { className: "flex min-h-0 flex-1 gap-1", children: [block(first, 0), rest.length > 0 ? (_jsx("div", { className: "flex min-w-0 flex-col gap-1", style: { flexGrow: restTotal, flexBasis: 0 }, children: rest.map((slice, i) => block(slice, i + 1)) })) : null] }));
|
|
101
101
|
}
|
|
@@ -118,11 +118,11 @@ export function ChartView({ node, props }) {
|
|
|
118
118
|
const t = useTranslations('genui');
|
|
119
119
|
const focus = useFocus();
|
|
120
120
|
const locale = useLocale();
|
|
121
|
+
const number = useMemo(() => new Intl.NumberFormat(locale), [locale]);
|
|
122
|
+
const compact = useMemo(() => new Intl.NumberFormat(locale, { notation: 'compact' }), [locale]);
|
|
121
123
|
// A streamed pie arrives before its slices resolve (the parent line comes first). Hold its space until one lands.
|
|
122
124
|
if (node.type === 'PieChart' && kids(props.slices).length === 0)
|
|
123
125
|
return GenuiPending(node);
|
|
124
|
-
const number = new Intl.NumberFormat(locale);
|
|
125
|
-
const compact = new Intl.NumberFormat(locale, { notation: 'compact' });
|
|
126
126
|
const format = (value) => (typeof value === 'number' ? number.format(value) : value == null ? '—' : String(value));
|
|
127
127
|
// The tooltip row of `ChartTooltipContent`, with the table's number format.
|
|
128
128
|
const tooltip = (_jsx(ChartTooltipContent, { hideLabel: node.type === 'PieChart', formatter: (value, name, item) => (_jsxs(_Fragment, { children: [_jsx("span", { className: "size-2.5 shrink-0 rounded-xs", style: { backgroundColor: item.payload?.fill ?? item.color } }), _jsxs("span", { className: "flex flex-1 items-center justify-between gap-2 leading-none", children: [_jsx("span", { className: "text-muted-foreground", children: name }), _jsx("span", { className: "text-foreground font-mono font-medium tabular-nums", children: format(value) })] })] })) }));
|
|
@@ -160,7 +160,8 @@ export function ChartView({ node, props }) {
|
|
|
160
160
|
/** The chart's values as text: the Show data dialog's table. A missing value stays a dash, never a zero. */
|
|
161
161
|
export function ChartDataTable({ node }) {
|
|
162
162
|
const t = useTranslations('genui');
|
|
163
|
-
const
|
|
163
|
+
const locale = useLocale();
|
|
164
|
+
const number = useMemo(() => new Intl.NumberFormat(locale), [locale]);
|
|
164
165
|
const format = (value) => (typeof value === 'number' ? number.format(value) : value == null ? '—' : String(value));
|
|
165
166
|
const { props } = node;
|
|
166
167
|
let head;
|
|
@@ -19,8 +19,10 @@ declare const TONE: {
|
|
|
19
19
|
readonly conflict: "bg-kortix-orange/15";
|
|
20
20
|
};
|
|
21
21
|
export type CountBadgeTone = keyof typeof TONE;
|
|
22
|
-
export declare function CountBadge({ count, tone, className, ...props }: {
|
|
22
|
+
export declare function CountBadge({ count, tone, icon, className, ...props }: {
|
|
23
23
|
count: number;
|
|
24
24
|
tone?: CountBadgeTone;
|
|
25
|
+
/** A glyph that names what is counted, drawn before the number. */
|
|
26
|
+
icon?: React.ReactNode;
|
|
25
27
|
} & Omit<React.ComponentProps<'span'>, 'children'>): import("react").JSX.Element;
|
|
26
28
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Badge } from './badge.js';
|
|
3
3
|
import { cn } from '../lib/utils.js';
|
|
4
4
|
/**
|
|
@@ -21,6 +21,6 @@ const TONE = {
|
|
|
21
21
|
reminder: 'bg-kortix-purple/15',
|
|
22
22
|
conflict: 'bg-kortix-orange/15',
|
|
23
23
|
};
|
|
24
|
-
export function CountBadge({ count, tone = 'neutral', className, ...props }) {
|
|
25
|
-
return (
|
|
24
|
+
export function CountBadge({ count, tone = 'neutral', icon, className, ...props }) {
|
|
25
|
+
return (_jsxs(Badge, { variant: "transparent", size: "tabular", className: cn(TONE[tone], 'shrink-0 text-current', icon && 'gap-1', className), ...props, children: [icon, count > 99 ? '99+' : count] }));
|
|
26
26
|
}
|
|
@@ -27,7 +27,7 @@ export function renderGlyphPathsModule() {
|
|
|
27
27
|
const paths = glyphPathsInDefaultWeight();
|
|
28
28
|
const lines = Object.entries(paths).map(([name, d]) => ` ${JSON.stringify(name)}: ${JSON.stringify(d)},`);
|
|
29
29
|
return [
|
|
30
|
-
'// GENERATED by
|
|
30
|
+
'// GENERATED by packages/ui/scripts/generate-glyph-paths.tsx. Do not edit.',
|
|
31
31
|
'// The path of every catalogue glyph in DEFAULT_ICON_WEIGHT; see glyph-face.tsx.',
|
|
32
32
|
'',
|
|
33
33
|
`export const GLYPH_PATHS_WEIGHT = ${JSON.stringify(DEFAULT_ICON_WEIGHT)};`,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// GENERATED by
|
|
1
|
+
// GENERATED by packages/ui/scripts/generate-glyph-paths.tsx. Do not edit.
|
|
2
2
|
// The path of every catalogue glyph in DEFAULT_ICON_WEIGHT; see glyph-face.tsx.
|
|
3
3
|
export const GLYPH_PATHS_WEIGHT = "bold";
|
|
4
4
|
export const GLYPH_PATHS = {
|