Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion canvas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"test:smoke:xr-spatial-capture-fallback:source": "node ../scripts/run-xr-spatial-capture-fallback-source-smoke.mjs",
"test:smoke:xr-spatial-capture-fallback:browser": "node ./scripts/run_xr_spatial_capture_fallback_browser_smoke.mjs",
"test:smoke:xr-v2:source": "node ../scripts/run-xr-v2-source-smoke.mjs",
"test:smoke:xr-v2:browser": "node ./scripts/run_xr_v2_workspace_seed_browser_smoke.mjs",
"test:smoke:xr-v2:browser": "npm run test:smoke:xr-v2:browser:comprehensive && npm run test:smoke:xr-v2:browser:workspace-seed",
"test:smoke:xr-v2:browser:comprehensive": "node ./scripts/run_xr_v2_browser_smoke.mjs",
"test:smoke:xr-v2:browser:workspace-seed": "node ./scripts/run_xr_v2_workspace_seed_browser_smoke.mjs",
"build:settings": "tsx src/cli/extract-settings-schema.ts",
"test:responsibility-flow": "node --import tsx --test src/cli/__tests__/settingsResponsibilityFlow.test.ts src/features/settings/__tests__/flowDetailsRuntime.test.ts",
"doc:generate-byteplus-chat-reference": "tsx src/cli/generate-byteplus-chat-reference.ts",
Expand Down
4 changes: 2 additions & 2 deletions canvas/scripts/run_xr_v2_workspace_seed_browser_smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { runLocalViteBrowserSmoke } from './lib/run-local-vite-browser-smoke.mjs

const canvasRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
process.chdir(canvasRoot)
process.env.VITE_KNOWGRPH_RUN_READY_DEMO = 'xr-v2'
process.env.VITE_KNOWGRPH_RUN_READY_REPO_LOCAL = '1'
process.env.VITE_WORKSPACE_INITIALIZATION_DOCS_ABS_ROOT = resolve(canvasRoot, '../docs')
process.env.KG_XR_V2_WORKSPACE_SMOKE_BASE_URL = `http://127.0.0.1:${String(process.env.KG_XR_V2_WORKSPACE_SMOKE_PORT || '4194')}`

