Skip to content

Commit 96702ed

Browse files
committed
fix design workspace browser runtime errors
Avoid TypeScript type-root resolution that depends on the Node process global during stored-node transpilation. Keep evaluated design results unproxied so they can be cloned across the visualization MessagePort. Add browser and end-to-end regression coverage for loading, rendering, and rerunning the local AI workstation example.
1 parent bec6771 commit 96702ed

4 files changed

Lines changed: 88 additions & 11 deletions

File tree

packages/comp-dag/dagNodeLoader.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,7 @@ export const parseStoredGraphNodeHashFromPath = (path: string): Hash | null => {
6868
return match?.[1] ? hashFromFilePart(match[1]) : null
6969
}
7070

71-
const loadTypescript = async (): Promise<typeof ts> => {
72-
const browserProcess = (
73-
globalThis as unknown as {
74-
process?: { versions?: Record<string, string | undefined> }
75-
}
76-
).process
77-
if (browserProcess && !browserProcess.versions) browserProcess.versions = {}
78-
return await import('typescript')
79-
}
71+
const loadTypescript = async (): Promise<typeof ts> => await import('typescript')
8072

8173
const formatTypeScript = async (source: string): Promise<string> => {
8274
const prettier = (await import('prettier/standalone')) as PrettierStandaloneModule
@@ -336,6 +328,7 @@ const transpileRunSource = (tsModule: typeof ts, runSource: string): string => {
336328
target: tsModule.ScriptTarget.ES2022,
337329
module: tsModule.ModuleKind.ESNext,
338330
removeComments: false,
331+
typeRoots: [],
339332
},
340333
fileName: 'stored-graph-node-run.ts',
341334
})

packages/comp-dag/test_dag_node_loader.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,33 @@ export default ${nodeBody} satisfies StoredDagNodeModule
4747

4848
testStoredDagNodeNormalizationEmitsStandaloneSource.description =
4949
'Normalizes stored design-graph nodes into standalone source without application imports.'
50+
51+
export const testStoredDagNodeNormalizationRunsWithoutBrowserProcess = async () => {
52+
if (typeof window === 'undefined') {
53+
return {
54+
skipped: true,
55+
reason: 'The missing process global is a browser runtime boundary.',
56+
}
57+
}
58+
59+
const processDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'process')
60+
const removed = Reflect.deleteProperty(globalThis, 'process')
61+
assert(
62+
removed || processDescriptor === undefined,
63+
'Expected the browser process shim to be removable',
64+
)
65+
66+
try {
67+
const normalized = await normalizeStoredGraphNodeSource(`export default ${nodeBody}\n`)
68+
assert(
69+
normalized.source.startsWith('export default {'),
70+
'Expected stored-node normalization to work without a browser process global',
71+
)
72+
return { source: normalized.source }
73+
} finally {
74+
if (processDescriptor) Object.defineProperty(globalThis, 'process', processDescriptor)
75+
}
76+
}
77+
78+
testStoredDagNodeNormalizationRunsWithoutBrowserProcess.description =
79+
'Normalizes a stored design-graph node when the browser has no Node process global.'

src/pages/taskyon/DesignWorkspacePage.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ import {
497497
designRendererBootstrapSource,
498498
type DesignRendererHostConnection,
499499
} from 'src/modules/designRendererProtocol'
500-
import { computed, markRaw, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
500+
import { computed, markRaw, nextTick, onBeforeUnmount, onMounted, ref, shallowRef } from 'vue'
501501
import { useRoute, useRouter } from 'vue-router'
502502
import type { RouteLocationRaw } from 'vue-router'
503503
import type { JSONSchema7 } from 'json-schema'
@@ -515,7 +515,7 @@ const selectedRoot = ref('')
515515
const refOptions = ref<string[]>([])
516516
const project = ref<DesignWorkspaceProject | null>(null)
517517
const params = ref<Record<string, unknown>>({})
518-
const result = ref<unknown>(null)
518+
const result = shallowRef<unknown>(null)
519519
const evaluation = ref<Awaited<ReturnType<typeof evaluateDesign>>['evaluation'] | null>(null)
520520
const visualizationHtml = ref('')
521521
const visualizationFrame = ref<HTMLIFrameElement | null>(null)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { expect, test } from '@playwright/test'
2+
3+
test.describe('design workspace', () => {
4+
test('opens, renders, and reruns the local AI workstation example', async ({ page }) => {
5+
const pageErrors: string[] = []
6+
const relevantConsoleErrors: string[] = []
7+
page.on('pageerror', (error) => pageErrors.push(error.message))
8+
page.on('console', (message) => {
9+
if (
10+
message.type() === 'error' &&
11+
/process is not defined|could not be cloned/i.test(message.text())
12+
) {
13+
relevantConsoleErrors.push(message.text())
14+
}
15+
})
16+
17+
await page.goto('/')
18+
await page.getByRole('button', { name: 'Local AI workstation', exact: true }).click()
19+
await expect(page).toHaveURL(/\/design\/ai-workstation\/revision\//)
20+
await expect(page.getByText('Building the design workspace…', { exact: true })).toBeHidden()
21+
22+
const resultCard = page.locator('.result-card')
23+
const evaluationStatus = resultCard.locator('.panel-summary')
24+
await expect(evaluationStatus).toHaveText(/^Artifact /)
25+
26+
await page.getByRole('tab', { name: 'Result', exact: true }).click()
27+
await expect(resultCard.locator('.q-banner.bg-negative')).toHaveCount(0)
28+
29+
await page.getByRole('tab', { name: 'Visual', exact: true }).click()
30+
const visualization = page.frameLocator('iframe[title="Design result visualization"]')
31+
await expect(visualization.getByText('Waiting for the evaluated design…')).toBeHidden()
32+
await expect(visualization.getByLabel('Generated AI workstation')).toBeVisible()
33+
34+
const nextArtifact = evaluationStatus.evaluate(
35+
(element) =>
36+
new Promise<void>((resolve) => {
37+
const observer = new MutationObserver(() => {
38+
if (!element.textContent?.startsWith('Artifact ')) return
39+
observer.disconnect()
40+
resolve()
41+
})
42+
observer.observe(element, { childList: true, subtree: true, characterData: true })
43+
}),
44+
)
45+
await page.getByRole('button', { name: 'Run', exact: true }).click()
46+
await nextArtifact
47+
await expect(visualization.getByLabel('Generated AI workstation')).toBeVisible()
48+
49+
await page.getByRole('tab', { name: 'Result', exact: true }).click()
50+
await expect(resultCard.locator('.q-banner.bg-negative')).toHaveCount(0)
51+
expect(pageErrors).toEqual([])
52+
expect(relevantConsoleErrors).toEqual([])
53+
})
54+
})

0 commit comments

Comments
 (0)