@nextclaw/ui 0.14.4 → 0.15.0
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/CHANGELOG.md +54 -0
- package/dist/assets/api-DLtsiBKm.js +13 -0
- package/dist/assets/appearance-settings-page-CtkeYXux.js +1 -0
- package/dist/assets/book-open-BQwWBDKb.js +1 -0
- package/dist/assets/channels-list-page-Cv8o9Hxg.js +8 -0
- package/dist/assets/chat-page-BtA7H1Ea.js +1 -0
- package/dist/assets/{config-split-page-CRhlZ_pH.js → config-split-page-EidRBpP2.js} +1 -1
- package/dist/assets/confirm-dialog-iIJFFp2T.js +5 -0
- package/dist/assets/desktop-update-config-C6n_TB1r.js +1 -0
- package/dist/assets/{dist-CikkhOlq.js → dist-CBxWOazz.js} +1 -1
- package/dist/assets/{dist-PVA_xL1D.js → dist-D2eZC3OR.js} +1 -1
- package/dist/assets/doc-browser-B76XOXmW.js +1 -0
- package/dist/assets/doc-browser-DBnj9UMB.js +1 -0
- package/dist/assets/{doc-browser-context-MTw4EvKd.js → doc-browser-context-Sv-044Cl.js} +1 -1
- package/dist/assets/{ellipsis-y6vDtq4M.js → ellipsis-2uzhypw5.js} +1 -1
- package/dist/assets/es2015-DhQo_oS6.js +41 -0
- package/dist/assets/{external-link-DVJIQf_G.js → external-link-CM5dnUI6.js} +1 -1
- package/dist/assets/host-capabilities-9bB_998Z.js +1 -0
- package/dist/assets/i18n-provider-di1waTGX.js +1 -0
- package/dist/assets/index-CBRP-7-a.css +1 -0
- package/dist/assets/index-Z1fYpbNd.js +104 -0
- package/dist/assets/loader-circle-BjmN5DeJ.js +1 -0
- package/dist/assets/mcp-marketplace-page-C_MVQUbT.js +9 -0
- package/dist/assets/mcp-marketplace-page-CoZIiD6G.js +1 -0
- package/dist/assets/model-config-page-Cdr9P6TQ.js +1 -0
- package/dist/assets/plus-3ztRZD_l.js +1 -0
- package/dist/assets/provider-scoped-model-input-g1WkGiJ8.js +1 -0
- package/dist/assets/providers-config-page-CWlO7J4A.js +1 -0
- package/dist/assets/{react-VNqT5Zp7.js → react-DjpzoZXB.js} +1 -1
- package/dist/assets/refresh-cw-DaTv3zxE.js +1 -0
- package/dist/assets/remote-CKWZfVVB.js +1 -0
- package/dist/assets/rotate-cw-CHcaRUDz.js +1 -0
- package/dist/assets/runtime-config-page-CfTqh1nv.js +1 -0
- package/dist/assets/{save-BOJ477H-.js → save-BZof1uie.js} +1 -1
- package/dist/assets/{search-BEti5beA.js → search-MxAarS_g.js} +1 -1
- package/dist/assets/search-config-page-Dm8daYny.js +1 -0
- package/dist/assets/{secrets-config-page--IzrIOid.js → secrets-config-page-D8AbcR05.js} +2 -2
- package/dist/assets/security-config-Ds0LsJmo.js +1 -0
- package/dist/assets/select-D7GcNP0z.js +1 -0
- package/dist/assets/setting-row-CuozlnJd.js +1 -0
- package/dist/assets/side-dock-BcLau5S_.js +3 -0
- package/dist/assets/skeleton-shfmXuYp.js +1 -0
- package/dist/assets/switch-C1BWMU5c.js +1 -0
- package/dist/assets/tag-chip-DCUrQUCP.js +1 -0
- package/dist/assets/tooltip-DfAHHA76.js +1 -0
- package/dist/assets/use-config-DAdbT5QJ.js +1 -0
- package/dist/assets/use-confirm-dialog-eWcJ6V-s.js +1 -0
- package/dist/assets/x-DqL1mYLC.js +1 -0
- package/dist/index.html +53 -17
- package/index.html +25 -2
- package/package.json +9 -9
- package/src/app/components/layout/__tests__/app-layout.test.tsx +27 -0
- package/src/app/components/layout/__tests__/settings-entry-page.test.tsx +9 -1
- package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +6 -3
- package/src/app/components/layout/app-layout.tsx +10 -3
- package/src/app/components/layout/sidebar-items.tsx +8 -8
- package/src/app/components/layout/sidebar-rail.styles.ts +4 -2
- package/src/app/components/layout/sidebar.tsx +21 -28
- package/src/app/configs/app-navigation.config.ts +11 -5
- package/src/app/index.tsx +8 -0
- package/src/app/managers/__tests__/viewport-layout.manager.test.ts +59 -0
- package/src/app/managers/viewport-layout.manager.ts +39 -0
- package/src/app/presenters/app.presenter.ts +25 -2
- package/src/app/styles/design-system.css +91 -4
- package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +86 -0
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +47 -17
- package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +7 -0
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +9 -5
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +5 -5
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +51 -2
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +170 -1
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +23 -1
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +24 -7
- package/src/features/chat/features/conversation/components/session-queued-input-rows.tsx +96 -0
- package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-controller.test.tsx +181 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +351 -89
- package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +29 -4
- package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +28 -3
- package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -8
- package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +50 -14
- package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +77 -6
- package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +21 -12
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +166 -0
- package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +19 -11
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +38 -64
- package/src/features/chat/features/message/utils/__tests__/chat-message-process-summary.utils.test.ts +59 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message-show-content-tool-card.utils.test.ts +56 -1
- package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +0 -2
- package/src/features/chat/features/message/utils/chat-message-process-summary.utils.ts +80 -0
- package/src/features/chat/features/message/utils/chat-message-show-content-tool-card.utils.ts +12 -3
- package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +60 -0
- package/src/features/chat/features/message/utils/chat-message.utils.ts +3 -0
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime.test.tsx +39 -0
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +2 -2
- package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +3 -1
- package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +1 -1
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +270 -115
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +121 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +75 -20
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +70 -5
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +83 -0
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +79 -1
- package/src/features/chat/managers/__tests__/chat-ui.manager.test.ts +2 -0
- package/src/features/chat/managers/chat-thread.manager.ts +9 -3
- package/src/features/chat/managers/chat-ui.manager.ts +5 -1
- package/src/features/chat/presenters/chat.presenter.ts +2 -1
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +4 -0
- package/src/features/chat/stores/chat-thread.store.ts +6 -64
- package/src/features/chat/types/chat-message.types.ts +4 -0
- package/src/features/marketplace/components/__tests__/marketplace-page-detail.test.tsx +33 -26
- package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +1 -0
- package/src/features/marketplace/components/curated-shelves/__tests__/marketplace-curated-scene-route.test.tsx +1 -0
- package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.tsx +115 -0
- package/src/features/marketplace/components/marketplace-detail-doc.tsx +441 -0
- package/src/features/marketplace/components/mcp/__tests__/mcp-marketplace-page.test.tsx +3 -3
- package/src/features/marketplace/components/mcp/mcp-marketplace-doc.ts +0 -60
- package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +64 -29
- package/src/features/marketplace/hooks/use-marketplace-item-detail.ts +42 -31
- package/src/features/marketplace/index.ts +3 -0
- package/src/features/marketplace/stores/marketplace-detail-doc.store.ts +38 -0
- package/src/features/panel-apps/components/__tests__/panel-apps-list.test.tsx +32 -2
- package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +11 -0
- package/src/features/pwa/managers/pwa-shell-theme.manager.ts +8 -3
- package/src/features/settings/pages/__tests__/appearance-settings-page.test.tsx +25 -0
- package/src/features/settings/pages/appearance-settings-page.tsx +40 -0
- package/src/features/side-dock/components/__tests__/side-dock.test.tsx +44 -2
- package/src/features/side-dock/components/side-dock.tsx +100 -13
- package/src/features/side-dock/configs/side-dock-built-in-items.config.ts +10 -0
- package/src/features/side-dock/managers/__tests__/side-dock.manager.test.ts +32 -2
- package/src/features/side-dock/managers/side-dock.manager.ts +17 -7
- package/src/features/side-dock/stores/side-dock.store.ts +9 -0
- package/src/features/side-dock/types/side-dock.types.ts +10 -3
- package/src/features/side-dock/utils/side-dock-item.utils.ts +15 -2
- package/src/index.css +501 -27
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +1 -1
- package/src/shared/components/doc-browser/__tests__/doc-browser-context.test.tsx +30 -1
- package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +206 -38
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +34 -11
- package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +1 -1
- package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +23 -5
- package/src/shared/components/doc-browser/doc-browser.tsx +128 -75
- package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +9 -1
- package/src/shared/components/doc-browser/utils/doc-browser-floating-panel.utils.ts +83 -0
- package/src/shared/components/ui/__tests__/dialog.test.tsx +27 -0
- package/src/shared/components/ui/actions/icon-action-button.tsx +14 -2
- package/src/shared/components/ui/dialog.tsx +2 -2
- package/src/shared/components/ui/popover.tsx +1 -1
- package/src/shared/components/ui/select.tsx +1 -1
- package/src/shared/components/ui/switch.tsx +4 -3
- package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +54 -0
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +4 -5
- package/src/shared/components/ui/tooltip.tsx +1 -1
- package/src/shared/lib/api/utils/server-path.utils.ts +27 -1
- package/src/shared/lib/i18n/locales/en-US/chat.json +15 -6
- package/src/shared/lib/i18n/locales/en-US/core.json +2 -0
- package/src/shared/lib/i18n/locales/en-US/doc-browser.json +11 -0
- package/src/shared/lib/i18n/locales/en-US/marketplace.json +5 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +15 -6
- package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -0
- package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +11 -0
- package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +5 -0
- package/src/shared/lib/session-project/__tests__/workspace-file-breadcrumb.test.ts +14 -0
- package/src/shared/lib/session-project/index.ts +1 -1
- package/src/shared/lib/session-project/{workspace-file-breadcrumb.ts → workspace-file-breadcrumb.utils.ts} +62 -8
- package/src/shared/lib/theme/index.ts +51 -13
- package/dist/assets/api-BP_8IaxB.js +0 -13
- package/dist/assets/channels-list-page-DEh5Jjqx.js +0 -8
- package/dist/assets/chat-page-BRB0N5WW.js +0 -105
- package/dist/assets/confirm-dialog-DsivFJ4r.js +0 -5
- package/dist/assets/desktop-update-config-B0w9p2ep.js +0 -1
- package/dist/assets/doc-browser-BoNJ7-tE.js +0 -1
- package/dist/assets/doc-browser-CdJ_d4ik.js +0 -1
- package/dist/assets/index-BiE-K8-q.css +0 -1
- package/dist/assets/index-D9BAE_L_.js +0 -105
- package/dist/assets/mcp-marketplace-page-B1SZNY5m.js +0 -40
- package/dist/assets/mcp-marketplace-page-Cq_Tw42T.js +0 -1
- package/dist/assets/model-config-page-DotaTUHb.js +0 -1
- package/dist/assets/plus-BbZqquJe.js +0 -1
- package/dist/assets/provider-scoped-model-input-DsFRuWJH.js +0 -1
- package/dist/assets/providers-config-page-DW4tQSLN.js +0 -1
- package/dist/assets/remote-B8TiUfwW.js +0 -1
- package/dist/assets/rotate-cw-DjHDTkCv.js +0 -1
- package/dist/assets/runtime-config-page-S6ErRHR8.js +0 -1
- package/dist/assets/search-config-page-DGj5am03.js +0 -1
- package/dist/assets/select-hoj8dK4K.js +0 -41
- package/dist/assets/tag-chip-T8-h-die.js +0 -1
- package/dist/assets/use-config-CjRV2hwL.js +0 -1
- package/dist/assets/x-D9PXs_xP.js +0 -1
- package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.ts +0 -40
- package/src/features/marketplace/components/detail-doc/marketplace-detail-doc-renderer.ts +0 -201
- package/src/features/marketplace/components/marketplace-detail-doc.ts +0 -142
- /package/dist/assets/{config-hints-BkYBc7z0.js → config-hints-fGnUjDe9.js} +0 -0
- /package/dist/assets/{middleware-CkFT3R4v.js → middleware-BILWbJE4.js} +0 -0
|
@@ -1,8 +1,36 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { resolveChatInputSurfaceState } from '@nextclaw/agent-chat-ui';
|
|
3
3
|
import { createSlashCommandInputSurfacePlugin } from '@/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils';
|
|
4
|
+
import type { PanelAppEntryView } from '@/shared/lib/api';
|
|
4
5
|
|
|
5
|
-
function
|
|
6
|
+
function createPanelApp(
|
|
7
|
+
overrides: Partial<PanelAppEntryView> & Pick<PanelAppEntryView, 'appId' | 'title'>,
|
|
8
|
+
): PanelAppEntryView {
|
|
9
|
+
return {
|
|
10
|
+
id: overrides.id ?? overrides.appId,
|
|
11
|
+
appId: overrides.appId,
|
|
12
|
+
fileName: overrides.fileName ?? `${overrides.appId}.panel.html`,
|
|
13
|
+
kind: overrides.kind ?? 'single-file',
|
|
14
|
+
title: overrides.title,
|
|
15
|
+
description: overrides.description,
|
|
16
|
+
contentPath: overrides.contentPath ?? `/panels/${overrides.appId}.panel.html`,
|
|
17
|
+
createdAt: overrides.createdAt ?? '2026-06-18T00:00:00.000Z',
|
|
18
|
+
updatedAt: overrides.updatedAt ?? '2026-06-18T00:00:00.000Z',
|
|
19
|
+
sizeBytes: overrides.sizeBytes ?? 100,
|
|
20
|
+
favorite: overrides.favorite ?? false,
|
|
21
|
+
clientDeclared: overrides.clientDeclared ?? false,
|
|
22
|
+
clientGranted: overrides.clientGranted ?? false,
|
|
23
|
+
lastOpenedAt: overrides.lastOpenedAt,
|
|
24
|
+
openCount: overrides.openCount ?? 0,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function createPlugin(
|
|
29
|
+
params: {
|
|
30
|
+
onSelectCommand?: () => void;
|
|
31
|
+
onSelectPanelApp?: (appId: string) => void;
|
|
32
|
+
} = {},
|
|
33
|
+
) {
|
|
6
34
|
return createSlashCommandInputSurfacePlugin({
|
|
7
35
|
commands: [
|
|
8
36
|
{
|
|
@@ -11,19 +39,33 @@ function createPlugin(onSelectCommand = vi.fn()) {
|
|
|
11
39
|
description: 'Open side chat',
|
|
12
40
|
detailLines: ['Creates a child session on first send'],
|
|
13
41
|
keywords: ['side', 'chat'],
|
|
14
|
-
onSelect: onSelectCommand,
|
|
42
|
+
onSelect: params.onSelectCommand ?? vi.fn(),
|
|
15
43
|
},
|
|
16
44
|
],
|
|
17
45
|
itemTexts: {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
46
|
+
panelAppTexts: {
|
|
47
|
+
appIdLabel: 'App ID',
|
|
48
|
+
fileLabel: 'File',
|
|
49
|
+
noDescriptionLabel: 'No panel app description',
|
|
50
|
+
subtitle: 'Panel App',
|
|
51
|
+
},
|
|
52
|
+
skillTexts: {
|
|
53
|
+
noSkillDescription: 'No description',
|
|
54
|
+
slashSkillScopeLabel: 'Scope',
|
|
55
|
+
slashSkillSpecLabel: 'Spec',
|
|
56
|
+
slashSkillSubtitle: 'Skill',
|
|
57
|
+
},
|
|
22
58
|
},
|
|
23
59
|
labels: {
|
|
24
60
|
commandHintLabel: 'Run command',
|
|
25
61
|
commandSectionLabel: 'Commands',
|
|
26
62
|
commandSubtitle: 'Command',
|
|
63
|
+
filterAllLabel: 'All',
|
|
64
|
+
filterCommandsLabel: 'Commands',
|
|
65
|
+
filterPanelAppsLabel: 'Panel Apps',
|
|
66
|
+
filterSkillsLabel: 'Skills',
|
|
67
|
+
panelAppHintLabel: 'Open panel app',
|
|
68
|
+
panelAppSectionLabel: 'Panel Apps',
|
|
27
69
|
skillHintLabel: 'Add skill',
|
|
28
70
|
skillSectionLabel: 'Skills',
|
|
29
71
|
},
|
|
@@ -34,6 +76,7 @@ function createPlugin(onSelectCommand = vi.fn()) {
|
|
|
34
76
|
loadingLabel: 'Loading',
|
|
35
77
|
sectionLabel: 'Slash',
|
|
36
78
|
},
|
|
79
|
+
onSelectPanelApp: params.onSelectPanelApp ?? vi.fn(),
|
|
37
80
|
onSelectSkill: vi.fn(),
|
|
38
81
|
});
|
|
39
82
|
}
|
|
@@ -44,7 +87,13 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
44
87
|
data: {
|
|
45
88
|
isPanelAppsLoading: false,
|
|
46
89
|
isSkillsLoading: false,
|
|
47
|
-
panelApps: [
|
|
90
|
+
panelApps: [
|
|
91
|
+
createPanelApp({
|
|
92
|
+
appId: 'task-board',
|
|
93
|
+
title: 'Task Board',
|
|
94
|
+
description: 'Track tasks',
|
|
95
|
+
}),
|
|
96
|
+
],
|
|
48
97
|
recentSkillValues: [],
|
|
49
98
|
skillRecords: [
|
|
50
99
|
{
|
|
@@ -67,6 +116,13 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
67
116
|
expect(state.panel?.items.map((item) => item.key)).toEqual([
|
|
68
117
|
'command:side-chat',
|
|
69
118
|
'skill:web-search',
|
|
119
|
+
'panel-app-action:task-board',
|
|
120
|
+
]);
|
|
121
|
+
expect(state.panel?.filterOptions).toEqual([
|
|
122
|
+
{ key: 'all', label: 'All' },
|
|
123
|
+
{ key: 'commands', label: 'Commands', sectionKeys: ['commands'] },
|
|
124
|
+
{ key: 'skills', label: 'Skills', sectionKeys: ['skills'] },
|
|
125
|
+
{ key: 'panel-apps', label: 'Panel Apps', sectionKeys: ['panel-apps'] },
|
|
70
126
|
]);
|
|
71
127
|
expect(state.panel?.items[0]).toMatchObject({
|
|
72
128
|
sectionKey: 'commands',
|
|
@@ -78,6 +134,11 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
78
134
|
sectionLabel: 'Skills',
|
|
79
135
|
hintLabel: 'Add skill',
|
|
80
136
|
});
|
|
137
|
+
expect(state.panel?.items[2]).toMatchObject({
|
|
138
|
+
sectionKey: 'panel-apps',
|
|
139
|
+
sectionLabel: 'Panel Apps',
|
|
140
|
+
hintLabel: 'Open panel app',
|
|
141
|
+
});
|
|
81
142
|
});
|
|
82
143
|
|
|
83
144
|
it('runs a command item through the panel selection handler', () => {
|
|
@@ -90,7 +151,7 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
90
151
|
recentSkillValues: [],
|
|
91
152
|
skillRecords: [],
|
|
92
153
|
},
|
|
93
|
-
plugins: [createPlugin(onSelectCommand)],
|
|
154
|
+
plugins: [createPlugin({ onSelectCommand })],
|
|
94
155
|
trigger: {
|
|
95
156
|
key: 'slash',
|
|
96
157
|
marker: '/',
|
|
@@ -105,4 +166,43 @@ describe('createSlashCommandInputSurfacePlugin', () => {
|
|
|
105
166
|
state.panel?.onSelectItem?.(item!);
|
|
106
167
|
expect(onSelectCommand).toHaveBeenCalledTimes(1);
|
|
107
168
|
});
|
|
169
|
+
|
|
170
|
+
it('opens a panel app action without turning it into an input token', () => {
|
|
171
|
+
const onSelectPanelApp = vi.fn();
|
|
172
|
+
const state = resolveChatInputSurfaceState({
|
|
173
|
+
data: {
|
|
174
|
+
isPanelAppsLoading: false,
|
|
175
|
+
isSkillsLoading: false,
|
|
176
|
+
panelApps: [
|
|
177
|
+
createPanelApp({
|
|
178
|
+
appId: 'task-board',
|
|
179
|
+
title: 'Task Board',
|
|
180
|
+
description: 'Track tasks',
|
|
181
|
+
}),
|
|
182
|
+
],
|
|
183
|
+
recentSkillValues: [],
|
|
184
|
+
skillRecords: [],
|
|
185
|
+
},
|
|
186
|
+
plugins: [createPlugin({ onSelectPanelApp })],
|
|
187
|
+
trigger: {
|
|
188
|
+
key: 'slash',
|
|
189
|
+
marker: '/',
|
|
190
|
+
query: 'task',
|
|
191
|
+
start: 0,
|
|
192
|
+
end: 5,
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const item = state.panel?.items[0];
|
|
197
|
+
expect(item).toMatchObject({
|
|
198
|
+
key: 'panel-app-action:task-board',
|
|
199
|
+
title: 'Task Board',
|
|
200
|
+
sectionKey: 'panel-apps',
|
|
201
|
+
});
|
|
202
|
+
expect(item).not.toHaveProperty('tokenKind');
|
|
203
|
+
expect(item).not.toHaveProperty('tokenKey');
|
|
204
|
+
expect(item).not.toHaveProperty('value');
|
|
205
|
+
state.panel?.onSelectItem?.(item!);
|
|
206
|
+
expect(onSelectPanelApp).toHaveBeenCalledWith('task-board');
|
|
207
|
+
});
|
|
108
208
|
});
|
package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts
CHANGED
|
@@ -21,16 +21,17 @@ function getPanelAppActivityTime(entry: PanelAppEntryView): number {
|
|
|
21
21
|
return Date.parse(entry.lastOpenedAt ?? entry.updatedAt) || 0;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
export type PanelAppInputSurfaceItemTexts = {
|
|
25
|
+
appIdLabel: string;
|
|
26
|
+
fileLabel: string;
|
|
27
|
+
noDescriptionLabel: string;
|
|
28
|
+
subtitle: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function resolvePanelAppInputSurfaceEntries(params: {
|
|
25
32
|
entries: readonly PanelAppEntryView[];
|
|
26
33
|
query: string;
|
|
27
|
-
|
|
28
|
-
appIdLabel: string;
|
|
29
|
-
fileLabel: string;
|
|
30
|
-
noDescriptionLabel: string;
|
|
31
|
-
subtitle: string;
|
|
32
|
-
};
|
|
33
|
-
}): ChatInputSurfaceItem[] {
|
|
34
|
+
}): PanelAppEntryView[] {
|
|
34
35
|
const collator = new Intl.Collator(undefined, { sensitivity: 'base', numeric: true });
|
|
35
36
|
return params.entries
|
|
36
37
|
.map((entry, order) => ({
|
|
@@ -67,8 +68,23 @@ function buildPanelAppReferenceItems(params: {
|
|
|
67
68
|
const labelCompare = collator.compare(left.entry.title || left.entry.appId, right.entry.title || right.entry.appId);
|
|
68
69
|
return labelCompare || left.order - right.order;
|
|
69
70
|
})
|
|
70
|
-
.map(({ entry }) =>
|
|
71
|
-
|
|
71
|
+
.map(({ entry }) => entry);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function buildPanelAppInputSurfaceItemEntries(params: {
|
|
75
|
+
entries: readonly PanelAppEntryView[];
|
|
76
|
+
keyPrefix?: string;
|
|
77
|
+
query: string;
|
|
78
|
+
texts: PanelAppInputSurfaceItemTexts;
|
|
79
|
+
}): Array<{ entry: PanelAppEntryView; item: ChatInputSurfaceItem }> {
|
|
80
|
+
const keyPrefix = params.keyPrefix ?? 'panel-app';
|
|
81
|
+
return resolvePanelAppInputSurfaceEntries({
|
|
82
|
+
entries: params.entries,
|
|
83
|
+
query: params.query,
|
|
84
|
+
}).map((entry) => ({
|
|
85
|
+
entry,
|
|
86
|
+
item: {
|
|
87
|
+
key: `${keyPrefix}:${entry.appId}`,
|
|
72
88
|
title: entry.title || entry.appId,
|
|
73
89
|
subtitle: params.texts.subtitle,
|
|
74
90
|
description: (entry.description ?? '').trim() || params.texts.noDescriptionLabel,
|
|
@@ -76,10 +92,30 @@ function buildPanelAppReferenceItems(params: {
|
|
|
76
92
|
`${params.texts.appIdLabel}: ${entry.appId}`,
|
|
77
93
|
`${params.texts.fileLabel}: ${entry.fileName}`,
|
|
78
94
|
],
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
},
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function buildPanelAppInputSurfaceItems(params: {
|
|
100
|
+
entries: readonly PanelAppEntryView[];
|
|
101
|
+
keyPrefix?: string;
|
|
102
|
+
query: string;
|
|
103
|
+
texts: PanelAppInputSurfaceItemTexts;
|
|
104
|
+
}): ChatInputSurfaceItem[] {
|
|
105
|
+
return buildPanelAppInputSurfaceItemEntries(params).map(({ item }) => item);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function buildPanelAppReferenceItems(params: {
|
|
109
|
+
entries: readonly PanelAppEntryView[];
|
|
110
|
+
query: string;
|
|
111
|
+
texts: PanelAppInputSurfaceItemTexts;
|
|
112
|
+
}): ChatInputSurfaceItem[] {
|
|
113
|
+
return buildPanelAppInputSurfaceItemEntries(params).map(({ entry, item }) => ({
|
|
114
|
+
...item,
|
|
115
|
+
tokenKind: 'panel_app',
|
|
116
|
+
tokenKey: entry.appId,
|
|
117
|
+
value: entry.appId,
|
|
118
|
+
}));
|
|
83
119
|
}
|
|
84
120
|
|
|
85
121
|
export function createPanelAppReferenceInputSurfacePlugin(params: {
|
package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts
CHANGED
|
@@ -8,12 +8,18 @@ import {
|
|
|
8
8
|
import { buildChatSlashItems } from '@/features/chat/features/input/utils/chat-input-bar.utils';
|
|
9
9
|
import type { ChatInputProductPluginData } from './chat-input-product-plugin-adapters.types';
|
|
10
10
|
import type { ChatInputBarAdapterTexts } from '@/features/chat/types/chat-input-bar.types';
|
|
11
|
+
import {
|
|
12
|
+
buildPanelAppInputSurfaceItems,
|
|
13
|
+
type PanelAppInputSurfaceItemTexts,
|
|
14
|
+
} from './panel-app-reference-plugin.utils';
|
|
11
15
|
import {
|
|
12
16
|
resolveInputSurfaceMatchTier,
|
|
13
17
|
scoreInputSurfaceSearchCandidate,
|
|
14
18
|
} from './input-surface-search.utils';
|
|
15
19
|
|
|
16
20
|
const SLASH_COMMAND_SECTION_KEY = 'commands';
|
|
21
|
+
const SLASH_PANEL_APP_KEY_PREFIX = 'panel-app-action';
|
|
22
|
+
const SLASH_PANEL_APP_SECTION_KEY = 'panel-apps';
|
|
17
23
|
const SLASH_SKILL_SECTION_KEY = 'skills';
|
|
18
24
|
|
|
19
25
|
export type ChatSlashCommandDescriptor = {
|
|
@@ -103,20 +109,55 @@ function buildSlashSkillItems(params: {
|
|
|
103
109
|
}));
|
|
104
110
|
}
|
|
105
111
|
|
|
112
|
+
function buildSlashPanelAppItems(params: {
|
|
113
|
+
data: ChatInputProductPluginData;
|
|
114
|
+
itemTexts: PanelAppInputSurfaceItemTexts;
|
|
115
|
+
panelAppHintLabel: string;
|
|
116
|
+
panelAppSectionLabel: string;
|
|
117
|
+
query: string;
|
|
118
|
+
}): ChatInputSurfaceItem[] {
|
|
119
|
+
return buildPanelAppInputSurfaceItems({
|
|
120
|
+
entries: params.data.panelApps,
|
|
121
|
+
keyPrefix: SLASH_PANEL_APP_KEY_PREFIX,
|
|
122
|
+
query: params.query,
|
|
123
|
+
texts: params.itemTexts,
|
|
124
|
+
}).map((item) => ({
|
|
125
|
+
...item,
|
|
126
|
+
hintLabel: params.panelAppHintLabel,
|
|
127
|
+
sectionKey: SLASH_PANEL_APP_SECTION_KEY,
|
|
128
|
+
sectionLabel: params.panelAppSectionLabel,
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function resolveSlashPanelAppId(item: ChatInputSurfaceItem): string | null {
|
|
133
|
+
const prefix = `${SLASH_PANEL_APP_KEY_PREFIX}:`;
|
|
134
|
+
return item.key.startsWith(prefix) ? item.key.slice(prefix.length) : null;
|
|
135
|
+
}
|
|
136
|
+
|
|
106
137
|
export function createSlashCommandInputSurfacePlugin(params: {
|
|
107
138
|
commands: readonly ChatSlashCommandDescriptor[];
|
|
108
139
|
menuTexts: ChatInputSurfaceMenuTexts;
|
|
109
|
-
itemTexts:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
140
|
+
itemTexts: {
|
|
141
|
+
panelAppTexts: PanelAppInputSurfaceItemTexts;
|
|
142
|
+
skillTexts: Pick<
|
|
143
|
+
ChatInputBarAdapterTexts,
|
|
144
|
+
'slashSkillSubtitle' | 'slashSkillSpecLabel' | 'slashSkillScopeLabel' | 'noSkillDescription'
|
|
145
|
+
>;
|
|
146
|
+
};
|
|
113
147
|
labels: {
|
|
114
148
|
commandHintLabel: string;
|
|
115
149
|
commandSectionLabel: string;
|
|
116
150
|
commandSubtitle: string;
|
|
151
|
+
filterAllLabel: string;
|
|
152
|
+
filterCommandsLabel: string;
|
|
153
|
+
filterPanelAppsLabel: string;
|
|
154
|
+
filterSkillsLabel: string;
|
|
155
|
+
panelAppHintLabel: string;
|
|
156
|
+
panelAppSectionLabel: string;
|
|
117
157
|
skillHintLabel: string;
|
|
118
158
|
skillSectionLabel: string;
|
|
119
159
|
};
|
|
160
|
+
onSelectPanelApp: (appId: string) => void;
|
|
120
161
|
onSelectSkill?: (skillRef: string) => void;
|
|
121
162
|
}): ChatInputSurfacePlugin<ChatInputProductPluginData> {
|
|
122
163
|
return createInputSurfaceTriggeredPanelPlugin({
|
|
@@ -125,7 +166,25 @@ export function createSlashCommandInputSurfacePlugin(params: {
|
|
|
125
166
|
resolvePanel: (context) => {
|
|
126
167
|
const { data, trigger } = context;
|
|
127
168
|
return {
|
|
128
|
-
|
|
169
|
+
filterOptions: [
|
|
170
|
+
{ key: 'all', label: params.labels.filterAllLabel },
|
|
171
|
+
{
|
|
172
|
+
key: SLASH_COMMAND_SECTION_KEY,
|
|
173
|
+
label: params.labels.filterCommandsLabel,
|
|
174
|
+
sectionKeys: [SLASH_COMMAND_SECTION_KEY],
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
key: SLASH_SKILL_SECTION_KEY,
|
|
178
|
+
label: params.labels.filterSkillsLabel,
|
|
179
|
+
sectionKeys: [SLASH_SKILL_SECTION_KEY],
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
key: SLASH_PANEL_APP_SECTION_KEY,
|
|
183
|
+
label: params.labels.filterPanelAppsLabel,
|
|
184
|
+
sectionKeys: [SLASH_PANEL_APP_SECTION_KEY],
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
isLoading: data.isPanelAppsLoading || data.isSkillsLoading,
|
|
129
188
|
items: [
|
|
130
189
|
...buildSlashCommandItems({
|
|
131
190
|
commands: params.commands,
|
|
@@ -138,17 +197,29 @@ export function createSlashCommandInputSurfacePlugin(params: {
|
|
|
138
197
|
}),
|
|
139
198
|
...buildSlashSkillItems({
|
|
140
199
|
data,
|
|
141
|
-
itemTexts: params.itemTexts,
|
|
200
|
+
itemTexts: params.itemTexts.skillTexts,
|
|
142
201
|
query: trigger.query,
|
|
143
202
|
skillHintLabel: params.labels.skillHintLabel,
|
|
144
203
|
skillSectionLabel: params.labels.skillSectionLabel,
|
|
145
204
|
}),
|
|
205
|
+
...buildSlashPanelAppItems({
|
|
206
|
+
data,
|
|
207
|
+
itemTexts: params.itemTexts.panelAppTexts,
|
|
208
|
+
panelAppHintLabel: params.labels.panelAppHintLabel,
|
|
209
|
+
panelAppSectionLabel: params.labels.panelAppSectionLabel,
|
|
210
|
+
query: trigger.query,
|
|
211
|
+
}),
|
|
146
212
|
],
|
|
147
213
|
onSelectItem: (item) => {
|
|
148
214
|
if (item.key.startsWith('command:')) {
|
|
149
215
|
params.commands.find((command) => item.key === `command:${command.key}`)?.onSelect();
|
|
150
216
|
return;
|
|
151
217
|
}
|
|
218
|
+
const panelAppId = resolveSlashPanelAppId(item);
|
|
219
|
+
if (panelAppId) {
|
|
220
|
+
params.onSelectPanelApp(panelAppId);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
152
223
|
const skillRef = item.tokenKey ?? item.value;
|
|
153
224
|
if (skillRef) {
|
|
154
225
|
params.onSelectSkill?.(skillRef);
|
package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx
CHANGED
|
@@ -52,27 +52,19 @@ beforeEach(() => {
|
|
|
52
52
|
mocks.handleIframeMessage.mockReset();
|
|
53
53
|
mocks.open.mockReset();
|
|
54
54
|
mocks.openTarget.mockReset();
|
|
55
|
+
mocks.panelApps.isLoading = false;
|
|
55
56
|
});
|
|
56
57
|
|
|
57
58
|
it('renders inline panel apps as bounded card-mode iframes with an expand action', () => {
|
|
58
59
|
render(<ChatInlinePanelAppCard panelApp={{
|
|
59
|
-
action: {
|
|
60
|
-
kind: 'show-content',
|
|
61
|
-
label: 'Show content',
|
|
62
|
-
request: {
|
|
63
|
-
placement: 'side_panel',
|
|
64
|
-
target: {
|
|
65
|
-
payload: { appId: 'weather-card' },
|
|
66
|
-
type: 'panel_app',
|
|
67
|
-
},
|
|
68
|
-
title: 'Weather',
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
60
|
appId: 'weather-card',
|
|
72
61
|
title: 'Weather',
|
|
73
62
|
}} />);
|
|
74
63
|
|
|
75
64
|
const iframe = screen.getByTitle('Weather');
|
|
65
|
+
const card = iframe.closest('[data-chat-message-wide-content="true"]');
|
|
66
|
+
expect(card?.className).toContain('w-full');
|
|
67
|
+
expect(card?.className).toContain('max-w-[48rem]');
|
|
76
68
|
expect(iframe.getAttribute('src')).toBe(
|
|
77
69
|
'/api/panel-apps/weather-card/content?nextclawDisplayMode=card&nextclawPlacement=inline',
|
|
78
70
|
);
|
|
@@ -87,3 +79,20 @@ it('renders inline panel apps as bounded card-mode iframes with an expand action
|
|
|
87
79
|
url: '/api/panel-apps/weather-card/content',
|
|
88
80
|
}));
|
|
89
81
|
});
|
|
82
|
+
|
|
83
|
+
it('can render inline panel apps without a side-panel expand action', () => {
|
|
84
|
+
render(
|
|
85
|
+
<ChatInlinePanelAppCard
|
|
86
|
+
panelApp={{
|
|
87
|
+
appId: 'weather-card',
|
|
88
|
+
title: 'Weather',
|
|
89
|
+
}}
|
|
90
|
+
showExpandAction={false}
|
|
91
|
+
/>,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(screen.getByTitle('Weather')).toBeTruthy();
|
|
95
|
+
expect(screen.queryByLabelText('chatPanelCardExpand')).toBeNull();
|
|
96
|
+
expect(mocks.open).not.toHaveBeenCalled();
|
|
97
|
+
expect(mocks.openTarget).not.toHaveBeenCalled();
|
|
98
|
+
});
|
package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { isValidElement } from "react";
|
|
2
3
|
import type { NcpMessage } from "@nextclaw/ncp";
|
|
3
4
|
import { beforeEach, expect, it, vi } from "vitest";
|
|
4
5
|
import { ChatMessageListContainer } from "@/features/chat/features/message/components/chat-message-list.container";
|
|
@@ -7,6 +8,8 @@ const captures = vi.hoisted(() => ({
|
|
|
7
8
|
renders: [] as Array<{
|
|
8
9
|
messages: unknown[];
|
|
9
10
|
onToolAction?: (action: unknown) => void;
|
|
11
|
+
onFileOpen?: (action: unknown) => void;
|
|
12
|
+
renderInlineDisplay?: (display: unknown) => unknown;
|
|
10
13
|
renderPanelAppCard?: (panelApp: unknown) => unknown;
|
|
11
14
|
texts?: Record<string, unknown>;
|
|
12
15
|
}>,
|
|
@@ -22,6 +25,8 @@ vi.mock("@nextclaw/agent-chat-ui", async (importOriginal) => {
|
|
|
22
25
|
ChatMessageList: (props: {
|
|
23
26
|
messages: unknown[];
|
|
24
27
|
onToolAction?: (action: unknown) => void;
|
|
28
|
+
onFileOpen?: (action: unknown) => void;
|
|
29
|
+
renderInlineDisplay?: (display: unknown) => unknown;
|
|
25
30
|
renderPanelAppCard?: (panelApp: unknown) => unknown;
|
|
26
31
|
texts?: Record<string, unknown>;
|
|
27
32
|
}) => {
|
|
@@ -171,6 +176,130 @@ it("adapts persisted inline token metadata into markdown token data", () => {
|
|
|
171
176
|
});
|
|
172
177
|
});
|
|
173
178
|
|
|
179
|
+
it("adds a completed assistant process summary without inventing a duration", () => {
|
|
180
|
+
const userMessage = {
|
|
181
|
+
id: "user-process-request",
|
|
182
|
+
sessionId: "session-1",
|
|
183
|
+
role: "user",
|
|
184
|
+
status: "final",
|
|
185
|
+
timestamp: "2026-03-31T10:00:00.000Z",
|
|
186
|
+
parts: [{ type: "text", text: "please inspect the repo" }],
|
|
187
|
+
} satisfies NcpMessage;
|
|
188
|
+
const assistantMessage = {
|
|
189
|
+
id: "assistant-process-result",
|
|
190
|
+
sessionId: "session-1",
|
|
191
|
+
role: "assistant",
|
|
192
|
+
status: "final",
|
|
193
|
+
timestamp: "2026-03-31T10:01:03.000Z",
|
|
194
|
+
parts: [
|
|
195
|
+
{
|
|
196
|
+
type: "reasoning",
|
|
197
|
+
text: "Inspecting current state.",
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
type: "tool-invocation",
|
|
201
|
+
toolCallId: "tool-1",
|
|
202
|
+
toolName: "exec_command",
|
|
203
|
+
state: "result",
|
|
204
|
+
args: "{\"cmd\":\"git status\"}",
|
|
205
|
+
result: "clean",
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
type: "text",
|
|
209
|
+
text: "Done.",
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
} satisfies NcpMessage;
|
|
213
|
+
|
|
214
|
+
render(
|
|
215
|
+
<ChatMessageListContainer
|
|
216
|
+
messages={[userMessage, assistantMessage]}
|
|
217
|
+
isSending={false}
|
|
218
|
+
/>,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const renderedMessages =
|
|
222
|
+
captures.renders[captures.renders.length - 1]?.messages ?? [];
|
|
223
|
+
expect(renderedMessages[1]).toMatchObject({
|
|
224
|
+
processSummary: {
|
|
225
|
+
label: "chatProcessSummaryProcessed",
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("derives completed assistant process duration from message lifecycle", () => {
|
|
231
|
+
const assistantMessage = {
|
|
232
|
+
id: "assistant-process-result",
|
|
233
|
+
sessionId: "session-1",
|
|
234
|
+
role: "assistant",
|
|
235
|
+
status: "final",
|
|
236
|
+
timestamp: "2026-03-31T10:01:03.000Z",
|
|
237
|
+
lifecycle: {
|
|
238
|
+
startedAt: "2026-03-31T10:00:00.000Z",
|
|
239
|
+
endedAt: "2026-03-31T10:03:51.000Z",
|
|
240
|
+
},
|
|
241
|
+
parts: [
|
|
242
|
+
{
|
|
243
|
+
type: "reasoning",
|
|
244
|
+
text: "Inspecting current state.",
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
type: "tool-invocation",
|
|
248
|
+
toolCallId: "tool-1",
|
|
249
|
+
toolName: "exec_command",
|
|
250
|
+
state: "result",
|
|
251
|
+
args: "{\"cmd\":\"git status\"}",
|
|
252
|
+
result: "clean",
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
type: "text",
|
|
256
|
+
text: "Done.",
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
} satisfies NcpMessage;
|
|
260
|
+
|
|
261
|
+
render(
|
|
262
|
+
<ChatMessageListContainer
|
|
263
|
+
messages={[assistantMessage]}
|
|
264
|
+
isSending={false}
|
|
265
|
+
/>,
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const renderedMessages =
|
|
269
|
+
captures.renders[captures.renders.length - 1]?.messages ?? [];
|
|
270
|
+
expect(renderedMessages[0]).toMatchObject({
|
|
271
|
+
processSummary: {
|
|
272
|
+
label: "chatProcessSummaryProcessed 3m 51s",
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it("wires markdown file link actions to the workspace file preview manager", () => {
|
|
278
|
+
const message = {
|
|
279
|
+
id: "assistant-file-link",
|
|
280
|
+
sessionId: "session-1",
|
|
281
|
+
role: "assistant",
|
|
282
|
+
status: "final",
|
|
283
|
+
timestamp: "2026-03-31T10:00:00.000Z",
|
|
284
|
+
parts: [
|
|
285
|
+
{
|
|
286
|
+
type: "text",
|
|
287
|
+
text: "[particle-cosmos.html](/Users/peiwang/Downloads/particle-cosmos.html)",
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
} satisfies NcpMessage;
|
|
291
|
+
const action = {
|
|
292
|
+
path: "/Users/peiwang/Downloads/particle-cosmos.html",
|
|
293
|
+
label: "particle-cosmos.html",
|
|
294
|
+
viewMode: "preview",
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
render(<ChatMessageListContainer messages={[message]} isSending={false} />);
|
|
298
|
+
captures.renders[captures.renders.length - 1]?.onFileOpen?.(action);
|
|
299
|
+
|
|
300
|
+
expect(captures.openFilePreview).toHaveBeenCalledWith(action);
|
|
301
|
+
});
|
|
302
|
+
|
|
174
303
|
it("renders context inheritance as a divider without repeating inherited messages", () => {
|
|
175
304
|
const inheritedMessage = {
|
|
176
305
|
id: "child-session:inherited:1",
|
|
@@ -294,6 +423,43 @@ it("passes the inline panel app renderer to the shared chat UI", () => {
|
|
|
294
423
|
expect(captures.renders[captures.renders.length - 1]?.renderPanelAppCard).toEqual(expect.any(Function));
|
|
295
424
|
});
|
|
296
425
|
|
|
426
|
+
it("passes the inline display renderer to the shared chat UI", () => {
|
|
427
|
+
render(<ChatMessageListContainer messages={[]} isSending={false} />);
|
|
428
|
+
|
|
429
|
+
const renderInlineDisplay =
|
|
430
|
+
captures.renders[captures.renders.length - 1]?.renderInlineDisplay;
|
|
431
|
+
|
|
432
|
+
expect(renderInlineDisplay).toEqual(expect.any(Function));
|
|
433
|
+
expect(
|
|
434
|
+
renderInlineDisplay?.({
|
|
435
|
+
target: {
|
|
436
|
+
type: "file",
|
|
437
|
+
payload: { path: "README.md" },
|
|
438
|
+
},
|
|
439
|
+
title: "README",
|
|
440
|
+
}),
|
|
441
|
+
).toBeUndefined();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it("keeps inline panel app displays expandable from the card header", () => {
|
|
445
|
+
render(<ChatMessageListContainer messages={[]} isSending={false} />);
|
|
446
|
+
|
|
447
|
+
const renderInlineDisplay =
|
|
448
|
+
captures.renders[captures.renders.length - 1]?.renderInlineDisplay;
|
|
449
|
+
const rendered = renderInlineDisplay?.({
|
|
450
|
+
target: {
|
|
451
|
+
type: "panel_app",
|
|
452
|
+
payload: { appId: "weather-card" },
|
|
453
|
+
},
|
|
454
|
+
title: "Weather",
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
if (!isValidElement<{ showExpandAction?: boolean }>(rendered)) {
|
|
458
|
+
throw new Error("Expected inline panel app renderer to return a React element");
|
|
459
|
+
}
|
|
460
|
+
expect(rendered.props.showExpandAction).toBeUndefined();
|
|
461
|
+
});
|
|
462
|
+
|
|
297
463
|
it("renders context compaction as an in-flow divider instead of a chat message", () => {
|
|
298
464
|
const beforeMessage = {
|
|
299
465
|
id: "message-before",
|