@talgent/workbench-ui 0.0.0 → 0.2.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/README.md +15 -2
- package/assets/brand-icons/LICENSE.md +30 -0
- package/assets/brand-icons/README.md +5 -0
- package/assets/brand-icons/figma.svg +1 -0
- package/assets/brand-icons/github.svg +1 -0
- package/assets/brand-icons/linear.svg +1 -0
- package/assets/brand-icons/notion.svg +1 -0
- package/assets/brand-icons/sentry.svg +1 -0
- package/assets/brand-icons/slack.svg +1 -0
- package/assets/fonts/Inter-OFL.txt +93 -0
- package/assets/fonts/inter-latin.woff2 +0 -0
- package/components/agent/agent-icon.css +14 -0
- package/components/agent/agent-icon.tsx +14 -0
- package/components/analytics/chart-geometry.ts +95 -0
- package/components/analytics/dashboard-charts.tsx +136 -0
- package/components/analytics/model-usage-chart.tsx +84 -0
- package/components/analytics/token-breakdown.css +13 -0
- package/components/analytics/token-breakdown.tsx +23 -0
- package/components/analytics/token-report.tsx +57 -0
- package/components/analytics/work-usage/turn-timeline.tsx +98 -0
- package/components/analytics/work-usage/turns.ts +170 -0
- package/components/analytics/work-usage/work-status.ts +36 -0
- package/components/analytics/work-usage/work-usage-reader.tsx +122 -0
- package/components/analytics/work-usage/work-usage.css +48 -0
- package/components/auth/auth-gate.tsx +44 -0
- package/components/chat/message-input.tsx +105 -0
- package/components/composer/permission-mode-list.tsx +15 -0
- package/components/composer/prompt-bar-controls.tsx +77 -0
- package/components/composer/prompt-bar.tsx +45 -0
- package/components/composer/skill-completion.css +27 -0
- package/components/composer/skill-completion.tsx +49 -0
- package/components/composer/use-slash-completion.tsx +69 -0
- package/components/composer/work-start.css +133 -0
- package/components/feedback/toast-region.css +2 -0
- package/components/feedback/toast-region.tsx +15 -0
- package/components/files/file-facts.tsx +16 -0
- package/components/files/file-section.tsx +181 -0
- package/components/markdown.tsx +144 -0
- package/components/marketplace/connector-pages.tsx +166 -0
- package/components/marketplace/marketplace-icons.tsx +66 -0
- package/components/marketplace/marketplace-shared.tsx +43 -0
- package/components/marketplace/marketplace-shell.tsx +32 -0
- package/components/marketplace/marketplace.css +97 -0
- package/components/marketplace/parameter-fields.tsx +46 -0
- package/components/marketplace/plugin-list.tsx +84 -0
- package/components/marketplace/plugin-page.tsx +133 -0
- package/components/marketplace/provider-pages.tsx +120 -0
- package/components/preview/file-format.ts +75 -0
- package/components/preview/file-preview.tsx +153 -0
- package/components/preview/preview-verdict.ts +44 -0
- package/components/preview/preview.css +19 -0
- package/components/preview/text-preview.tsx +174 -0
- package/components/profile/provider-callback.tsx +43 -0
- package/components/profile/work-agent-appearance.tsx +109 -0
- package/components/profile/work-agent-soul.tsx +76 -0
- package/components/rail/rail-section.tsx +20 -0
- package/components/rail/rail.css +19 -0
- package/components/rail/use-expand-on-change.ts +18 -0
- package/components/session/startup-progress.css +77 -0
- package/components/session/startup-progress.tsx +103 -0
- package/components/session/work-permission-control.tsx +45 -0
- package/components/session/work-thinking-control.tsx +52 -0
- package/components/shell/account-menu.tsx +134 -0
- package/components/shell/app-shell.tsx +148 -0
- package/components/shell/core-reading-views.tsx +26 -0
- package/components/shell/core-reading.tsx +17 -0
- package/components/shell/document-title.tsx +11 -0
- package/components/shell/entry-shell.ts +39 -0
- package/components/shell/file-reading.tsx +22 -0
- package/components/shell/page-layout.tsx +7 -0
- package/components/shell/reading-context.ts +41 -0
- package/components/shell/reading-panel.css +67 -0
- package/components/shell/reading-panel.tsx +74 -0
- package/components/shell/sidebar-parts.tsx +38 -0
- package/components/work/context-rail.css +61 -0
- package/components/work/conversation-unavailable.css +6 -0
- package/components/work/conversation-unavailable.tsx +11 -0
- package/components/work/subagent-item.tsx +42 -0
- package/components/work/subagents-section.css +31 -0
- package/components/work/subagents-section.tsx +40 -0
- package/components/work/token-usage.tsx +28 -0
- package/components/work/tool-message-detail.tsx +39 -0
- package/components/work/tool-output-preview.tsx +52 -0
- package/components/work/tool-preview-reading.tsx +27 -0
- package/components/work/tool-reading-content.tsx +31 -0
- package/components/work/work-context-rail.tsx +23 -0
- package/components/work/work-conversation.tsx +207 -0
- package/components/work/work-history.tsx +115 -0
- package/components/work/work-interaction.css +38 -0
- package/components/work/work-interaction.tsx +105 -0
- package/components/work/work-message-flow.css +86 -0
- package/components/work/work-message-flow.tsx +352 -0
- package/components/work/work-progress-section.tsx +20 -0
- package/components/work/work-prompt-bar.tsx +82 -0
- package/components/work/work-question-record.tsx +72 -0
- package/components/work/work-snapshot-state.tsx +24 -0
- package/components/work/work-status-control.tsx +54 -0
- package/components/work-start/start-work-launch-state.ts +33 -0
- package/components/work-start/work-start-catalog.tsx +116 -0
- package/components/work-start/work-start-form.tsx +173 -0
- package/components/work-start/work-start-tray.css +40 -0
- package/components/work-start/work-start-tray.tsx +128 -0
- package/hooks/use-debounced.ts +11 -0
- package/hooks/use-work-conversation.ts +125 -0
- package/hooks/use-work-lifecycle-controls.ts +40 -0
- package/hooks/use-work-stream.ts +91 -0
- package/lib/account-service.ts +77 -0
- package/lib/agent-appearance-service.ts +59 -0
- package/lib/agent-icon.ts +26 -0
- package/lib/agent-soul-service.ts +48 -0
- package/lib/agent-work-service.ts +23 -0
- package/lib/client.ts +20 -0
- package/lib/computer-release.ts +30 -0
- package/lib/computer-service.ts +241 -0
- package/lib/dashboard-report.ts +38 -0
- package/lib/dashboard.ts +21 -0
- package/lib/marketplace-catalog.ts +138 -0
- package/lib/marketplace-connections.ts +98 -0
- package/lib/marketplace-plugins.ts +53 -0
- package/lib/marketplace-routes.ts +12 -0
- package/lib/model-vendors.ts +13 -0
- package/lib/name-taken-error.ts +7 -0
- package/lib/notifications.ts +37 -0
- package/lib/permission-modes.ts +25 -0
- package/lib/personal-usage-service.ts +19 -0
- package/lib/personal-work-service.ts +159 -0
- package/lib/plugin-parameters.ts +64 -0
- package/lib/preview-verdict.ts +26 -0
- package/lib/relative-time.ts +11 -0
- package/lib/session.ts +108 -0
- package/lib/share-link.ts +4 -0
- package/lib/stale-build-reload.ts +41 -0
- package/lib/start-work-request.ts +34 -0
- package/lib/startup-progress.ts +9 -0
- package/lib/subagents.ts +12 -0
- package/lib/thinking-levels.ts +2 -0
- package/lib/token-analytics.ts +168 -0
- package/lib/token-usage.ts +64 -0
- package/lib/trace-view-model.ts +292 -0
- package/lib/transport.ts +29 -0
- package/lib/use-now.ts +11 -0
- package/lib/use-work-trace.ts +175 -0
- package/lib/user-display.ts +49 -0
- package/lib/work-conversation.ts +118 -0
- package/lib/work-file-service.ts +118 -0
- package/lib/work-model.ts +38 -0
- package/lib/work-notices.ts +23 -0
- package/lib/work-progress.ts +41 -0
- package/lib/work-projection.ts +122 -0
- package/lib/work-questions.ts +18 -0
- package/lib/work-start-draft.ts +61 -0
- package/lib/work-start-loadout.ts +100 -0
- package/lib/work-start-source.ts +16 -0
- package/lib/work-state-presentation.ts +80 -0
- package/lib/work-stream-source.ts +32 -0
- package/package.json +81 -5
- package/pages/computers/computers-page.tsx +56 -0
- package/pages/computers/connect-computer-page.tsx +92 -0
- package/pages/computers/install-commands.tsx +42 -0
- package/pages/computers/remove-computer-dialog.tsx +33 -0
- package/pages/computers/rename-computer-dialog.tsx +37 -0
- package/pages/computers/room-rules-dialog.tsx +55 -0
- package/pages/login-page.css +33 -0
- package/pages/login-page.tsx +88 -0
- package/pages/login-threads.tsx +51 -0
- package/pages/marketplace-page.tsx +59 -0
- package/pages/profile-page.tsx +68 -0
- package/pages/workbench/library-page.tsx +88 -0
- package/pages/workbench/new-work-page.tsx +24 -0
- package/pages/workbench/person.tsx +13 -0
- package/pages/workbench/personal-work-context.tsx +93 -0
- package/pages/workbench/personal-work-page.tsx +100 -0
- package/pages/workbench/personal-works-page.tsx +36 -0
- package/pages/workbench/share-dialog.tsx +57 -0
- package/pages/workbench/title-dialog.tsx +35 -0
- package/pages/workbench/usage-page.tsx +52 -0
- package/public/assets/brand/talgent-lettering.png +0 -0
- package/public/assets/brand/talgent-logo-page-bg.png +0 -0
- package/public/assets/brand/talgent-logo-white-bg.png +0 -0
- package/public/assets/brand/talgent-mark-page-bg.png +0 -0
- package/public/assets/brand/talgent-mark-white-bg.png +0 -0
- package/public/assets/brand/talgent-mark.png +0 -0
- package/public/assets/product-icons/LICENSE +21 -0
- package/public/assets/product-icons/README.md +13 -0
- package/public/assets/product-icons/anthropic.svg +1 -0
- package/public/assets/product-icons/deepseek.svg +1 -0
- package/public/assets/product-icons/glm.svg +1 -0
- package/public/assets/product-icons/kimi.svg +1 -0
- package/public/assets/product-icons/linear.svg +1 -0
- package/public/assets/product-icons/minimax.svg +1 -0
- package/public/assets/product-icons/notion.svg +1 -0
- package/public/assets/product-icons/openai.svg +1 -0
- package/public/assets/product-icons/qwen.svg +1 -0
- package/styles/base.css +95 -0
- package/styles/fonts.css +14 -0
- package/styles/markdown.css +79 -0
- package/styles/uber-theme.css +96 -0
- package/styles/work-composer.css +222 -0
- package/styles/work-stream.css +343 -0
- package/styles/workbench.css +144 -0
- package/styles/workspace.css +260 -0
- package/test-support/backend.ts +67 -0
- package/test-support/plugin-summary.ts +8 -0
- package/test-support/setup.ts +59 -0
- package/vite-preset.ts +85 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/* Composition only. Component appearance comes from HeroUI and the official Theme Builder export. */
|
|
2
|
+
.p-app { min-height: 100dvh; display: grid; grid-template-columns: auto minmax(0,1fr); }
|
|
3
|
+
/* Never taller than the window: pages that size the sidebar to their grid row
|
|
4
|
+
(the Work page) would otherwise let a long Recent list push the account
|
|
5
|
+
footer off screen; the Recent list scrolls instead. */
|
|
6
|
+
.p-sidebar { position: sticky; top: 0; width: 252px; height: 100dvh; max-height: 100dvh; padding: 8px 0 8px 8px; }
|
|
7
|
+
.p-sidebar[data-collapsed="true"] { width: 72px; }
|
|
8
|
+
/* The shared nav and account footer sit tight against Recent; the header keeps
|
|
9
|
+
its wider break from the navigation through its own margin. */
|
|
10
|
+
.p-sidebar-shell { display: flex; flex-direction: column; height: 100%; padding: 12px 12px 4px; gap: 8px; }
|
|
11
|
+
.p-sidebar[data-collapsed="true"] .p-sidebar-shell { align-items: center; padding: 12px 8px 4px; gap: 8px; }
|
|
12
|
+
.p-sidebar-header { display: flex; flex-direction: column; gap: 16px; flex-shrink: 0; width: 100%; margin-bottom: 8px; }
|
|
13
|
+
.p-sidebar-scroll { display: flex; flex-direction: column; gap: 16px; flex: 1; min-height: 0; width: 100%; overflow-y: auto; }
|
|
14
|
+
.p-sidebar-scroll>* { flex-shrink: 0; }
|
|
15
|
+
.p-sidebar[data-collapsed="true"] .p-sidebar-header,.p-sidebar[data-collapsed="true"] .p-sidebar-scroll { align-items: center; gap: 12px; }
|
|
16
|
+
.p-sidebar[data-collapsed="true"] .p-sidebar-header { margin-bottom: 4px; }
|
|
17
|
+
.p-sidebar-brand { display: flex; align-items: center; justify-content: space-between; gap: 4px; flex-shrink: 0; }
|
|
18
|
+
.p-brand-lockup { display: flex; align-items: center; gap: 0; }
|
|
19
|
+
.p-brand-button { width: 40px; height: 40px; padding: 0; flex-shrink: 0; }
|
|
20
|
+
.p-brand-icon { display: block; width: 32px; height: 32px; flex-shrink: 0; }
|
|
21
|
+
.p-brand-name { width: 84px; height: 40px; margin-inline-start: -15px; }
|
|
22
|
+
/* Avatar centred on the nav icon column and name on the nav label column. */
|
|
23
|
+
.p-sidebar-account { width: 100%; justify-content: start; padding-inline-start: 6px; gap: 10px; }
|
|
24
|
+
.p-sidebar-account-name { flex: 1; min-width: 0; text-align: start; }
|
|
25
|
+
.p-sidebar-menu { width: 248px; max-width: calc(100vw - 24px); }
|
|
26
|
+
.p-sidebar-nav { display: flex; flex-direction: column; gap: 4px; }
|
|
27
|
+
.p-sidebar-nav>* { flex-shrink: 0; }
|
|
28
|
+
.p-nav-item { justify-content: start; width: 100%; gap: 16px; }
|
|
29
|
+
.p-nav-label { flex: 1; display: flex; }
|
|
30
|
+
.p-sidebar-footer { width: 100%; display: flex; flex-direction: column; gap: 4px; margin-top: auto; flex-shrink: 0; }
|
|
31
|
+
.p-sidebar[data-collapsed="true"] .p-sidebar-footer { align-items: center; }
|
|
32
|
+
|
|
33
|
+
.p-session-label { flex: 1; min-width: 0; }
|
|
34
|
+
.p-session-title { display: block; }
|
|
35
|
+
.p-session-list { gap: 2px; }
|
|
36
|
+
.p-session-item>.chip { color: var(--muted); background: transparent; }
|
|
37
|
+
/* A Work waiting on the person's answer is the one row that asks for them. */
|
|
38
|
+
.p-session-item[data-waiting]>.chip { color: var(--warning); }
|
|
39
|
+
.p-session-item { height: 36px; gap: 6px; padding-inline-end: 8px; }
|
|
40
|
+
.p-session-item[data-actions-visible="true"],.p-drawer-navigation .p-session-item { padding-inline-end: 40px; }
|
|
41
|
+
.p-session-action[data-visible="false"]>button { display: none; }
|
|
42
|
+
.p-drawer-navigation .p-session-action[data-visible="false"]>button { display: inline-flex; }
|
|
43
|
+
@media (pointer: coarse) {
|
|
44
|
+
.p-session-action[data-visible="false"]>button { display: inline-flex; }
|
|
45
|
+
.p-session-item { padding-inline-end: 40px; }
|
|
46
|
+
}
|
|
47
|
+
.p-session-rows { position: relative; }
|
|
48
|
+
.p-session-actions { position: absolute; inset-block-start: 4px; inset-inline-end: 8px; display: flex; flex-direction: column; gap: 2px; }
|
|
49
|
+
.p-session-action { display: flex; align-items: center; height: 36px; }
|
|
50
|
+
.p-more-navigation { display: flex; flex-direction: column; gap: 4px; padding-block: 4px; padding-inline: 16px 0; }
|
|
51
|
+
.p-session-tooltip { max-width: min(280px,calc(100vw - 24px)); }
|
|
52
|
+
.p-sessions-popover { width: 304px; max-width: calc(100vw - 24px); }
|
|
53
|
+
.p-sidebar-empty { padding: 8px; }
|
|
54
|
+
.p-sidebar-drawer { width: 288px; max-width: calc(100vw - 24px); }
|
|
55
|
+
.p-drawer-navigation { display: flex; flex-direction: column; gap: 16px; height: 100%; min-height: 0; }
|
|
56
|
+
.p-body,.p-grow { min-width: 0; }
|
|
57
|
+
.p-grow { flex: 1; display: grid; overflow-wrap: anywhere; }
|
|
58
|
+
.p-topbar { position: sticky; top: 0; z-index: 20; background: var(--background); min-height: var(--topbar-height); display: grid; grid-template-columns: minmax(0,1fr) auto; align-items: center; gap: 16px; padding: 4px 24px 4px 4px; }
|
|
59
|
+
.p-location { flex-wrap: wrap; min-width: 0; }
|
|
60
|
+
.p-page-actions { margin-inline-start: auto; }
|
|
61
|
+
.p-breadcrumb,.p-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; min-width: 0; }
|
|
62
|
+
.p-mobile-toggle { display: none; }
|
|
63
|
+
.p-page { width: 100%; margin-inline: auto; padding: 16px 24px 32px 4px; }
|
|
64
|
+
html:has(.p-app) { scroll-padding-top: 72px; }
|
|
65
|
+
@media (max-width: 740px) { html:has(.p-app) { scroll-padding-top: 120px; } }
|
|
66
|
+
.p-setup-heading { display: grid; gap: 6px; }
|
|
67
|
+
.p-caption { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
|
|
68
|
+
.p-spaced { margin-top: 24px; }
|
|
69
|
+
.p-spaced-bottom { margin-bottom: 24px; }
|
|
70
|
+
.p-work-heading { padding: 16px 24px; display: grid; gap: 12px; }
|
|
71
|
+
.p-title-row { display: flex; align-items: baseline; gap: 12px; }
|
|
72
|
+
.p-work-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
|
|
73
|
+
.p-work-layout { display: grid; grid-template-columns: minmax(0, 1fr); min-height: calc(100dvh - 282px); }
|
|
74
|
+
.p-work-layout.p-show-context { grid-template-columns: minmax(0, 1fr) 280px; }
|
|
75
|
+
.p-work-main { min-width: 0; display: flex; flex-direction: column; }
|
|
76
|
+
.p-start-stage { width: 100%; max-width: 740px; margin-inline: auto; padding: 24px; display: flex; flex-direction: column; gap: 20px; }
|
|
77
|
+
.p-start-description { max-width: 440px; }
|
|
78
|
+
.p-start-composer { width: 100%; }
|
|
79
|
+
.p-config-buttons { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
|
|
80
|
+
.p-composer-bottom { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 12px; align-items: center; margin-top: 16px; }
|
|
81
|
+
.p-error:empty { display: none; }
|
|
82
|
+
.p-check-list { display: grid; gap: 12px; }
|
|
83
|
+
.p-check-list li { display: flex; align-items: flex-start; gap: 8px; }
|
|
84
|
+
.p-start-symbol { display: grid; place-items: center; }
|
|
85
|
+
.p-stepper { width: 100%; max-width: 440px; display: grid; gap: 16px; margin-block: 8px; }
|
|
86
|
+
.p-stepper li { display: flex; gap: 12px; align-items: center; justify-content: space-between; }
|
|
87
|
+
.p-preparation { align-items: center; padding-block: 32px; }
|
|
88
|
+
.p-conversation { padding: 24px; max-width: 860px; width: 100%; margin-inline: auto; display: grid; gap: 24px; flex: 1; }
|
|
89
|
+
.p-message-author { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
90
|
+
.p-message-author time { margin-inline-start: auto; }
|
|
91
|
+
.p-human-message { margin-inline-start: 24px; overflow-wrap: anywhere; }
|
|
92
|
+
.p-agent-message { display: grid; gap: 16px; }
|
|
93
|
+
.p-tool-summary,.p-live-status { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
|
|
94
|
+
.p-code-note { margin-block: 16px; overflow-wrap: anywhere; }
|
|
95
|
+
.p-reply-zone { padding: 0 24px 24px; }
|
|
96
|
+
.p-reply-composer { max-width: 780px; margin-inline: auto; }
|
|
97
|
+
.p-resume-bar { margin-block: 16px; }
|
|
98
|
+
.p-section { margin-block: 24px; }
|
|
99
|
+
.p-section-title { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 16px; margin-bottom: 16px; }
|
|
100
|
+
.p-activity-line { display: flex; align-items: start; gap: 12px; margin-block: 20px; }
|
|
101
|
+
.p-activity-line>div { display: grid; gap: 8px; }
|
|
102
|
+
.p-document-link { margin-top: 20px; }
|
|
103
|
+
.p-document-link>span { display: grid; gap: 4px; }
|
|
104
|
+
.p-list-toolbar { display: flex; align-items: end; flex-wrap: wrap; gap: 16px; margin-bottom: 20px; }
|
|
105
|
+
.p-search { flex: 1; min-width: 180px; }
|
|
106
|
+
.p-picker { min-width: 0; }
|
|
107
|
+
.p-list-toolbar>.p-picker { min-width: 165px; }
|
|
108
|
+
.p-empty { padding: 48px 20px; display: flex; flex-direction: column; align-items: center; gap: 16px; }
|
|
109
|
+
.p-detail-grid { display: grid; grid-template-columns: minmax(0,1fr) 256px; gap: 20px; }
|
|
110
|
+
.p-prose { max-width: 68ch; display: grid; gap: 16px; }
|
|
111
|
+
.p-prose ol { padding-inline-start: 24px; }
|
|
112
|
+
.p-detail-properties { display: flex; flex-direction: column; align-items: start; gap: 20px; }
|
|
113
|
+
.p-settings form { max-width: 660px; }
|
|
114
|
+
.p-settings section { display: grid; gap: 20px; margin-bottom: 24px; }
|
|
115
|
+
.p-connection { max-width: 660px; }
|
|
116
|
+
.p-connection>span { display: grid; gap: 4px; }
|
|
117
|
+
.p-plugin-grid { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 24px; }
|
|
118
|
+
.p-feedback { position: fixed; z-index: 45; bottom: 16px; left: 50%; transform: translateX(-50%); width: min(620px,calc(100vw - 32px)); }
|
|
119
|
+
.p-stack { display: flex; flex-direction: column; gap: 20px; }
|
|
120
|
+
.p-state-json { white-space: pre-wrap; overflow-wrap: anywhere; max-height: 400px; overflow: auto; }
|
|
121
|
+
.p-trace-event { display: flex; gap: 12px; padding: 12px 0; }
|
|
122
|
+
.p-trace-event small { margin-inline-start: auto; }
|
|
123
|
+
.p-session-history { display: flex; flex-direction: column; gap: 4px; }
|
|
124
|
+
.p-history-heading { display: flex; align-items: center; min-height: 32px; padding-inline: 12px; }
|
|
125
|
+
|
|
126
|
+
/* Analytics marks carry data colors in SVG. Containers use unmodified HeroUI cards. */
|
|
127
|
+
.p-analytics { display: flex; flex-direction: column; gap: 16px; }
|
|
128
|
+
.p-analytics>.p-section-title { margin-bottom: 0; }
|
|
129
|
+
.p-metric-grid { display: grid; grid-template-columns: repeat(4,minmax(0,1fr)); gap: 12px; }
|
|
130
|
+
.p-token-metrics { grid-template-columns: repeat(5,minmax(0,1fr)); }
|
|
131
|
+
.p-metric-label { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
132
|
+
.p-distribution-grid,.p-ranking-grid { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 16px; }
|
|
133
|
+
.p-trace-charts { display: grid; grid-template-columns: minmax(0,1fr); gap: 16px; }
|
|
134
|
+
.p-trace-distributions { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 16px; }
|
|
135
|
+
.p-plot-card { min-width: 0; }
|
|
136
|
+
.p-plot-card .p-section-title { margin-bottom: 0; }
|
|
137
|
+
.p-chart-legend { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
138
|
+
.p-time-plot { position: relative; height: 190px; margin-left: 42px; }
|
|
139
|
+
.p-time-plot>svg { width: 100%; height: 100%; overflow: visible; }
|
|
140
|
+
.p-y-axis { position: absolute; left: -42px; top: 0; width: 36px; height: 100%; }
|
|
141
|
+
.p-y-axis>span { position: absolute; left: 0; transform: translateY(-50%); }
|
|
142
|
+
.p-x-axis { position: relative; height: 32px; margin: 0 0 12px 42px; }
|
|
143
|
+
.p-x-axis>p>span { display: block; }
|
|
144
|
+
.p-x-axis>p { position: absolute; transform: translateX(-50%); white-space: nowrap; }
|
|
145
|
+
.p-chart-empty { display: grid; place-items: center; height: 190px; }
|
|
146
|
+
.p-distribution { display: flex; align-items: center; justify-content: center; padding: 16px 0; }
|
|
147
|
+
.p-donut { position: relative; width: 152px; height: 152px; flex-shrink: 0; }
|
|
148
|
+
.p-donut>svg { width: 100%; height: 100%; }
|
|
149
|
+
.p-donut-total { pointer-events: none; position: absolute; inset: 0; display: grid; place-items: center; }
|
|
150
|
+
.p-ranking { display: grid; grid-template-columns: minmax(0,1fr); gap: 16px; }
|
|
151
|
+
.p-ranking li { display: flex; align-items: center; gap: 8px; }
|
|
152
|
+
.p-ranking li>p:last-child { margin-inline-start: auto; flex-shrink: 0; }
|
|
153
|
+
.p-ranking li>p:not(:last-child) { min-width: 0; flex: 1; }
|
|
154
|
+
.p-card-meta { gap: 8px; flex-wrap: wrap; }
|
|
155
|
+
.p-card-meta>span:last-child { margin-inline-start: auto; }
|
|
156
|
+
.p-detail-properties { align-self: start; }
|
|
157
|
+
.p-settings form { display: grid; gap: 20px; }
|
|
158
|
+
.p-form-fields { display: grid; gap: 20px; }
|
|
159
|
+
.p-connection-row { display: flex; align-items: center; flex-wrap: wrap; gap: 16px; }
|
|
160
|
+
.p-connection-row>span { display: grid; gap: 4px; }
|
|
161
|
+
|
|
162
|
+
@media (max-width: 1200px) {
|
|
163
|
+
.p-plugin-grid { grid-template-columns: 1fr; }
|
|
164
|
+
}
|
|
165
|
+
@media (max-width: 1000px) {
|
|
166
|
+
.p-work-layout.p-show-context { grid-template-columns: minmax(0,1fr); }
|
|
167
|
+
.p-detail-grid { grid-template-columns: minmax(0,1fr); }
|
|
168
|
+
.p-overview-grid,.p-lower-grid { grid-template-columns: 1fr; }
|
|
169
|
+
}
|
|
170
|
+
@media (max-width: 740px) {
|
|
171
|
+
.p-app { display: block; }
|
|
172
|
+
.p-sidebar { display: none; }
|
|
173
|
+
.p-mobile-toggle { display: inline-flex; }
|
|
174
|
+
.p-topbar { padding: 12px; gap: 8px; }
|
|
175
|
+
.p-breadcrumb { gap: 4px; flex-wrap: nowrap; }
|
|
176
|
+
.p-page { padding: 20px 16px; }
|
|
177
|
+
.p-work-heading { padding: 16px; }
|
|
178
|
+
.p-title-row { align-items: start; flex-direction: column; gap: 4px; }
|
|
179
|
+
.p-start-stage,.p-conversation { padding: 20px 16px; }
|
|
180
|
+
.p-reply-zone { padding-inline: 16px; }
|
|
181
|
+
.p-human-message { margin-inline-start: 0; }
|
|
182
|
+
.p-chart { grid-template-columns: 1fr; }
|
|
183
|
+
.p-feedback { bottom: 16px; }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@media (max-width: 1200px) {
|
|
187
|
+
.p-distribution-grid,.p-trace-distributions { grid-template-columns: repeat(2,minmax(0,1fr)); }
|
|
188
|
+
.p-ranking-grid { grid-template-columns: minmax(0,1fr); }
|
|
189
|
+
}
|
|
190
|
+
@media (max-width: 1000px) {
|
|
191
|
+
.p-trace-charts { grid-template-columns: 1fr; }
|
|
192
|
+
.p-metric-grid { grid-template-columns: repeat(2,minmax(0,1fr)); }
|
|
193
|
+
}
|
|
194
|
+
@media (max-width: 740px) {
|
|
195
|
+
.p-distribution-grid,.p-trace-distributions { grid-template-columns: 1fr; }
|
|
196
|
+
.p-metric-grid { gap: 8px; }
|
|
197
|
+
.p-metric-label { flex-wrap: wrap; }
|
|
198
|
+
}
|
|
199
|
+
.p-actions>[role="radiogroup"] { flex-shrink: 0; }
|
|
200
|
+
|
|
201
|
+
.p-sessions-popover .p-history-list { max-height: 340px; overflow-y: auto; }
|
|
202
|
+
|
|
203
|
+
.p-dashboard-topbar { grid-template-columns: minmax(0,1fr) auto minmax(0,1fr); }
|
|
204
|
+
/* HeroUI's 4px tab-list inset makes Tabs 40px; fit them to the topbar control height. */
|
|
205
|
+
.p-topbar .tabs__list { height: var(--topbar-control-height); padding: 2px; }
|
|
206
|
+
/* The single-row desktop topbar owns its height; slot content cannot stretch it. Narrower layouts wrap rows. */
|
|
207
|
+
@media (min-width: 1101px) { .p-topbar { height: var(--topbar-height); } }
|
|
208
|
+
.p-metric-value { display: flex; align-items: baseline; justify-content: space-between; flex-wrap: wrap; gap: 8px; }
|
|
209
|
+
.p-metric-trend { display: inline-flex; }
|
|
210
|
+
@media (max-width: 1100px) {
|
|
211
|
+
.p-topbar { grid-template-columns: minmax(0,1fr) auto; }
|
|
212
|
+
.p-topbar .p-breadcrumb { grid-column: 1; grid-row: 1; }
|
|
213
|
+
.p-perspective-tabs,.p-page-actions { grid-column: 1 / -1; }
|
|
214
|
+
.p-perspective-tabs { width: fit-content; }
|
|
215
|
+
.p-page-actions:empty { display: none; }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.p-series-label { display: inline-flex; align-items: center; gap: 6px; }
|
|
219
|
+
.p-series-dot { display: inline-flex; width: 8px; height: 8px; flex-shrink: 0; }
|
|
220
|
+
.p-ranking li>svg { flex-shrink: 0; }
|
|
221
|
+
/* Custom SVG chart marks only; HeroUI components retain their original motion. */
|
|
222
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
223
|
+
.p-donut-slice { transition: stroke-width 150ms ease-out, opacity 150ms ease-out; }
|
|
224
|
+
}
|
|
225
|
+
.p-series-dot>svg { width: 8px; height: 8px; margin: 0; }
|
|
226
|
+
.p-donut-legend { display: flex; justify-content: center; align-items: center; flex-wrap: wrap; gap: 8px; }
|
|
227
|
+
|
|
228
|
+
/* Hover cards read like the Work context card's Token Usage list. */
|
|
229
|
+
.p-chart-hover-tip { display: grid; gap: 8px; min-width: 200px; max-width: 320px; }
|
|
230
|
+
.p-chart-hover-interval { white-space: nowrap; }
|
|
231
|
+
.p-chart-hover-head { display: flex; justify-content: space-between; align-items: center; gap: 16px; font-size: 13px; font-weight: 500; }
|
|
232
|
+
.p-chart-hover-head strong { font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
233
|
+
.p-chart-hover-values { display: grid; gap: 8px; font-size: 12px; font-variant-numeric: tabular-nums; }
|
|
234
|
+
.p-chart-hover-values>div { display: flex; justify-content: space-between; align-items: center; gap: 16px; }
|
|
235
|
+
.p-chart-hover-values dt { color: var(--muted); }
|
|
236
|
+
.p-chart-hover-values dd { font-weight: 500; }
|
|
237
|
+
|
|
238
|
+
.p-signed-out { min-height: 100dvh; display: grid; place-items: center; padding: 24px; }
|
|
239
|
+
|
|
240
|
+
/* Dashboard chart surfaces on the shared workspace canvas. */
|
|
241
|
+
#dashboard-report :is(.p-plot-card, .p-distribution-grid > .card, .p-trace-distributions > .card, .p-ranking-grid > .card) {
|
|
242
|
+
border: 1px solid var(--separator);
|
|
243
|
+
box-shadow: 0 2px 4px rgb(0 0 0 / 0.04), 0 6px 16px rgb(0 0 0 / 0.03);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
#dashboard-report :is(.p-plot-card, .p-distribution-grid > .card, .p-trace-distributions > .card, .p-ranking-grid > .card) > .card__header {
|
|
247
|
+
padding-bottom: 12px;
|
|
248
|
+
border-bottom: 1px solid color-mix(in oklab, var(--separator) 65%, transparent);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* A page whose content scrolls inside itself (marked .p-fill) fills the
|
|
252
|
+
screen: the page stops scrolling, and the marked content takes the height
|
|
253
|
+
left under the topbar, as the Work message flow does. */
|
|
254
|
+
#root:has(.p-fill) { height: 100dvh; display: flex; flex-direction: column; }
|
|
255
|
+
#root:has(.p-fill) > [role="status"] { flex-shrink: 0; }
|
|
256
|
+
.p-app:has(.p-fill) { flex: 1; min-height: 0; overflow: hidden; }
|
|
257
|
+
.p-app:has(.p-fill) > :is(.p-sidebar, .pr-divider, .pr-reader:not([data-fullscreen="true"])) { height: 100%; }
|
|
258
|
+
.p-body:has(.p-fill) { display: flex; flex-direction: column; height: 100%; min-height: 0; }
|
|
259
|
+
.p-body:has(.p-fill) > .p-topbar { flex-shrink: 0; }
|
|
260
|
+
.p-page:has(.p-fill) { display: flex; flex-direction: column; flex: 1; min-height: 0; padding-bottom: 0; }
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { DescService } from "@bufbuild/protobuf";
|
|
2
|
+
import { Code, ConnectError, createRouterTransport, type ConnectRouter, type HandlerContext } from "@connectrpc/connect";
|
|
3
|
+
import { AgentWorkService } from "@talgent/workbench-contracts/agent/public/v1/work_pb";
|
|
4
|
+
import { PersonalWorkService } from "@talgent/workbench-contracts/agent/public/v1/personal_work_pb";
|
|
5
|
+
import { WorkFileService } from "@talgent/workbench-contracts/agent/public/v1/work_file_pb";
|
|
6
|
+
import { SessionService } from "@talgent/workbench-contracts/account/public/v1/session_pb";
|
|
7
|
+
import { ComputerService } from "@talgent/workbench-contracts/computer/public/v1/computer_pb";
|
|
8
|
+
import { MarketplaceService } from "@talgent/workbench-contracts/marketplace/public/v1/marketplace_pb";
|
|
9
|
+
import { StreamGatewayService } from "@talgent/workbench-contracts/streamgateway/public/v1/streamgateway_pb";
|
|
10
|
+
import { TraceQueryService } from "@talgent/workbench-contracts/trace/public/v1/trace_pb";
|
|
11
|
+
import { vi } from "vitest";
|
|
12
|
+
|
|
13
|
+
// The single frontend test boundary: pages render through the real
|
|
14
|
+
// routes while every ConnectRPC client talks to an in-memory router. Tests
|
|
15
|
+
// replace service methods per case and assert on the requests received. An
|
|
16
|
+
// app built on workbench-ui routes its own services beside these.
|
|
17
|
+
type Handler = (request: never, context: HandlerContext) => unknown;
|
|
18
|
+
export type Fakes = Record<string, Handler | undefined>;
|
|
19
|
+
|
|
20
|
+
/** The core's services, each a set of faked methods. */
|
|
21
|
+
export const services = { session: {} as Fakes, marketplace: {} as Fakes, agent: {} as Fakes, stream: {} as Fakes, trace: {} as Fakes, personal: {} as Fakes, workFile: {} as Fakes, computer: {} as Fakes };
|
|
22
|
+
|
|
23
|
+
/** Routes service to fakes(); a method nobody faked is unimplemented. */
|
|
24
|
+
export function routeFakes(router: ConnectRouter, service: DescService, fakes: () => Fakes) {
|
|
25
|
+
const implementation: Record<string, Handler> = {};
|
|
26
|
+
for (const method of service.methods) {
|
|
27
|
+
implementation[method.localName] = (request, context) => {
|
|
28
|
+
const fake = fakes()[method.localName];
|
|
29
|
+
if (!fake) throw new ConnectError(`${method.localName} is not faked`, Code.Unimplemented);
|
|
30
|
+
return fake(request, context);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
router.service(service, implementation as never);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function routeCoreServices(router: ConnectRouter) {
|
|
37
|
+
routeFakes(router, SessionService, () => services.session);
|
|
38
|
+
routeFakes(router, MarketplaceService, () => services.marketplace);
|
|
39
|
+
routeFakes(router, AgentWorkService, () => services.agent);
|
|
40
|
+
routeFakes(router, StreamGatewayService, () => services.stream);
|
|
41
|
+
routeFakes(router, TraceQueryService, () => services.trace);
|
|
42
|
+
routeFakes(router, PersonalWorkService, () => services.personal);
|
|
43
|
+
routeFakes(router, WorkFileService, () => services.workFile);
|
|
44
|
+
routeFakes(router, ComputerService, () => services.computer);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const testTransport = createRouterTransport(routeCoreServices);
|
|
48
|
+
|
|
49
|
+
/** Stands in for lib/transport: vi.mock("@talgent/workbench-ui/lib/transport", ...). */
|
|
50
|
+
export const transportModule = { transport: testTransport, streamTransport: testTransport, traceTransport: testTransport };
|
|
51
|
+
|
|
52
|
+
export const userId = "e6c7e756-007d-5236-a492-7f4b9be0b2e8";
|
|
53
|
+
|
|
54
|
+
/** A signed-in person and nothing else; each test adds the services it exercises. */
|
|
55
|
+
export function resetServices() {
|
|
56
|
+
for (const fakes of Object.values(services)) for (const key of Object.keys(fakes)) delete fakes[key];
|
|
57
|
+
Object.assign(services.session, {
|
|
58
|
+
getCurrentSession: () => ({ session: { userId, displayName: "Octo Cat", username: "octocat", primaryEmail: "octo@example.test" } }),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Records requests so tests assert on what reached the backend.
|
|
63
|
+
export function recorder<Request, Response>(respond: (request: Request) => Response) {
|
|
64
|
+
const calls: Request[] = [];
|
|
65
|
+
const handler = vi.fn((request: Request) => { calls.push(request); return respond(request); });
|
|
66
|
+
return Object.assign(handler, { calls });
|
|
67
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { create } from "@bufbuild/protobuf";
|
|
2
|
+
import { PluginSummarySchema, type PluginRelease } from "@talgent/workbench-contracts/marketplace/public/v1/marketplace_pb";
|
|
3
|
+
|
|
4
|
+
// ListPlugins lists summaries; fixtures describe full releases and list them through this.
|
|
5
|
+
export const summaryOf = (release: PluginRelease) => create(PluginSummarySchema, {
|
|
6
|
+
slug: release.slug, source: release.source, version: release.version, description: release.description, nativeEntry: release.nativeEntry,
|
|
7
|
+
skillCount: release.skills.length, agentCount: release.agents.length, mcpServerCount: release.mcpServers.length, parameters: release.parameters,
|
|
8
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import "@testing-library/jest-dom/vitest";
|
|
2
|
+
import { vi } from "vitest";
|
|
3
|
+
import { notifyManager } from "@tanstack/react-query";
|
|
4
|
+
import { configure } from "@testing-library/react";
|
|
5
|
+
|
|
6
|
+
// CI tests every workspace in parallel on one runner; under that load the 1s
|
|
7
|
+
// default for findBy*/waitFor expired before a multi-step dialog settled.
|
|
8
|
+
configure({ asyncUtilTimeout: 3000 });
|
|
9
|
+
|
|
10
|
+
// API mock resolutions should be observable within an awaited React act block,
|
|
11
|
+
// including tests that use fake timers for polling intervals.
|
|
12
|
+
notifyManager.setScheduler(queueMicrotask);
|
|
13
|
+
|
|
14
|
+
Object.defineProperty(window, "scrollTo", {
|
|
15
|
+
value: vi.fn(),
|
|
16
|
+
writable: true,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (!Element.prototype.getAnimations) {
|
|
20
|
+
Object.defineProperty(Element.prototype, "getAnimations", {
|
|
21
|
+
value: () => [],
|
|
22
|
+
writable: true,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// jsdom has no layout observer; browser acceptance verifies measured overflow.
|
|
27
|
+
if (!globalThis.ResizeObserver) {
|
|
28
|
+
globalThis.ResizeObserver = class ResizeObserver {
|
|
29
|
+
observe() {}
|
|
30
|
+
unobserve() {}
|
|
31
|
+
disconnect() {}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// jsdom has no media queries; notifications consult reduced-motion preferences.
|
|
36
|
+
if (!window.matchMedia) {
|
|
37
|
+
Object.defineProperty(window, "matchMedia", {
|
|
38
|
+
value: (query: string) => ({ matches: false, media: query, onchange: null, addListener() {}, removeListener() {}, addEventListener() {}, removeEventListener() {}, dispatchEvent: () => false }),
|
|
39
|
+
writable: true,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// jsdom has no viewport; lazily rendered blocks (Mermaid diagrams) see
|
|
44
|
+
// themselves as visible at once.
|
|
45
|
+
if (!globalThis.IntersectionObserver) {
|
|
46
|
+
globalThis.IntersectionObserver = class IntersectionObserver {
|
|
47
|
+
constructor(private readonly callback: IntersectionObserverCallback) {}
|
|
48
|
+
observe(target: Element) { this.callback([{ isIntersecting: true, target, intersectionRatio: 1 } as IntersectionObserverEntry], this as unknown as globalThis.IntersectionObserver); }
|
|
49
|
+
unobserve() {}
|
|
50
|
+
disconnect() {}
|
|
51
|
+
takeRecords() { return []; }
|
|
52
|
+
readonly root = null; readonly rootMargin = ""; readonly thresholds = [];
|
|
53
|
+
} as unknown as typeof globalThis.IntersectionObserver;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// jsdom has no element scrolling; the workspace card scrolls to a folder.
|
|
57
|
+
if (!Element.prototype.scrollTo) {
|
|
58
|
+
Object.defineProperty(Element.prototype, "scrollTo", { value: () => {}, writable: true });
|
|
59
|
+
}
|
package/vite-preset.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { availableParallelism } from "node:os";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import type { ProxyOptions, UserConfig } from "vite";
|
|
4
|
+
import type { InlineConfig } from "vitest/node";
|
|
5
|
+
|
|
6
|
+
// What every web app built on workbench-ui shares: its public files, how its
|
|
7
|
+
// bundles split, its test environment and how its dev server reaches the core.
|
|
8
|
+
|
|
9
|
+
/** The brand and product icons the components reference by URL. */
|
|
10
|
+
export const publicDir = fileURLToPath(new URL("./public", import.meta.url));
|
|
11
|
+
|
|
12
|
+
export type ChunkGroup = { name: string; test: RegExp; priority: number };
|
|
13
|
+
|
|
14
|
+
// Libraries that load only with the lazy chunk that imports them, never in
|
|
15
|
+
// vendor: Shiki's languages and themes, and Mermaid with what only its diagrams
|
|
16
|
+
// use (cytoscape's layout-base and cose-base included). Merged into vendor they
|
|
17
|
+
// put megabytes on every first load. A name ending in "/" is a directory, any
|
|
18
|
+
// other a prefix.
|
|
19
|
+
const lazyLibraries = [
|
|
20
|
+
"@shikijs/", "shiki/", "mermaid/", "@mermaid-js/", "@streamdown/mermaid/", "cytoscape", "layout-base/", "cose-base/", "d3", "dagre-d3-es/",
|
|
21
|
+
"khroma/", "katex/", "roughjs/", "@iconify/", "@upsetjs/", "@braintree/", "delaunator/", "robust-predicates/", "internmap/", "langium/",
|
|
22
|
+
"chevrotain", "lodash-es/", "dayjs/", "uuid/", "stylis/", "ts-dedent/", "marked/",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const separator = String.raw`[\\/]`;
|
|
26
|
+
const escaped = (name: string) => name.replace(/[.]/g, String.raw`\.`).replaceAll("/", separator);
|
|
27
|
+
|
|
28
|
+
/** How an app's bundles split: the core's groups and the app's own, and vendor
|
|
29
|
+
* without the lazy libraries, the core's and the app's own. */
|
|
30
|
+
export function build(app: { groups?: ChunkGroup[]; lazy?: string[] } = {}): UserConfig["build"] {
|
|
31
|
+
// This package splits like an app's own sources, also when installed from
|
|
32
|
+
// the registry.
|
|
33
|
+
const lazy = [".pnpm/", "@talgent/workbench-ui/", ...(app.lazy ?? []), ...lazyLibraries].map(escaped);
|
|
34
|
+
return {
|
|
35
|
+
rollupOptions: {
|
|
36
|
+
output: {
|
|
37
|
+
codeSplitting: {
|
|
38
|
+
groups: [
|
|
39
|
+
{ name: "react-runtime", test: /node_modules[\\/](react|react-dom|scheduler)[\\/]/, priority: 40 },
|
|
40
|
+
{ name: "router-query", test: /node_modules[\\/](react-router|react-router-dom|@tanstack)[\\/]/, priority: 35 },
|
|
41
|
+
{ name: "connect-runtime", test: /node_modules[\\/](@bufbuild|@connectrpc|@talgent[\\/][\w-]+-contracts)[\\/]|gen[\\/]ts[\\/]dist[\\/]/, priority: 30 },
|
|
42
|
+
{
|
|
43
|
+
name: "markdown-highlight",
|
|
44
|
+
test: /node_modules[\\/](react-markdown|react-syntax-highlighter|refractor|remark-gfm|unified|micromark|mdast-util-|hast-util-|property-information|vfile)[\\/]/,
|
|
45
|
+
priority: 25,
|
|
46
|
+
},
|
|
47
|
+
...(app.groups ?? []),
|
|
48
|
+
{ name: "vendor", test: new RegExp(`node_modules${separator}(?!${lazy.join("|")})`), priority: 1 },
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const test: InlineConfig = {
|
|
57
|
+
globals: true,
|
|
58
|
+
environment: "jsdom",
|
|
59
|
+
setupFiles: fileURLToPath(new URL("./test-support/setup.ts", import.meta.url)),
|
|
60
|
+
// Most tests render whole routes, which takes seconds; the 5s default
|
|
61
|
+
// failed them at random whenever the machine was busy.
|
|
62
|
+
testTimeout: 20_000,
|
|
63
|
+
// Vitest defaults to one worker per CPU minus one, which runs serially on
|
|
64
|
+
// the 2-vCPU CI runner; two workers there cut the suite by about 15%.
|
|
65
|
+
maxWorkers: Math.max(2, availableParallelism() - 1),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** The core's addresses on a local controlplane and Computer Gateway. */
|
|
69
|
+
export const coreProxy: Record<string, ProxyOptions> = {
|
|
70
|
+
"/api": {
|
|
71
|
+
target: "http://localhost:8080",
|
|
72
|
+
changeOrigin: true,
|
|
73
|
+
rewrite: path => path.replace(/^\/api/, ""),
|
|
74
|
+
},
|
|
75
|
+
// Account's sign-in endpoints.
|
|
76
|
+
"/auth": {
|
|
77
|
+
target: "http://localhost:8080",
|
|
78
|
+
changeOrigin: true,
|
|
79
|
+
},
|
|
80
|
+
// The Computer Gateway's public routes; "/computers" is the web's own.
|
|
81
|
+
"/computer/": {
|
|
82
|
+
target: "http://localhost:8085",
|
|
83
|
+
changeOrigin: true,
|
|
84
|
+
},
|
|
85
|
+
};
|