@grackle-ai/web-components 0.112.0 → 0.112.2
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/.rush/temp/{c8b4fba3a6cfb704f3582f1197cac0fc11abbc50.tar.log → 1421806d07f6b0c455deca4bf89a6412726ffd8b.tar.log} +15 -14
- package/.rush/temp/{c8b4fba3a6cfb704f3582f1197cac0fc11abbc50.untar.log → 1421806d07f6b0c455deca4bf89a6412726ffd8b.untar.log} +2 -2
- package/.rush/temp/{e2794f6b3dd02e58a0ca34e8c438c22d25296c3e.tar.log → a0341c0f1c835c664217d8a879aa38d780e62122.tar.log} +2 -2
- package/.rush/temp/{e2794f6b3dd02e58a0ca34e8c438c22d25296c3e.untar.log → a0341c0f1c835c664217d8a879aa38d780e62122.untar.log} +2 -2
- package/.rush/temp/chunked-rush-logs/web-components._phase_build.chunks.jsonl +5 -5
- package/.rush/temp/chunked-rush-logs/web-components._phase_test.chunks.jsonl +24 -24
- package/.rush/temp/operation/_phase_build/all.log +5 -5
- package/.rush/temp/operation/_phase_build/log-chunks.jsonl +5 -5
- package/.rush/temp/operation/_phase_build/state.json +1 -1
- package/.rush/temp/operation/_phase_test/all.log +24 -24
- package/.rush/temp/operation/_phase_test/log-chunks.jsonl +24 -24
- package/.rush/temp/operation/_phase_test/state.json +1 -1
- package/dist/index.js +3969 -3964
- package/package.json +2 -2
- package/rush-logs/web-components._phase_build.cache.log +1 -1
- package/rush-logs/web-components._phase_build.log +5 -5
- package/rush-logs/web-components._phase_test.cache.log +1 -1
- package/rush-logs/web-components._phase_test.log +24 -24
- package/src/components/display/SplashScreen.tsx +2 -1
- package/src/components/layout/AppNav.stories.tsx +5 -5
- package/src/components/layout/AppNav.tsx +23 -14
- package/src/components/layout/StatusBar.tsx +2 -1
- package/src/components/panels/KeyboardShortcutsPanel.stories.tsx +2 -2
- package/src/components/panels/KeyboardShortcutsPanel.tsx +3 -2
- package/src/hooks/types.ts +1 -1
- package/src/index.ts +2 -0
- package/src/mocks/MockGrackleProvider.tsx +2 -2
- package/src/utils/assetUrl.test.ts +23 -0
- package/src/utils/assetUrl.ts +33 -0
- package/temp/build/lint/_eslint-5eVG3S6w.json +18 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grackle-ai/web-components",
|
|
3
|
-
"version": "0.112.
|
|
3
|
+
"version": "0.112.2",
|
|
4
4
|
"description": "Presentational React component library for the Grackle web UI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"sideEffects": [
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"remark-gfm": "^4.0.0",
|
|
47
47
|
"lucide-react": "~0.474.0",
|
|
48
48
|
"react-router": "^7.0.0",
|
|
49
|
-
"@grackle-ai/common": "0.112.
|
|
49
|
+
"@grackle-ai/common": "0.112.2"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@rushstack/heft": "1.2.7",
|
|
@@ -7,12 +7,12 @@ Invoking: heft run --only build -- --clean
|
|
|
7
7
|
[build:lint] Using ESLint version 9.39.4
|
|
8
8
|
vite v6.4.2 building for production...
|
|
9
9
|
transforming...
|
|
10
|
-
✓
|
|
10
|
+
✓ 2716 modules transformed.
|
|
11
11
|
rendering chunks...
|
|
12
12
|
computing gzip size...
|
|
13
13
|
dist/index.css 158.47 kB │ gzip: 20.60 kB
|
|
14
|
-
dist/index.js 1,
|
|
15
|
-
✓ built in 5.
|
|
14
|
+
dist/index.js 1,591.31 kB │ gzip: 401.72 kB
|
|
15
|
+
✓ built in 5.69s
|
|
16
16
|
[build:vite-build] Vite build completed.
|
|
17
|
-
---- build finished (
|
|
18
|
-
-------------------- Finished (
|
|
17
|
+
---- build finished (72.731s) ----
|
|
18
|
+
-------------------- Finished (72.734s) --------------------
|
|
@@ -5,30 +5,30 @@ The provided list of phases does not contain all phase dependencies. You may nee
|
|
|
5
5
|
|
|
6
6
|
RUN v3.2.4 /home/runner/work/grackle/grackle/packages/web-components
|
|
7
7
|
|
|
8
|
-
✓ src/utils/sessionEvents.test.ts (14 tests)
|
|
9
|
-
✓ src/utils/eventContent.test.ts (38 tests)
|
|
10
|
-
✓ src/utils/dashboard.test.ts (4 tests)
|
|
11
|
-
✓ src/utils/route-config.test.ts (23 tests)
|
|
12
|
-
✓ src/utils/scrollUtils.test.ts (11 tests)
|
|
13
|
-
✓ src/utils/breadcrumbs.test.ts (18 tests)
|
|
14
|
-
✓ src/components/tools/
|
|
15
|
-
✓ src/components/tools/
|
|
16
|
-
✓ src/
|
|
17
|
-
✓ src/
|
|
18
|
-
✓ src/components/editable/useEditableField.test.tsx (17 tests)
|
|
19
|
-
✓ src/
|
|
20
|
-
✓ src/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Test Files
|
|
26
|
-
Tests
|
|
27
|
-
Start at
|
|
28
|
-
Duration 16.
|
|
8
|
+
✓ src/utils/sessionEvents.test.ts (14 tests) 60ms
|
|
9
|
+
✓ src/utils/eventContent.test.ts (38 tests) 155ms
|
|
10
|
+
✓ src/utils/dashboard.test.ts (4 tests) 37ms
|
|
11
|
+
✓ src/utils/route-config.test.ts (23 tests) 50ms
|
|
12
|
+
✓ src/utils/scrollUtils.test.ts (11 tests) 20ms
|
|
13
|
+
✓ src/utils/breadcrumbs.test.ts (18 tests) 65ms
|
|
14
|
+
✓ src/components/tools/classifyTool.test.ts (6 tests) 22ms
|
|
15
|
+
✓ src/components/tools/toolCardHelpers.test.ts (10 tests) 28ms
|
|
16
|
+
✓ src/utils/assetUrl.test.ts (3 tests) 18ms
|
|
17
|
+
✓ src/components/display/extractText.test.tsx (8 tests) 17ms
|
|
18
|
+
✓ src/components/editable/useEditableField.test.tsx (17 tests) 206ms
|
|
19
|
+
✓ src/hooks/useEventSelection.test.ts (13 tests) 146ms
|
|
20
|
+
✓ src/components/notifications/UpdateBanner.test.tsx (4 tests) 199ms
|
|
21
|
+
✓ src/utils/grackleHostStyleVariables.test.ts (2 tests) 445ms
|
|
22
|
+
✓ grackleHostStyleVariables > always returns the MCP-standard fallback variables 416ms
|
|
23
|
+
✓ src/components/display/McpAppWidget.test.tsx (3 tests) 258ms
|
|
24
|
+
|
|
25
|
+
Test Files 15 passed (15)
|
|
26
|
+
Tests 174 passed (174)
|
|
27
|
+
Start at 23:48:14
|
|
28
|
+
Duration 16.41s (transform 2.75s, setup 0ms, collect 18.15s, tests 1.73s, environment 14.38s, prepare 5.42s)
|
|
29
29
|
|
|
30
30
|
[test:vitest] Vitest completed.
|
|
31
|
-
[test:storybook-test] Starting Storybook static server on port
|
|
31
|
+
[test:storybook-test] Starting Storybook static server on port 36655...
|
|
32
32
|
[test:storybook-test] Storybook server ready. Running interaction tests...
|
|
33
33
|
jest-haste-map: duplicate manual mock found: adapter-manager
|
|
34
34
|
The following files share their name; please delete one of them:
|
|
@@ -121,5 +121,5 @@ jest-haste-map: duplicate manual mock found: utils/network
|
|
|
121
121
|
* <rootDir>/packages/server/src/__mocks__/utils/network.ts
|
|
122
122
|
|
|
123
123
|
[test:storybook-test] Storybook interaction tests completed.
|
|
124
|
-
---- test finished (
|
|
125
|
-
-------------------- Finished (
|
|
124
|
+
---- test finished (113.186s) ----
|
|
125
|
+
-------------------- Finished (113.19s) --------------------
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { JSX } from "react";
|
|
2
2
|
import { Spinner } from "./Spinner.js";
|
|
3
|
+
import { assetUrl } from "../../utils/assetUrl.js";
|
|
3
4
|
import styles from "./SplashScreen.module.scss";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -9,7 +10,7 @@ import styles from "./SplashScreen.module.scss";
|
|
|
9
10
|
export function SplashScreen(): JSX.Element {
|
|
10
11
|
return (
|
|
11
12
|
<div className={styles.splash} data-testid="splash-screen">
|
|
12
|
-
<img src="
|
|
13
|
+
<img src={assetUrl("grackle-logo.png")} alt="Grackle" className={styles.logo} />
|
|
13
14
|
<Spinner size="xl" label="Loading Grackle" liveRegion />
|
|
14
15
|
</div>
|
|
15
16
|
);
|
|
@@ -17,7 +17,7 @@ type Story = StoryObj<typeof meta>;
|
|
|
17
17
|
export const AllTabsRendered: Story = {
|
|
18
18
|
play: async ({ canvas }) => {
|
|
19
19
|
await expect(canvas.getByRole("tab", { name: /Dashboard/ })).toBeInTheDocument();
|
|
20
|
-
await expect(canvas.getByRole("tab", { name: /
|
|
20
|
+
await expect(canvas.getByRole("tab", { name: /Sessions/ })).toBeInTheDocument();
|
|
21
21
|
await expect(canvas.getByRole("tab", { name: /Tasks/ })).toBeInTheDocument();
|
|
22
22
|
await expect(canvas.getByRole("tab", { name: /Environments/ })).toBeInTheDocument();
|
|
23
23
|
await expect(canvas.getByRole("tab", { name: /Knowledge/ })).toBeInTheDocument();
|
|
@@ -30,14 +30,14 @@ export const CoreOnlyTabs: Story = {
|
|
|
30
30
|
args: {
|
|
31
31
|
tabs: [
|
|
32
32
|
{ view: "dashboard", label: "Dashboard", icon: <Home size={ICON_LG} />, route: HOME_URL, testId: "sidebar-tab-dashboard" },
|
|
33
|
-
{ view: "chat", label: "
|
|
33
|
+
{ view: "chat", label: "Sessions", icon: <MessageSquare size={ICON_LG} />, route: CHAT_URL, testId: "sidebar-tab-chat" },
|
|
34
34
|
{ view: "environments", label: "Environments", icon: <Monitor size={ICON_LG} />, route: ENVIRONMENTS_URL, testId: "sidebar-tab-environments" },
|
|
35
35
|
{ view: "settings", label: "Settings", icon: <Settings size={ICON_LG} />, route: SETTINGS_CREDENTIALS_URL, testId: "sidebar-tab-settings" },
|
|
36
36
|
],
|
|
37
37
|
},
|
|
38
38
|
play: async ({ canvas }) => {
|
|
39
39
|
await expect(canvas.getByRole("tab", { name: /Dashboard/ })).toBeInTheDocument();
|
|
40
|
-
await expect(canvas.getByRole("tab", { name: /
|
|
40
|
+
await expect(canvas.getByRole("tab", { name: /Sessions/ })).toBeInTheDocument();
|
|
41
41
|
await expect(canvas.getByRole("tab", { name: /Environments/ })).toBeInTheDocument();
|
|
42
42
|
await expect(canvas.getByRole("tab", { name: /Settings/ })).toBeInTheDocument();
|
|
43
43
|
await expect(canvas.queryByRole("tab", { name: /Tasks/ })).not.toBeInTheDocument();
|
|
@@ -51,7 +51,7 @@ export const AllTabsExplicit: Story = {
|
|
|
51
51
|
args: {
|
|
52
52
|
tabs: [
|
|
53
53
|
{ view: "dashboard", label: "Dashboard", icon: <Home size={ICON_LG} />, route: HOME_URL, testId: "sidebar-tab-dashboard" },
|
|
54
|
-
{ view: "chat", label: "
|
|
54
|
+
{ view: "chat", label: "Sessions", icon: <MessageSquare size={ICON_LG} />, route: CHAT_URL, testId: "sidebar-tab-chat" },
|
|
55
55
|
{ view: "tasks", label: "Tasks", icon: <ClipboardList size={ICON_LG} />, route: TASKS_URL, testId: "sidebar-tab-tasks" },
|
|
56
56
|
{ view: "environments", label: "Environments", icon: <Monitor size={ICON_LG} />, route: ENVIRONMENTS_URL, testId: "sidebar-tab-environments" },
|
|
57
57
|
{ view: "knowledge", label: "Knowledge", icon: <Brain size={ICON_LG} />, route: KNOWLEDGE_URL, testId: "sidebar-tab-knowledge" },
|
|
@@ -78,7 +78,7 @@ export const SettingsPinnedRight: Story = {
|
|
|
78
78
|
args: {
|
|
79
79
|
tabs: [
|
|
80
80
|
{ view: "dashboard", label: "Dashboard", icon: <Home size={ICON_LG} />, route: HOME_URL, testId: "sidebar-tab-dashboard" },
|
|
81
|
-
{ view: "chat", label: "
|
|
81
|
+
{ view: "chat", label: "Sessions", icon: <MessageSquare size={ICON_LG} />, route: CHAT_URL, testId: "sidebar-tab-chat" },
|
|
82
82
|
{ view: "environments", label: "Environments", icon: <Monitor size={ICON_LG} />, route: ENVIRONMENTS_URL, testId: "sidebar-tab-environments" },
|
|
83
83
|
{ view: "settings", label: "Settings", icon: <Settings size={ICON_LG} />, route: SETTINGS_CREDENTIALS_URL, testId: "sidebar-tab-settings", align: "end" },
|
|
84
84
|
{ view: "tasks", label: "Tasks", icon: <ClipboardList size={ICON_LG} />, route: TASKS_URL, testId: "sidebar-tab-tasks" },
|
|
@@ -21,18 +21,24 @@ export interface AppTab {
|
|
|
21
21
|
route: string;
|
|
22
22
|
/** data-testid suffix. */
|
|
23
23
|
testId: string;
|
|
24
|
+
/**
|
|
25
|
+
* Display order within the nav bar (lower numbers appear first). Applied
|
|
26
|
+
* across all plugins so tab order is explicit rather than dependent on plugin
|
|
27
|
+
* load order. End-aligned tabs ignore this and are always pinned right.
|
|
28
|
+
*/
|
|
29
|
+
order?: number;
|
|
24
30
|
/** Horizontal alignment within the nav bar. `"end"` pins the tab to the right edge. */
|
|
25
31
|
align?: "end";
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
/** Ordered list of all app navigation tabs. Exported for plugin registry use. */
|
|
29
35
|
export const TABS: AppTab[] = [
|
|
30
|
-
{ view: "dashboard", label: "Dashboard", icon: <Home size={ICON_LG} />, route: HOME_URL, testId: "sidebar-tab-dashboard" },
|
|
31
|
-
{ view: "
|
|
32
|
-
{ view: "
|
|
33
|
-
{ view: "
|
|
34
|
-
{ view: "
|
|
35
|
-
{ view: "
|
|
36
|
+
{ view: "dashboard", label: "Dashboard", icon: <Home size={ICON_LG} />, route: HOME_URL, testId: "sidebar-tab-dashboard", order: 0 },
|
|
37
|
+
{ view: "tasks", label: "Tasks", icon: <ClipboardList size={ICON_LG} />, route: TASKS_URL, testId: "sidebar-tab-tasks", order: 1 },
|
|
38
|
+
{ view: "environments", label: "Environments", icon: <Monitor size={ICON_LG} />, route: ENVIRONMENTS_URL, testId: "sidebar-tab-environments", order: 2 },
|
|
39
|
+
{ view: "chat", label: "Sessions", icon: <MessageSquare size={ICON_LG} />, route: CHAT_URL, testId: "sidebar-tab-chat", order: 3 },
|
|
40
|
+
{ view: "findings", label: "Findings", icon: <Search size={ICON_LG} />, route: FINDINGS_URL, testId: "sidebar-tab-findings", order: 4 },
|
|
41
|
+
{ view: "knowledge", label: "Knowledge", icon: <Brain size={ICON_LG} />, route: KNOWLEDGE_URL, testId: "sidebar-tab-knowledge", order: 5 },
|
|
36
42
|
{ view: "settings", label: "Settings", icon: <Settings size={ICON_LG} />, route: SETTINGS_CREDENTIALS_URL, testId: "sidebar-tab-settings", align: "end" },
|
|
37
43
|
];
|
|
38
44
|
|
|
@@ -67,15 +73,18 @@ export function AppNav({ tabs = TABS }: { tabs?: AppTab[] }): JSX.Element {
|
|
|
67
73
|
|
|
68
74
|
const activeView = getActiveView(location.pathname);
|
|
69
75
|
|
|
70
|
-
//
|
|
71
|
-
// so they stay pinned to the right edge no matter which
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
// Sort by explicit `order`, then render end-aligned tabs (e.g. Settings) last
|
|
77
|
+
// regardless of order, so they stay pinned to the right edge no matter which
|
|
78
|
+
// plugins contribute tabs. Tabs without an `order` keep their incoming order
|
|
79
|
+
// (stable sort) and fall after explicitly-ordered ones.
|
|
80
|
+
const orderedTabs = useMemo(() => {
|
|
81
|
+
const byOrder = (a: AppTab, b: AppTab): number =>
|
|
82
|
+
(a.order ?? Number.MAX_SAFE_INTEGER) - (b.order ?? Number.MAX_SAFE_INTEGER);
|
|
83
|
+
return [
|
|
84
|
+
...tabs.filter((t) => t.align !== "end").sort(byOrder),
|
|
75
85
|
...tabs.filter((t) => t.align === "end"),
|
|
76
|
-
]
|
|
77
|
-
|
|
78
|
-
);
|
|
86
|
+
];
|
|
87
|
+
}, [tabs]);
|
|
79
88
|
const firstEndAlignedView = orderedTabs.find((t) => t.align === "end")?.view;
|
|
80
89
|
|
|
81
90
|
const handleClick = useCallback((tab: AppTab) => {
|
|
@@ -3,6 +3,7 @@ import { Circle, Menu } from "lucide-react";
|
|
|
3
3
|
import type { ConnectionStatus, Environment, Session } from "../../hooks/types.js";
|
|
4
4
|
import { ICON_LG, ICON_XS } from "../../utils/iconSize.js";
|
|
5
5
|
import { HOME_URL, useAppNavigate } from "../../utils/navigation.js";
|
|
6
|
+
import { assetUrl } from "../../utils/assetUrl.js";
|
|
6
7
|
import { Tooltip } from "../display/Tooltip.js";
|
|
7
8
|
import styles from "./StatusBar.module.scss";
|
|
8
9
|
|
|
@@ -51,7 +52,7 @@ export function StatusBar({ connectionStatus, environments, sessions, onToggleSi
|
|
|
51
52
|
)}
|
|
52
53
|
<Tooltip text="Home" placement="bottom">
|
|
53
54
|
<button type="button" className={styles.brand} onClick={() => navigate(HOME_URL)} data-testid="statusbar-brand">
|
|
54
|
-
<img src="
|
|
55
|
+
<img src={assetUrl("icon-192x192.png")} alt="" className={styles.brandLogo} aria-hidden="true" data-testid="statusbar-logo" />
|
|
55
56
|
Grackle
|
|
56
57
|
</button>
|
|
57
58
|
</Tooltip>
|
|
@@ -25,7 +25,7 @@ export const AllCategoriesRendered: Story = {
|
|
|
25
25
|
await expect(canvas.getByText("Workspace Page")).toBeInTheDocument();
|
|
26
26
|
await expect(canvas.getByText("Navigation Lists")).toBeInTheDocument();
|
|
27
27
|
await expect(canvas.getByText("Editing")).toBeInTheDocument();
|
|
28
|
-
await expect(canvas.getByText("
|
|
28
|
+
await expect(canvas.getByText("Sessions")).toBeInTheDocument();
|
|
29
29
|
},
|
|
30
30
|
};
|
|
31
31
|
|
|
@@ -35,6 +35,6 @@ export const ShortcutDescriptions: Story = {
|
|
|
35
35
|
await expect(canvas.getByText("Open keyboard shortcuts reference")).toBeInTheDocument();
|
|
36
36
|
await expect(canvas.getByText("Create a new task")).toBeInTheDocument();
|
|
37
37
|
await expect(canvas.getByText("Switch to Overview tab")).toBeInTheDocument();
|
|
38
|
-
await expect(canvas.getByText("Send message (when
|
|
38
|
+
await expect(canvas.getByText("Send message (when the composer is focused)")).toBeInTheDocument();
|
|
39
39
|
},
|
|
40
40
|
};
|
|
@@ -66,9 +66,10 @@ const SHORTCUT_GROUPS: ShortcutGroup[] = [
|
|
|
66
66
|
],
|
|
67
67
|
},
|
|
68
68
|
{
|
|
69
|
-
title: "
|
|
69
|
+
title: "Sessions",
|
|
70
70
|
shortcuts: [
|
|
71
|
-
{ keys: ["Enter"], description: "Send message (when
|
|
71
|
+
{ keys: ["Ctrl/Cmd", "Enter"], description: "Send message (when the composer is focused)" },
|
|
72
|
+
{ keys: ["Enter"], description: "Insert a new line in the message composer" },
|
|
72
73
|
],
|
|
73
74
|
},
|
|
74
75
|
];
|
package/src/hooks/types.ts
CHANGED
|
@@ -327,7 +327,7 @@ export interface UseWorkspacesResult {
|
|
|
327
327
|
defaultPersonaId?: string,
|
|
328
328
|
useWorktrees?: boolean,
|
|
329
329
|
workingDirectory?: string,
|
|
330
|
-
onSuccess?: () => void,
|
|
330
|
+
onSuccess?: (workspace: Workspace) => void,
|
|
331
331
|
onError?: (message: string) => void,
|
|
332
332
|
) => Promise<void>;
|
|
333
333
|
/** Archive a workspace by ID. */
|
package/src/index.ts
CHANGED
|
@@ -202,6 +202,8 @@ export { computeKpis, getAttentionTasks, getActiveSessions, getWorkspaceSnapshot
|
|
|
202
202
|
|
|
203
203
|
export { isNearAnchor, computeScrollCompensation, SCROLL_ANCHOR_THRESHOLD_PX } from "./utils/scrollUtils.js";
|
|
204
204
|
|
|
205
|
+
export { assetUrl } from "./utils/assetUrl.js";
|
|
206
|
+
|
|
205
207
|
// ─── Themes ──────────────────────────────────────────────────────────────────
|
|
206
208
|
|
|
207
209
|
export { THEMES, THEME_IDS, DEFAULT_THEME_ID, getThemeById } from "./themes.js";
|
|
@@ -382,7 +382,7 @@ export function MockGrackleProvider({ children }: MockGrackleProviderProps): JSX
|
|
|
382
382
|
defaultPersonaId?: string,
|
|
383
383
|
useWorktrees?: boolean,
|
|
384
384
|
workingDirectory?: string,
|
|
385
|
-
onSuccess?: () => void,
|
|
385
|
+
onSuccess?: (workspace: Workspace) => void,
|
|
386
386
|
_onError?: (message: string) => void,
|
|
387
387
|
) => {
|
|
388
388
|
console.log("[MockGrackle] createWorkspace", { name, description });
|
|
@@ -405,7 +405,7 @@ export function MockGrackleProvider({ children }: MockGrackleProviderProps): JSX
|
|
|
405
405
|
|
|
406
406
|
setWorkspaces((prev) => [...prev, newWorkspace]);
|
|
407
407
|
if (onSuccess) {
|
|
408
|
-
onSuccess();
|
|
408
|
+
onSuccess(newWorkspace);
|
|
409
409
|
}
|
|
410
410
|
},
|
|
411
411
|
[nextId],
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach, vi } from "vitest";
|
|
2
|
+
import { assetUrl } from "./assetUrl.js";
|
|
3
|
+
|
|
4
|
+
describe("assetUrl", () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
vi.unstubAllEnvs();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("prefixes the file with the default base URL", () => {
|
|
10
|
+
vi.stubEnv("BASE_URL", "/");
|
|
11
|
+
expect(assetUrl("icon-192x192.png")).toBe("/icon-192x192.png");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("prefixes the file with a sub-path base URL (demo deployment)", () => {
|
|
15
|
+
vi.stubEnv("BASE_URL", "/grackle/demo/");
|
|
16
|
+
expect(assetUrl("icon-192x192.png")).toBe("/grackle/demo/icon-192x192.png");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("collapses a leading slash on the file name so the base join never double-slashes", () => {
|
|
20
|
+
vi.stubEnv("BASE_URL", "/grackle/demo/");
|
|
21
|
+
expect(assetUrl("/grackle-logo.png")).toBe("/grackle/demo/grackle-logo.png");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Minimal typing for Vite's `import.meta.env.BASE_URL`. We deliberately avoid a
|
|
2
|
+
// full `/// <reference types="vite/client" />` so that Vite's broad ambient
|
|
3
|
+
// asset-module declarations (`*.png`, `*.css`, ...) don't leak into the type
|
|
4
|
+
// graph of every program that compiles this file — we only need `BASE_URL`.
|
|
5
|
+
declare global {
|
|
6
|
+
interface ImportMetaEnv {
|
|
7
|
+
/** Base public path the app is served from (Vite `base`); always ends in `/`. */
|
|
8
|
+
readonly BASE_URL: string;
|
|
9
|
+
}
|
|
10
|
+
interface ImportMeta {
|
|
11
|
+
readonly env: ImportMetaEnv;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a public asset path against the application's configured base URL.
|
|
17
|
+
*
|
|
18
|
+
* Public assets (logos, icons, the web manifest) sit at the site root in local
|
|
19
|
+
* development but under a sub-path when the app is deployed to a base URL — e.g.
|
|
20
|
+
* the GitHub Pages demo served from `/grackle/demo/`. A leading-slash `src` is
|
|
21
|
+
* resolved by the browser against the origin root and ignores Vite's `base`, so
|
|
22
|
+
* such assets 404 on sub-path deployments. Prefixing with
|
|
23
|
+
* `import.meta.env.BASE_URL` (always set by Vite, with a trailing slash) yields
|
|
24
|
+
* a path that is correct in every deployment.
|
|
25
|
+
*
|
|
26
|
+
* @param fileName - The asset's file name relative to the public root, ideally
|
|
27
|
+
* without a leading slash (e.g. `"icon-192x192.png"`). A leading slash is
|
|
28
|
+
* tolerated and stripped to avoid a double slash after the base.
|
|
29
|
+
* @returns The base-prefixed asset URL (e.g. `"/grackle/demo/icon-192x192.png"`).
|
|
30
|
+
*/
|
|
31
|
+
export function assetUrl(fileName: string): string {
|
|
32
|
+
return `${import.meta.env.BASE_URL}${fileName.replace(/^\//, "")}`;
|
|
33
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
],
|
|
8
8
|
[
|
|
9
9
|
"hooks/types.ts",
|
|
10
|
-
"/
|
|
10
|
+
"Q3IfPXTzOZH/42LKa4mjM52/+X0=_7W3TvvwZxl0TGST6/dyQa8DKDDc="
|
|
11
11
|
],
|
|
12
12
|
[
|
|
13
13
|
"components/chat/ChatInput.tsx",
|
|
@@ -161,9 +161,13 @@
|
|
|
161
161
|
"components/display/Spinner.tsx",
|
|
162
162
|
"p7oAA36O26nwdV3QtEJdt9vSMRk=_7W3TvvwZxl0TGST6/dyQa8DKDDc="
|
|
163
163
|
],
|
|
164
|
+
[
|
|
165
|
+
"utils/assetUrl.ts",
|
|
166
|
+
"KmzyO2hja0ddDKHVjGabZurZzN0=_7W3TvvwZxl0TGST6/dyQa8DKDDc="
|
|
167
|
+
],
|
|
164
168
|
[
|
|
165
169
|
"components/display/SplashScreen.tsx",
|
|
166
|
-
"
|
|
170
|
+
"OTmXaBxnSDqnKvDW1/SajhKz1zU=_7W3TvvwZxl0TGST6/dyQa8DKDDc="
|
|
167
171
|
],
|
|
168
172
|
[
|
|
169
173
|
"components/display/Tooltip.tsx",
|
|
@@ -267,11 +271,11 @@
|
|
|
267
271
|
],
|
|
268
272
|
[
|
|
269
273
|
"components/layout/StatusBar.tsx",
|
|
270
|
-
"
|
|
274
|
+
"IwwWop/68/zHUpaH0JxUuhhga9Q=_7W3TvvwZxl0TGST6/dyQa8DKDDc="
|
|
271
275
|
],
|
|
272
276
|
[
|
|
273
277
|
"components/layout/AppNav.tsx",
|
|
274
|
-
"+
|
|
278
|
+
"m+cH8YKux2EB/NGH9BzaikTuEw0=_7W3TvvwZxl0TGST6/dyQa8DKDDc="
|
|
275
279
|
],
|
|
276
280
|
[
|
|
277
281
|
"components/layout/Sidebar.tsx",
|
|
@@ -387,7 +391,7 @@
|
|
|
387
391
|
],
|
|
388
392
|
[
|
|
389
393
|
"components/panels/KeyboardShortcutsPanel.tsx",
|
|
390
|
-
"
|
|
394
|
+
"0yOtcA8wE4p4ctgFYThvwW0x8Pk=_7W3TvvwZxl0TGST6/dyQa8DKDDc="
|
|
391
395
|
],
|
|
392
396
|
[
|
|
393
397
|
"components/panels/CredentialProvidersPanel.tsx",
|
|
@@ -467,7 +471,7 @@
|
|
|
467
471
|
],
|
|
468
472
|
[
|
|
469
473
|
"mocks/MockGrackleProvider.tsx",
|
|
470
|
-
"
|
|
474
|
+
"Zrw0hhCq5Bbv4P33XGGTpJjLm/8=_7W3TvvwZxl0TGST6/dyQa8DKDDc="
|
|
471
475
|
],
|
|
472
476
|
[
|
|
473
477
|
"test-utils/storybook-decorators.tsx",
|
|
@@ -479,7 +483,7 @@
|
|
|
479
483
|
],
|
|
480
484
|
[
|
|
481
485
|
"index.ts",
|
|
482
|
-
"
|
|
486
|
+
"Lbo23h9QnwqCWZmf6x/aafniLGY=_7W3TvvwZxl0TGST6/dyQa8DKDDc="
|
|
483
487
|
],
|
|
484
488
|
[
|
|
485
489
|
"components/index.ts",
|
|
@@ -607,7 +611,7 @@
|
|
|
607
611
|
],
|
|
608
612
|
[
|
|
609
613
|
"components/layout/AppNav.stories.tsx",
|
|
610
|
-
"
|
|
614
|
+
"oYykVWEa7SEp4YtAOIe+xpB1D3k=_7W3TvvwZxl0TGST6/dyQa8DKDDc="
|
|
611
615
|
],
|
|
612
616
|
[
|
|
613
617
|
"components/layout/BottomStatusBar.stories.tsx",
|
|
@@ -671,7 +675,7 @@
|
|
|
671
675
|
],
|
|
672
676
|
[
|
|
673
677
|
"components/panels/KeyboardShortcutsPanel.stories.tsx",
|
|
674
|
-
"
|
|
678
|
+
"gZ9hP3WGlzy58xTJYrj2eHoEpYQ=_7W3TvvwZxl0TGST6/dyQa8DKDDc="
|
|
675
679
|
],
|
|
676
680
|
[
|
|
677
681
|
"components/panels/TaskActionButtons.stories.tsx",
|
|
@@ -797,6 +801,10 @@
|
|
|
797
801
|
"hooks/useEventSelection.test.ts",
|
|
798
802
|
"/ggsjovG/PzoHNtCbMzOcwYEa/k=_orHdc0vDZqoYfD6TIDl1Za3EAL4="
|
|
799
803
|
],
|
|
804
|
+
[
|
|
805
|
+
"utils/assetUrl.test.ts",
|
|
806
|
+
"IaZp6S9n0W2AQtaTJgpZV3+L9sQ=_orHdc0vDZqoYfD6TIDl1Za3EAL4="
|
|
807
|
+
],
|
|
800
808
|
[
|
|
801
809
|
"utils/breadcrumbs.test.ts",
|
|
802
810
|
"uvtwd3hxobqtDMQ8hessqUzyrmc=_orHdc0vDZqoYfD6TIDl1Za3EAL4="
|
|
@@ -826,5 +834,5 @@
|
|
|
826
834
|
"hL9OyAvsoDAPSh4+TPUEgQrdYMI=_orHdc0vDZqoYfD6TIDl1Za3EAL4="
|
|
827
835
|
]
|
|
828
836
|
],
|
|
829
|
-
"filesHash": "
|
|
837
|
+
"filesHash": "wzmDK7nkrsOam3n0gh_j3g"
|
|
830
838
|
}
|