runLocalViteBrowserSmoke({
logLabel: 'xr-v2-workspace-seed-browser-smoke',
devServerPort: String(process.env.KG_XR_V2_WORKSPACE_SMOKE_PORT || '4194'),
devServerPath: '/knowgrph/',
baseUrlEnvName: 'KG_XR_V2_WORKSPACE_SMOKE_BASE_URL',
baseUrlEnvName: 'KG_XR_V2_WORKSPACE_SMOKE_BASE_URL_UNUSED',
verifierCommand: process.execPath,
verifierArgs: ['./scripts/verify_xr_v2_workspace_seed_browser_smoke.mjs'],
verifierFailureLabel: 'XR v2 workspace-seed browser smoke',
Expand Down
151 changes: 138 additions & 13 deletions canvas/scripts/verify_xr_v2_workspace_seed_browser_smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,153 @@ const context = await browser.newContext({ permissions: [] })
const page = await context.newPage()
const browserErrors = []
const coldStartTimeoutMs = 90_000
const browserPollingIntervalMs = 250

async function readWorkspaceSeedReadinessSnapshot() {
return page.evaluate(() => {
const runtimeNode = document.querySelector('[data-kg-xr-v2-authoring-runtime="1"]')
const readinessNode = document.querySelector('[data-kg-xr-v2-workspace-readiness="1"]')
const tier = readinessNode?.getAttribute('data-kg-xr-v2-capability-tier') || ''
const ecsEvidence = readinessNode?.querySelector('[data-kg-xr-v2-ac="AC-6"]')
?.getAttribute('data-kg-xr-v2-ac-local-evidence') || null
const materialEvidence = readinessNode?.querySelector('[data-kg-xr-v2-ac="AC-7"]')
?.getAttribute('data-kg-xr-v2-ac-local-evidence') || null
return Object.freeze({
ecsStatus: runtimeNode?.getAttribute('data-kg-xr-v2-ecs-status') || null,
ecsEntityCount: Number(runtimeNode?.getAttribute('data-kg-xr-v2-ecs-entity-count') || 0),
probeStatus: readinessNode?.getAttribute('data-kg-xr-v2-probe-status') || null,
capabilityTier: tier,
ecsEvidence,
materialEvidence,
ready:
runtimeNode?.getAttribute('data-kg-xr-v2-ecs-status') === 'ready'
&& Number(runtimeNode?.getAttribute('data-kg-xr-v2-ecs-entity-count') || 0) >= 2
&& readinessNode?.getAttribute('data-kg-xr-v2-probe-status') === 'ready'
&& ['webxr-ar', 'webxr-vr', 'pseudo-ar-depth-parallax', 'flat-fallback'].includes(tier)
&& ecsEvidence === 'browser-observed'
&& materialEvidence === 'browser-observed',
})
})
}

async function waitForWorkspaceSeedReadiness() {
const deadline = Date.now() + coldStartTimeoutMs
let snapshot = await readWorkspaceSeedReadinessSnapshot()
while (!snapshot.ready && Date.now() < deadline) {
await page.waitForTimeout(browserPollingIntervalMs)
snapshot = await readWorkspaceSeedReadinessSnapshot()
}
assert.equal(
snapshot.ready,
true,
`XR v2 workspace readiness never converged: ${JSON.stringify(snapshot)}`,
)
}

page.on('pageerror', error => browserErrors.push(error.message))
try {
await page.goto(`${baseUrl}/knowgrph/`, { waitUntil: 'domcontentloaded' })
await page.goto(`${baseUrl}/knowgrph/?openEditorWorkspace=1`, {
waitUntil: 'domcontentloaded',
timeout: coldStartTimeoutMs,
})
const sourceFiles = page.getByRole('navigation', { name: 'Source files', exact: true })
await sourceFiles.waitFor({ state: 'visible', timeout: coldStartTimeoutMs })
const docsFolder = sourceFiles.getByRole('button', { name: 'Folder docs', exact: true })
await docsFolder.waitFor({ state: 'visible', timeout: coldStartTimeoutMs })
const workspaceSeedsFolder = sourceFiles.getByRole('button', {
name: 'Folder workspace-seeds',
exact: true,
})
if (!await workspaceSeedsFolder.isVisible()) await docsFolder.click()
await workspaceSeedsFolder.waitFor({ state: 'visible', timeout: coldStartTimeoutMs })
const seedRow = sourceFiles.getByRole('button', {
name: 'File knowgrph-ar-vr-xr-runtime-readiness-demo.md',
exact: true,
})
if (!await seedRow.isVisible()) await workspaceSeedsFolder.click()
await seedRow.waitFor({ state: 'visible', timeout: coldStartTimeoutMs })
assert.equal(
await page.locator('[data-kg-xr-v2-authoring-runtime="1"]').count(),
0,
'XR v2 must remain inactive until the actual Explorer seed row is selected',
)
await seedRow.click()

const panel = page.locator('[data-kg-motion-control-floating-panel="1"]')
await panel.waitFor({ state: 'visible', timeout: coldStartTimeoutMs })
const runtime = page.locator('[data-kg-xr-v2-authoring-runtime="1"]')
await runtime.waitFor({ state: 'visible', timeout: coldStartTimeoutMs })
await page.waitForFunction(() => {
const node = document.querySelector('[data-kg-xr-v2-authoring-runtime="1"]')
return node?.getAttribute('data-kg-xr-v2-ecs-status') === 'ready'
&& Number(node?.getAttribute('data-kg-xr-v2-ecs-entity-count') || 0) >= 2
}, undefined, { timeout: coldStartTimeoutMs })
for (const selector of [
'[data-kg-motion-control-start="1"]',
'[data-kg-motion-control-stop="1"]',
'[data-kg-motion-control-enable-sensors="1"]',
'[data-kg-motion-control-disable-sensors="1"]',
]) assert.equal(await page.locator(selector).count(), 1, `missing ${selector}`)
const readiness = page.locator('[data-kg-xr-v2-workspace-readiness="1"]')
await readiness.waitFor({ state: 'visible', timeout: coldStartTimeoutMs })
const threeCanvas = page.locator('[data-kg-three-canvas-owner="1"]')
await threeCanvas.waitFor({ state: 'visible', timeout: coldStartTimeoutMs })
const xrStage = page.locator('[data-kg-xr-document-loaded="1"]')
await xrStage.waitFor({ state: 'visible', timeout: coldStartTimeoutMs })
await waitForWorkspaceSeedReadiness()
assert.equal(await runtime.getAttribute('data-kg-xr-v2-scene-ready'), 'true')
assert.ok(await runtime.getAttribute('data-kg-xr-v2-readiness'))
assert.equal(await readiness.getAttribute('data-kg-xr-v2-camera-auto-request'), 'false')
assert.equal(await readiness.getAttribute('data-kg-xr-v2-sensor-auto-request'), 'false')
assert.equal(await readiness.getAttribute('data-kg-xr-v2-immersive-auto-request'), 'false')
assert.equal(await readiness.getAttribute('data-kg-xr-v2-physical-certification'), 'external-required')
const indexedDbProbe = readiness.locator('[data-kg-xr-v2-browser-api="indexedDb"]')
assert.equal(await indexedDbProbe.count(), 1, 'missing real IndexedDB readiness preflight')
assert.equal(await indexedDbProbe.getAttribute('data-kg-xr-v2-browser-api-available'), 'true')
assert.equal(
await readiness.locator('[data-kg-xr-v2-ac="AC-4"]').getAttribute('data-kg-xr-v2-ac-local-evidence'),
'not-observed',
'saved-asset viewer evidence must stay closed before user capture/playback',
)
assert.equal(
await readiness.locator('[data-kg-xr-v2-ac="AC-6"]').getAttribute('data-kg-xr-v2-ac-local-evidence'),
'browser-observed',
)
assert.equal(
await readiness.locator('[data-kg-xr-v2-ac="AC-7"]').getAttribute('data-kg-xr-v2-ac-local-evidence'),
'browser-observed',
)
const startCamera = page.locator('[data-kg-motion-control-start="1"]')
const stopCamera = page.locator('[data-kg-motion-control-stop="1"]')
const enableSensors = page.locator('[data-kg-motion-control-enable-sensors="1"]')
const disableSensors = page.locator('[data-kg-motion-control-disable-sensors="1"]')
const spatialCapture = page.locator('[data-kg-xr-v2-spatial-capture="1"]')
const startSpatialCapture = page.locator('[data-kg-xr-v2-spatial-capture-start="1"]')
const stopSpatialCapture = page.locator('[data-kg-xr-v2-spatial-capture-stop="1"]')
const immersiveSession = page.locator('[data-kg-xr-v2-immersive-session]')
const enterImmersive = page.locator('[data-kg-xr-v2-immersive-enter="1"]')
for (const control of [startCamera, stopCamera, enableSensors, disableSensors]) {
assert.equal(await control.count(), 1, 'camera and sensor actions must be separate controls')
}
assert.equal(await startSpatialCapture.count(), 1, 'missing explicit spatial capture action')
assert.equal(await stopSpatialCapture.count(), 1, 'missing explicit spatial save action')
assert.equal(await immersiveSession.count(), 1, 'missing tier-gated immersive session action')
assert.equal(await enterImmersive.count(), 1, 'missing explicit immersive entry action')
assert.equal(
await page.locator('[data-kg-canvas-xr-mode="1"]').count(),
0,
'generic XR session controls must stay unmounted while the pinned XR v2 owner is active',
)
assert.equal(await panel.getAttribute('data-kg-motion-control-runtime'), 'off')
assert.equal(await panel.getAttribute('data-kg-motion-control-device-sensors'), 'off')
assert.equal(await spatialCapture.getAttribute('data-kg-xr-v2-spatial-capture-phase'), 'idle')
assert.equal(await spatialCapture.getAttribute('data-kg-xr-v2-spatial-camera-requested'), 'false')
assert.equal(await spatialCapture.getAttribute('data-kg-xr-v2-spatial-sensors-requested'), 'false')
assert.equal(await immersiveSession.getAttribute('data-kg-xr-v2-immersive-permission-requested'), 'false')
assert.equal(await startCamera.isDisabled(), false)
assert.equal(await stopCamera.isDisabled(), true)
assert.equal(await enableSensors.isDisabled(), false)
assert.equal(await disableSensors.isDisabled(), true)
assert.equal(await startSpatialCapture.isDisabled(), true)
assert.equal(await stopSpatialCapture.isDisabled(), true)
const capabilityTier = await readiness.getAttribute('data-kg-xr-v2-capability-tier')
if (capabilityTier === 'webxr-ar' || capabilityTier === 'webxr-vr') {
assert.equal(await immersiveSession.getAttribute('data-kg-xr-v2-immersive-tier-admitted'), 'true')
} else {
assert.equal(await immersiveSession.getAttribute('data-kg-xr-v2-immersive-tier-admitted'), 'false')
assert.equal(await enterImmersive.isDisabled(), true)
}
assert.deepEqual(browserErrors, [])
console.log('XR v2 source-authored workspace seed browser smoke passed')
console.log('XR v2 Explorer-selected source-authored workspace seed browser smoke passed')
} finally {
await context.close()
await browser.close()
Expand Down
8 changes: 6 additions & 2 deletions canvas/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Suspense, lazy, useEffect, useLayoutEffect, useMemo } from 'react'
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
import ErrorBoundary from '@/components/ErrorBoundary'
import Canvas from '@/pages/Canvas'
import { cancelIdle, scheduleIdle } from '@/features/panels/utils/idle'
import { CanvasRouteRuntime } from '@/features/canvas/CanvasRouteRuntime'
import { LS_KEYS } from '@/lib/config.ls'
Expand All @@ -14,6 +13,7 @@ import { XrMotionReferenceRuntimeBridge } from '@/features/three/XrMotionReferen
import { CanvasSourceAuthorityBoundary } from '@/features/canvas/CanvasSourceAuthorityBoundary'
import { AgenticOsRemoteGrammarAutoHydrationBoundary } from '@/features/agentic-os/useAgenticOsRemoteGrammarAutoHydration'

const CanvasLazy = lazy(() => import('@/pages/Canvas'))
const PerformanceAutomationReadoutLazy = lazy(async () => ({
default: (await import('@/features/canvas/PerformanceAutomationReadout')).PerformanceAutomationReadout,
}))
Expand Down Expand Up @@ -246,7 +246,11 @@ export default function App() {
<Suspense fallback={null}>
<XrV2RuntimeSmokePageLazy />
</Suspense>
) : <Canvas />}
) : (
<Suspense fallback={null}>
<CanvasLazy />
</Suspense>
)}
/>
</Routes>
</ErrorBoundary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ export function testWorkspaceInitializationDocsAbsRootDefaultStaysOutOfProductio
}
}

