@@ -218,13 +218,25 @@ export function OnboardingPreview({
<>
{firstBriefReady ? 'Your first picture is ready' : session?.stage === 'blocked' ? 'ACE needs your attention' : session === null ? 'Your governed plan is ready' : 'Your first picture is assembling'}
- {session === null ? 'These are proposed steps. No agent action is represented as complete until Core admits its durable session record.' : 'This status comes from the governed, append-only Intelligence Builder session—not UI animation.'}
+
+ {session === null
+ ? 'Review the plan before ACE connects sources or starts watching.'
+ : firstBriefReady
+ ? 'ACE built this picture from the sources and watch settings you approved.'
+ : session.stage === 'blocked'
+ ? 'ACE paused safely before changing your intelligence picture.'
+ : 'ACE is assembling the picture from the sources and watch settings you approved.'}
+
{lanes.map((lane) => )}
- {session === null ? 'Reviewing this plan grants no source, monitor, or activation authority.' : `${session.artifacts.length} exact builder artifact${session.artifacts.length === 1 ? '' : 's'} retained · session revision ${session.sequence}`}
+ {session === null
+ ? 'Reviewing this plan changes nothing until you approve it.'
+ : firstBriefReady
+ ? 'First cited Brief ready · Setup saved'
+ : `Setup saved · Step ${session.sequence}`}
>
)}
diff --git a/core/ui/canvas/tests/e2e/atrium-domain-resource-page.spec.ts b/core/ui/canvas/tests/e2e/atrium-domain-resource-page.spec.ts
index 7ff64a9..e50dd79 100644
--- a/core/ui/canvas/tests/e2e/atrium-domain-resource-page.spec.ts
+++ b/core/ui/canvas/tests/e2e/atrium-domain-resource-page.spec.ts
@@ -11,13 +11,6 @@ test('Atrium renders an external domain resource page without domain UI code', a
items: Array<{ reference: { resource_kind: string }; title: string }>
}
- await page.route('**/auth/token', (route) =>
- route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ token: 'fixture-token' }) }),
- )
- await page.route('**/v1/intelligence/resources/query', (route) =>
- route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(resourcePage) }),
- )
-
await page.goto('/atrium')
const brief = resourcePage.items.find((item) => item.reference.resource_kind === 'brief')
@@ -26,8 +19,24 @@ test('Atrium renders an external domain resource page without domain UI code', a
if (brief !== undefined) {
await expect(page.getByRole('button', { name: `Open ${brief.title}` })).toBeVisible()
}
+ if ((resourcePage as { state?: string }).state === 'degraded') {
+ await expect(page.getByText('Some evidence still needs review')).toBeVisible()
+ }
+ await expect(page.getByText(`${resourcePage.items.length} cited records`)).toBeVisible()
await page.screenshot({ path: testInfo.outputPath('atrium-domain-resource-page.png'), fullPage: true })
+ const viewBuild = page.getByRole('button', { name: 'View build' })
+ if (await viewBuild.isVisible()) {
+ await viewBuild.click()
+ await page.getByRole('button', { name: 'Continue' }).click()
+ await page.getByRole('button', { name: 'Continue' }).click()
+ await page.getByRole('button', { name: 'View live build' }).click()
+ await expect(page.getByRole('heading', { name: 'Your first picture is ready' })).toBeVisible()
+ await expect(page.getByText('ACE built this picture from the sources and watch settings you approved.')).toBeVisible()
+ await expect(page.getByText('First cited Brief ready · Setup saved')).toBeVisible()
+ await page.getByRole('button', { name: 'Open my first briefing' }).click()
+ }
+
await page.setViewportSize({ width: 390, height: 844 })
await page.goto('/atrium')
await expect(page.getByRole('heading', { name: 'Intelligence' })).toBeVisible()
diff --git a/core/ui/canvas/vite.config.ts b/core/ui/canvas/vite.config.ts
index 0252c5a..d611e68 100644
--- a/core/ui/canvas/vite.config.ts
+++ b/core/ui/canvas/vite.config.ts
@@ -103,6 +103,63 @@ function serveExtensionStaticHtml(): Plugin {
}
}
+/**
+ * Replay one immutable Intelligence resource page through Atrium during local demos.
+ *
+ * This is deliberately a Vite-only presentation seam. It does not create a store,
+ * admit evidence, grant authority, or exist in the production canvas host. A domain
+ * repository generates the page through Core's real resource projection, then points
+ * this local server at the resulting JSON artifact with ACE_ATRIUM_RESOURCE_PAGE.
+ */
+function serveAtriumResourceReplay(): Plugin {
+ const configuredPath = process.env.ACE_ATRIUM_RESOURCE_PAGE
+ if (configuredPath === undefined || configuredPath.trim() === '') {
+ return { name: 'ace:atrium-resource-replay-disabled' }
+ }
+
+ const resourcePath = path.resolve(configuredPath)
+ let resourceBody: string
+ try {
+ const raw = readFileSync(resourcePath, 'utf-8')
+ if (raw.length > 10_000_000) throw new Error('resource page exceeds the 10 MB local replay limit')
+ const parsed = JSON.parse(raw) as { contract?: unknown; items?: unknown }
+ if (
+ parsed.contract !== 'ace.intelligence.resource-plane-page/v1alpha1' ||
+ !Array.isArray(parsed.items)
+ ) {
+ throw new Error('resource page does not match the public Intelligence page contract')
+ }
+ resourceBody = JSON.stringify(parsed)
+ } catch (error) {
+ throw new Error(`ACE_ATRIUM_RESOURCE_PAGE could not be loaded from ${resourcePath}: ${String(error)}`)
+ }
+
+ return {
+ name: 'ace:atrium-resource-replay',
+ configureServer(server) {
+ server.config.logger.info(`Atrium is replaying ${resourcePath}`)
+ server.middlewares.use((req, res, next) => {
+ const [pathname] = (req.url ?? '').split('?')
+ if (req.method === 'POST' && pathname === '/auth/token') {
+ res.setHeader('Content-Type', 'application/json; charset=utf-8')
+ res.setHeader('Cache-Control', 'no-store')
+ res.statusCode = 200
+ res.end(JSON.stringify({ token: 'atrium-local-replay' }))
+ return
+ }
+ if (req.method === 'POST' && pathname === '/v1/intelligence/resources/query') {
+ res.setHeader('Content-Type', 'application/json; charset=utf-8')
+ res.setHeader('Cache-Control', 'no-store')
+ res.statusCode = 200
+ res.end(resourceBody)
+ return
+ }
+ next()
+ })
+ },
+ }
+}
+
/** The kernel's own routes. An extension may not claim any of these (fail-closed). */
const kernelProxy: Record
= Object.fromEntries(
KERNEL_DEV_PROXY_ROUTES.map(([route, ws]) => [
@@ -116,7 +173,7 @@ const kernelProxy: Record = Object.fromEntries(
)
export default defineConfig({
- plugins: [react(), tailwindcss(), serveExtensionStaticHtml()],
+ plugins: [serveAtriumResourceReplay(), react(), tailwindcss(), serveExtensionStaticHtml()],
// Yjs constructor checks break if two copies of the module end up in the
// bundle (one from `import * as Y from 'yjs'`, one from `y-websocket`
// pulling its own pre-bundled copy). Force a single resolved instance.
diff --git a/docs/evidence/atrium-live-onboarding-candidate-v1.md b/docs/evidence/atrium-live-onboarding-candidate-v1.md
index f4d5c76..a88b611 100644
--- a/docs/evidence/atrium-live-onboarding-candidate-v1.md
+++ b/docs/evidence/atrium-live-onboarding-candidate-v1.md
@@ -32,5 +32,8 @@ corroboration claim.
- the Atrium production build completes;
- focused World tests reproduce the four-agent journey and exact 23-resource Atrium page.
-Full repository, installed-wheel, accessibility, and paired-PR review gates remain required before
-this candidate advances to passed.
+The paired implementation landed through Core PR #145 and World PR #24 after the supported Core,
+World, Canvas, naked-kernel, Docker, installed-wheel, and browser gates passed. Post-landing demo
+hardening adds a Vite-only immutable-page replay seam and removes internal persistence vocabulary
+from the leadership-facing journey. Live network freshness, general accessibility review, and
+broader AI-area coverage remain separate follow-on gates.