export function testCiFlagIsExposedToBrowserRuntime() {
const viteConfigPath = path.resolve(process.cwd(), 'vite.config.ts')
const text = fs.readFileSync(viteConfigPath, 'utf8')
if (!text.includes("'import.meta.env.CI': JSON.stringify(process.env.CI === 'true')")) {
throw new Error('expected the browser runtime to receive the CI flag for renderer compile selection')
}
}

export function testProductionHtmlInlinesGeneratedStylesheetAssets() {
const viteConfigPath = path.resolve(process.cwd(), 'vite.config.ts')
const text = fs.readFileSync(viteConfigPath, 'utf8')
Expand Down
31 changes: 26 additions & 5 deletions canvas/src/__tests__/workspaceSeedActiveHydration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { CANONICAL_WORKSPACE_SEED_BASENAMES } from '@/features/workspace-fs/work
import { buildWorkspaceEntriesSemanticKey } from '@/features/workspace-fs/workspaceEntriesSemanticKey'
import { applyWorkspaceImportToCanvas } from '@/features/workspace-fs/applyWorkspaceImportToCanvas'
import { mergeWorkspaceEntriesIntoSourceFiles } from '@/features/workspace-fs/syncToSourceFiles'
import { resolveDocumentRepositoryAuthority } from 'grph-shared/collaboration/documentRepositoryAuthority'

const createMinimalFs = (overrides: Partial<WorkspaceFs> = {}): WorkspaceFs => ({
ensureSeed: async () => false,
Expand Down Expand Up @@ -80,7 +81,17 @@ export async function testMaterializeActiveWorkspaceEntryReadsActiveFileWithoutL
const { restore } = initJsdomHarness()
try {
useGraphStore.getState().resetAll()
const activePath = '/docs/active-only.md'
const activePath = '/docs/workspace-seeds/knowgrph-ar-vr-xr-runtime-readiness-demo.md'
const activeSourcePath = `workspace:${activePath}`
const activeText = [
'---',
'title: "Knowgrph AR/VR/XR runtime-readiness demo"',
'run_ready_demo:',
' id: "xr-v2"',
` canonical_source_file: "${activePath}"`,
'---',
'# XR v2 active seed',
].join('\n')
useMarkdownExplorerStore.getState().setActivePath(activePath)
let listEntriesCalls = 0
await materializeActiveWorkspaceEntryIntoSourceFiles({
Expand All @@ -90,15 +101,25 @@ export async function testMaterializeActiveWorkspaceEntryReadsActiveFileWithoutL
listEntriesCalls += 1
throw new Error('active materialization should not list the whole workspace')
},
readFileText: async path => (String(path || '').trim() === activePath ? '# active only' : null),
readFileText: async path => (String(path || '').trim() === activePath ? activeText : null),
}),
applyToGraph: false,
})
const sourceFiles = useGraphStore.getState().sourceFiles || []
const active = sourceFiles.find(file => String(file.source?.path || '') === 'workspace:/docs/active-only.md') || null
const active = sourceFiles.find(file => String(file.source?.path || '') === activeSourcePath) || null
if (listEntriesCalls !== 0) throw new Error(`expected active materialization not to list workspace entries, got ${listEntriesCalls}`)
if (!active || String(active.text || '').trim() !== '# active only') {
throw new Error(`expected active-only materialization to read only the active file, got ${JSON.stringify(sourceFiles)}`)
if (!active || String(active.text || '') !== activeText) {
throw new Error(`expected canonical XR active-only materialization to preserve ${activeSourcePath}, got ${JSON.stringify(sourceFiles)}`)
}
const repositoryAuthority = resolveDocumentRepositoryAuthority({
documentKey: activePath,
documentKind: 'markdown',
})
if (
repositoryAuthority?.repositoryTarget !== 'knowgrph-docs'
|| repositoryAuthority.canonicalPath !== 'knowgrph/docs/workspace-seeds/knowgrph-ar-vr-xr-runtime-readiness-demo.md'
) {
throw new Error(`expected active seed repository authority to remain rooted in knowgrph/docs, got ${JSON.stringify(repositoryAuthority)}`)
}
} finally {
restore()
Expand Down
Loading