From b1c1e720603b64acac89c1aa31aad6df7d1ee30b Mon Sep 17 00:00:00 2001 From: Edwin Amirian Date: Wed, 12 Aug 2026 20:49:29 -0700 Subject: [PATCH 1/3] feat(atrium): become the intelligence command center --- core/ui/canvas/src/app/atrium/AskAce.tsx | 51 +++++----- .../canvas/src/app/atrium/IntelligenceOS.tsx | 96 +++++++++++++++---- .../ui/canvas/src/app/atrium/ResourceCard.tsx | 61 +++++++++--- .../src/app/atrium/experienceModel.test.ts | 22 +++++ .../canvas/src/app/atrium/experienceModel.ts | 49 ++++++++++ .../canvas/src/app/ext/defaults/KernelNav.tsx | 7 ++ core/ui/canvas/src/index.css | 32 +++++++ .../e2e/atrium-domain-resource-page.spec.ts | 35 +++++++ ...atrium-command-center-reference-lock-v1.md | 58 +++++++++++ 9 files changed, 354 insertions(+), 57 deletions(-) create mode 100644 core/ui/canvas/src/app/atrium/experienceModel.test.ts create mode 100644 core/ui/canvas/src/app/atrium/experienceModel.ts create mode 100644 core/ui/canvas/tests/e2e/atrium-domain-resource-page.spec.ts create mode 100644 docs/design/atrium-command-center-reference-lock-v1.md diff --git a/core/ui/canvas/src/app/atrium/AskAce.tsx b/core/ui/canvas/src/app/atrium/AskAce.tsx index 50c3893..00f53a9 100644 --- a/core/ui/canvas/src/app/atrium/AskAce.tsx +++ b/core/ui/canvas/src/app/atrium/AskAce.tsx @@ -1,5 +1,5 @@ import { useMemo, useState } from 'react' -import { ArrowRight, Search, ShieldCheck } from 'lucide-react' +import { ArrowRight, Search, ShieldCheck, Sparkles } from 'lucide-react' import type { IntelligenceResourceRecord } from '@/api/intelligenceResourcesApi' import { Badge } from '@/design/shadcn/ui/badge' @@ -31,37 +31,40 @@ export function AskAce({ items }: { readonly items: readonly IntelligenceResourc } return ( - - -
-
- + + +
+
+
Ask ACE
-
Answers from your governed intelligence
+
A sourced answer from the intelligence currently in view
- + - cited + governed sources
-
- setDraft(event.target.value)} - onKeyDown={(event) => { - if (event.key === 'Enter') ask() - }} - placeholder="Ask about competitors, shifts, evidence, or decisions" - aria-label="Ask ACE about current intelligence" - className="h-11 border-foreground/15 bg-background text-foreground placeholder:text-muted-foreground" - /> +
+
+ + setDraft(event.target.value)} + onKeyDown={(event) => { + if (event.key === 'Enter') ask() + }} + placeholder="Ask about competitors, shifts, evidence, or decisions" + aria-label="Ask ACE about current intelligence" + className="h-11 border-border bg-background pl-10 text-foreground placeholder:text-muted-foreground focus-visible:border-brand/50" + /> +
{question.length === 0 ? ( -
+
{SUGGESTIONS.map((suggestion) => (
) : ( -
+
Intelligence answer · {matches.length} cited record{matches.length === 1 ? '' : 's'}
diff --git a/core/ui/canvas/src/app/atrium/IntelligenceOS.tsx b/core/ui/canvas/src/app/atrium/IntelligenceOS.tsx index 668ebe1..5d7f2f9 100644 --- a/core/ui/canvas/src/app/atrium/IntelligenceOS.tsx +++ b/core/ui/canvas/src/app/atrium/IntelligenceOS.tsx @@ -6,9 +6,13 @@ import { Bot, BrainCircuit, CircleAlert, + Clock3, + Layers3, Network, + Radio, RefreshCw, Route, + ShieldCheck, } from 'lucide-react' import type { IntelligenceResourceRecord } from '@/api/intelligenceResourcesApi' @@ -21,6 +25,7 @@ import { Skeleton } from '@/design/shadcn/ui/skeleton' import { KernelNav } from '../ext/defaults/KernelNav' import { AskAce } from './AskAce' +import { pageFreshness, productDisplayName } from './experienceModel' import { EXPLICITLY_DEGRADED_RESOURCE_KINDS, groupResources, @@ -123,10 +128,12 @@ function ResourceGrid({ items, empty, single = false, + compact = false, }: { readonly items: readonly IntelligenceResourceRecord[] readonly empty: string readonly single?: boolean + readonly compact?: boolean }) { if (items.length === 0) { return ( @@ -138,7 +145,7 @@ function ResourceGrid({ return (
{items.map((item) => ( - + ))}
) @@ -147,19 +154,22 @@ function ResourceGrid({ function BriefingHome({ groups, all }: { readonly groups: ResourceGroups; readonly all: IntelligenceResourceRecord[] }) { const briefs = groups.intelligence.filter((item) => item.reference.resource_kind === 'brief') const latestBrief = briefs[0] + const stream = groups.intelligence + .filter((item) => item !== latestBrief && ['signal', 'shift', 'brief'].includes(item.reference.resource_kind)) + .slice(0, 6) return ( -
+
-
+
-
Latest briefing
+
Latest briefing

The situation now

- {briefs.length} brief{briefs.length === 1 ? '' : 's'} + {briefs.length} current
{latestBrief === undefined ? ( @@ -170,12 +180,49 @@ function BriefingHome({ groups, all }: { readonly groups: ResourceGroups; readon
+ +
+
+
+
Live intelligence
+

What is moving

+
+ {stream.length} updates +
+ +
+
+ ) +} + +function CoverageStrip({ groups, freshness }: { readonly groups: ResourceGroups; readonly freshness: string }) { + const sources = groups.connections.filter((item) => item.reference.resource_kind === 'source').length + const monitors = groups.agents.filter((item) => item.reference.resource_kind === 'monitor').length + const openCases = groups.opportunities.filter((item) => item.reference.resource_kind === 'case').length + const entries = [ + { icon: Radio, label: 'Sources', value: `${sources} admitted` }, + { icon: Activity, label: 'Watches', value: `${monitors} active` }, + { icon: Layers3, label: 'Open cases', value: `${openCases} material` }, + { icon: Clock3, label: 'Freshness', value: freshness }, + ] + + return ( +
+ {entries.map((entry, index) => ( +
+ +
+
{entry.label}
+
{entry.value}
+
+
+ ))}
) } @@ -272,21 +319,30 @@ export function IntelligenceOS() { const copy = SURFACE_COPY[surface] const { page, loading, error, refresh } = useIntelligenceResources() const groups = useMemo(() => groupResources(page?.items ?? []), [page?.items]) + const productName = productDisplayName(page?.product_id) + const freshness = pageFreshness(page) return ( - - - -
+
+ + + +
-

{copy.title}

-

{copy.subtitle}

+
+ ACE / {productName} +
+
+

{copy.title}

+

{copy.subtitle}

+
{page !== null && ( - - {page.state === 'degraded' ? 'Partial intelligence' : 'Intelligence current'} + + {page.state === 'degraded' ? : } + {page.state === 'degraded' ? 'Partial picture' : 'Picture current'} )}
) } diff --git a/core/ui/canvas/src/app/atrium/ResourceCard.tsx b/core/ui/canvas/src/app/atrium/ResourceCard.tsx index fe6acc5..33be044 100644 --- a/core/ui/canvas/src/app/atrium/ResourceCard.tsx +++ b/core/ui/canvas/src/app/atrium/ResourceCard.tsx @@ -15,6 +15,7 @@ import { import { Separator } from '@/design/shadcn/ui/separator' import { compactReference, kindLabel } from './intelligenceModel' +import { payloadNumber, payloadText } from './experienceModel' function availabilityLabel(record: IntelligenceResourceRecord): string { if (record.availability === 'degraded') return 'Needs context' @@ -34,47 +35,56 @@ function relativeTime(value: string): string { export function ResourceCard({ record, featured = false, + compact = false, }: { readonly record: IntelligenceResourceRecord readonly featured?: boolean + readonly compact?: boolean }) { + const whyItMatters = payloadText(record.payload, 'why_it_matters') + const confidence = payloadNumber(record.payload, 'confidence') + const confidencePercent = confidence !== null && confidence >= 0 && confidence <= 1 + ? Math.round(confidence * 100) + : null + return ( ) @@ -151,7 +150,7 @@ function ResourceGrid({ ) } -function BriefingHome({ groups, all }: { readonly groups: ResourceGroups; readonly all: IntelligenceResourceRecord[] }) { +function BriefingHome({ groups, all, onStart }: { readonly groups: ResourceGroups; readonly all: IntelligenceResourceRecord[]; readonly onStart: () => void }) { const briefs = groups.intelligence.filter((item) => item.reference.resource_kind === 'brief') const latestBrief = briefs[0] const stream = groups.intelligence @@ -172,7 +171,7 @@ function BriefingHome({ groups, all }: { readonly groups: ResourceGroups; readon {briefs.length} current
{latestBrief === undefined ? ( - + ) : ( )} @@ -291,8 +290,8 @@ function AgentRole({ title, detail, state }: { readonly title: string; readonly ) } -function PageContent({ surface, groups, all }: { readonly surface: Surface; readonly groups: ResourceGroups; readonly all: IntelligenceResourceRecord[] }) { - if (surface === 'intelligence') return +function PageContent({ surface, groups, all, onStart }: { readonly surface: Surface; readonly groups: ResourceGroups; readonly all: IntelligenceResourceRecord[]; readonly onStart: () => void }) { + if (surface === 'intelligence') return if (surface === 'connections') return if (surface === 'agents') return if (surface === 'opportunities') { @@ -321,6 +320,8 @@ export function IntelligenceOS() { const groups = useMemo(() => groupResources(page?.items ?? []), [page?.items]) const productName = productDisplayName(page?.product_id) const freshness = pageFreshness(page) + const [onboardingOpen, setOnboardingOpen] = useState(false) + const onboardingProfile = useMemo(() => onboardingProfileFromResources(page?.items ?? []), [page?.items]) return (
@@ -378,7 +379,7 @@ export function IntelligenceOS() { ) : ( <> - + setOnboardingOpen(true)} /> )} @@ -391,6 +392,7 @@ export function IntelligenceOS() { · exact provenance retained +
diff --git a/core/ui/canvas/src/app/atrium/OnboardingPreview.tsx b/core/ui/canvas/src/app/atrium/OnboardingPreview.tsx new file mode 100644 index 0000000..3b62a7e --- /dev/null +++ b/core/ui/canvas/src/app/atrium/OnboardingPreview.tsx @@ -0,0 +1,140 @@ +import { useMemo, useState } from 'react' +import { + ArrowLeft, + ArrowRight, + BarChart3, + Check, + CircleDot, + Compass, + FlaskConical, + Gauge, + Radar, + Scale, + ShieldAlert, + Sparkles, +} from 'lucide-react' + +import { Badge } from '@/design/shadcn/ui/badge' +import { Button } from '@/design/shadcn/ui/button' +import { Card, CardContent } from '@/design/shadcn/ui/card' +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/design/shadcn/ui/dialog' +import type { IntelligenceOnboardingOutcome, IntelligenceOnboardingProfile } from './onboardingModel' + +const ICONS: Record = { + choice: Gauge, + strategy: BarChart3, + research: FlaskConical, + risk: ShieldAlert, + competition: Radar, + custom: Compass, +} + +function OutcomeIcon({ outcome }: { readonly outcome: IntelligenceOnboardingOutcome }) { + const Icon = ICONS[outcome.icon_hint] ?? Compass + return +} + +export function OnboardingPreview({ open, onOpenChange, profile }: { readonly open: boolean; readonly onOpenChange: (open: boolean) => void; readonly profile: IntelligenceOnboardingProfile }) { + const [step, setStep] = useState(0) + const [outcomeId, setOutcomeId] = useState(profile.outcomes[0]?.outcome_id ?? '') + const [cadenceId, setCadenceId] = useState(profile.default_cadence_id) + const outcome = useMemo(() => profile.outcomes.find((item) => item.outcome_id === outcomeId) ?? profile.outcomes[0], [outcomeId, profile.outcomes]) + + function close(next: boolean) { + onOpenChange(next) + if (!next) setStep(0) + } + + return ( + + +
+
+ Build your intelligence +
+
+ {[0, 1, 2, 3].map((item) =>
)} +
+
+ +
+ {step === 0 && ( + <> + + {profile.prompt} + {profile.description} + +
+ {profile.outcomes.map((item) => { + const selected = item.outcome_id === outcomeId + return ( + + ) + })} +
+ + )} + + {step === 1 && ( + <> + + Tune the picture + ACE recommends a complete starting view for {outcome.label.toLowerCase()}. You can refine it later. + +
+
Recommended coverage
{outcome.recommended_topic_labels.length > 0 ? outcome.recommended_topic_labels.map((topic) => {topic}) : Choose topics after continuing.}
You can add specific entities, organizations, products, policies, or technologies next.

ACE asks only for details it cannot safely infer.

+
How often should ACE orient you?
{profile.cadences.map((cadence) => { const selected = cadence.cadence_id === cadenceId; return })}
+
+ + )} + + {step === 2 && ( + <> + Review what ACE will buildPublic evidence creates the first picture. Private sources remain optional and require explicit permission. +
+ + + + 0 ? outcome.recommended_intelligence_labels.join(' · ') : 'ACE will propose intelligence products from your custom questions.'} /> +
+

Nothing is connected or activated silently. You will see every requested permission, every proposed source that remains unconnected, and every watch before it receives authority.

+ + )} + + {step === 3 && ( + <> + Your first picture is assemblingACE's governed agents work as one team. Inspect them when you need to; otherwise, follow the outcomes. +
+ + + + + +
+
This preview demonstrates the experience contract. Live activation still follows ACE's governed Connect → Map → Watch → Brief → Activate lifecycle.
+ + )} +
+ +
+ + {step < 3 ? : } +
+ +
+ ) +} + +function PlanCard({ label, value, detail }: { readonly label: string; readonly value: string | number; readonly detail: string }) { + return
{label}
{value}

{detail}

+} + +function BuildStep({ label, result, featured = false }: { readonly label: string; readonly result: string; readonly featured?: boolean }) { + return
{label}
{result}
{featured && First value}
+} diff --git a/core/ui/canvas/src/app/atrium/onboardingModel.test.ts b/core/ui/canvas/src/app/atrium/onboardingModel.test.ts new file mode 100644 index 0000000..979dca6 --- /dev/null +++ b/core/ui/canvas/src/app/atrium/onboardingModel.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from 'vitest' + +import type { IntelligenceResourceRecord } from '@/api/intelligenceResourcesApi' +import { onboardingProfileFromResources, parseOnboardingProfile } from './onboardingModel' + +const profile = { + contract: 'ace.domain-pack.intelligence-onboarding-profile/v1alpha1', + display_name: 'Test Intelligence', + prompt: 'What matters?', + description: 'Choose an outcome.', + outcomes: [{ + outcome_id: 'test', + label: 'Track the test', + description: 'Follow material test changes.', + icon_hint: 'research', + recommended_topic_labels: ['Tests'], + recommended_intelligence_labels: ['Test movement'], + }], + cadences: [{ cadence_id: 'weekly', label: 'Weekly', description: 'Once a week.' }], + default_cadence_id: 'weekly', + first_value: { completion_label: 'Open the test brief' }, +} + +describe('Atrium onboarding profile', () => { + it('accepts a bounded declarative profile', () => { + expect(parseOnboardingProfile(profile)).toMatchObject({ + display_name: 'Test Intelligence', + completion_label: 'Open the test brief', + }) + }) + + it('rejects unknown or malformed payloads', () => { + expect(parseOnboardingProfile({ ...profile, contract: 'unknown' })).toBeNull() + expect(parseOnboardingProfile({ ...profile, outcomes: [] })).toBeNull() + }) + + it('reads only an admitted context-manifest projection and otherwise stays domain-neutral', () => { + const record = { + reference: { resource_kind: 'context_manifest' }, + payload: { onboarding_profile: profile }, + } as IntelligenceResourceRecord + expect(onboardingProfileFromResources([record]).display_name).toBe('Test Intelligence') + expect(onboardingProfileFromResources([]).display_name).toBe('Your Intelligence') + expect(onboardingProfileFromResources([]).outcomes.some((item) => item.label.includes('AI'))).toBe(false) + }) +}) diff --git a/core/ui/canvas/src/app/atrium/onboardingModel.ts b/core/ui/canvas/src/app/atrium/onboardingModel.ts new file mode 100644 index 0000000..0422f76 --- /dev/null +++ b/core/ui/canvas/src/app/atrium/onboardingModel.ts @@ -0,0 +1,158 @@ +import type { IntelligenceResourceRecord } from '@/api/intelligenceResourcesApi' + +export interface IntelligenceOnboardingOutcome { + readonly outcome_id: string + readonly label: string + readonly description: string + readonly icon_hint: string + readonly recommended_topic_labels: readonly string[] + readonly recommended_intelligence_labels: readonly string[] +} + +export interface IntelligenceOnboardingCadence { + readonly cadence_id: string + readonly label: string + readonly description: string +} + +export interface IntelligenceOnboardingProfile { + readonly contract: 'ace.domain-pack.intelligence-onboarding-profile/v1alpha1' + readonly display_name: string + readonly prompt: string + readonly description: string + readonly outcomes: readonly IntelligenceOnboardingOutcome[] + readonly cadences: readonly IntelligenceOnboardingCadence[] + readonly default_cadence_id: string + readonly completion_label: string +} + +const FALLBACK_PROFILE: IntelligenceOnboardingProfile = { + contract: 'ace.domain-pack.intelligence-onboarding-profile/v1alpha1', + display_name: 'Your Intelligence', + prompt: 'What do you need to stay ahead of?', + description: 'Choose the decision context. ACE will recommend the evidence, concepts, watches, and briefing system.', + outcomes: [ + { + outcome_id: 'choice', + label: 'Make a product or technology choice', + description: 'Compare the options, trade-offs, evidence, and operating implications that matter.', + icon_hint: 'choice', + recommended_topic_labels: ['Options', 'Evidence', 'Cost', 'Performance', 'Risk'], + recommended_intelligence_labels: ['Comparative movement', 'Claim versus evidence'], + }, + { + outcome_id: 'strategy', + label: 'Set strategy or evaluate investments', + description: 'Track the forces, commitments, and outcomes shaping durable advantage.', + icon_hint: 'strategy', + recommended_topic_labels: ['Market forces', 'Investment', 'Capabilities', 'Adoption', 'Outcomes'], + recommended_intelligence_labels: ['Momentum', 'Constraints', 'Execution gaps'], + }, + { + outcome_id: 'frontier', + label: 'Track emerging change', + description: 'Follow early signals as they become material products, policies, or behavior.', + icon_hint: 'research', + recommended_topic_labels: ['Research', 'Products', 'Leading indicators', 'Adoption'], + recommended_intelligence_labels: ['Diffusion', 'Material shifts'], + }, + { + outcome_id: 'risk', + label: 'Manage policy and operational risk', + description: 'Watch rules, incidents, dependencies, safeguards, and implementation gaps.', + icon_hint: 'risk', + recommended_topic_labels: ['Policy', 'Incidents', 'Reliability', 'Dependencies'], + recommended_intelligence_labels: ['Implementation gaps', 'Risk movement'], + }, + { + outcome_id: 'competition', + label: 'Understand the competitive landscape', + description: 'Compare organizations through claims, investment, capability, and execution.', + icon_hint: 'competition', + recommended_topic_labels: ['Organizations', 'Offerings', 'Claims', 'Investment', 'Execution'], + recommended_intelligence_labels: ['Position movement', 'Strategy before announcement'], + }, + { + outcome_id: 'custom', + label: 'Build a custom picture', + description: 'Choose the entities, questions, evidence, thresholds, and cadence that matter to you.', + icon_hint: 'custom', + recommended_topic_labels: [], + recommended_intelligence_labels: [], + }, + ], + cadences: [ + { cadence_id: 'urgent', label: 'Urgent only', description: 'Only material thresholds, contradictions, and incidents.' }, + { cadence_id: 'daily', label: 'Daily pulse', description: 'A concise daily orientation plus urgent alerts.' }, + { cadence_id: 'weekly', label: 'Weekly briefing', description: "The week's movement, open questions, and next catalysts." }, + ], + default_cadence_id: 'weekly', + completion_label: 'Open my first briefing', +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value) +} + +function strings(value: unknown): readonly string[] | null { + return Array.isArray(value) && value.every((item) => typeof item === 'string') ? value : null +} + +function parseOutcome(value: unknown): IntelligenceOnboardingOutcome | null { + if (!isRecord(value)) return null + const topics = strings(value.recommended_topic_labels) + const intelligence = strings(value.recommended_intelligence_labels) + if ( + typeof value.outcome_id !== 'string' || typeof value.label !== 'string' || + typeof value.description !== 'string' || typeof value.icon_hint !== 'string' || + topics === null || intelligence === null + ) return null + return { + outcome_id: value.outcome_id, + label: value.label, + description: value.description, + icon_hint: value.icon_hint, + recommended_topic_labels: topics, + recommended_intelligence_labels: intelligence, + } +} + +function parseCadence(value: unknown): IntelligenceOnboardingCadence | null { + if (!isRecord(value)) return null + if (typeof value.cadence_id !== 'string' || typeof value.label !== 'string' || typeof value.description !== 'string') return null + return { cadence_id: value.cadence_id, label: value.label, description: value.description } +} + +export function parseOnboardingProfile(value: unknown): IntelligenceOnboardingProfile | null { + if (!isRecord(value) || value.contract !== 'ace.domain-pack.intelligence-onboarding-profile/v1alpha1') return null + if ( + typeof value.display_name !== 'string' || typeof value.prompt !== 'string' || + typeof value.description !== 'string' || typeof value.default_cadence_id !== 'string' + ) return null + const outcomes = Array.isArray(value.outcomes) ? value.outcomes.map(parseOutcome) : [] + const cadences = Array.isArray(value.cadences) ? value.cadences.map(parseCadence) : [] + if (outcomes.length === 0 || outcomes.some((item) => item === null) || cadences.length === 0 || cadences.some((item) => item === null)) return null + const firstValue = isRecord(value.first_value) ? value.first_value : null + const completionLabel = firstValue !== null && typeof firstValue.completion_label === 'string' + ? firstValue.completion_label + : 'Open my first briefing' + return { + contract: value.contract, + display_name: value.display_name, + prompt: value.prompt, + description: value.description, + outcomes: outcomes as IntelligenceOnboardingOutcome[], + cadences: cadences as IntelligenceOnboardingCadence[], + default_cadence_id: value.default_cadence_id, + completion_label: completionLabel, + } +} + +export function onboardingProfileFromResources(items: readonly IntelligenceResourceRecord[]): IntelligenceOnboardingProfile { + for (const item of items) { + if (item.reference.resource_kind !== 'context_manifest' || !isRecord(item.payload)) continue + const profile = parseOnboardingProfile(item.payload.onboarding_profile) + if (profile !== null) return profile + } + return FALLBACK_PROFILE +} diff --git a/core/ui/canvas/tests/e2e/atrium-intelligence-os.spec.ts b/core/ui/canvas/tests/e2e/atrium-intelligence-os.spec.ts index 7d9f2c3..c7f2405 100644 --- a/core/ui/canvas/tests/e2e/atrium-intelligence-os.spec.ts +++ b/core/ui/canvas/tests/e2e/atrium-intelligence-os.spec.ts @@ -112,3 +112,72 @@ test('Atrium is a briefing-first Intelligence OS over governed resources', async await page.getByRole('button', { name: 'Toggle Sidebar' }).click() await expect(page.getByText('Connections', { exact: true })).toBeVisible() }) + +test('Atrium empty state starts with the user job and previews a complete first-Brief journey', async ({ page }, testInfo) => { + const onboardingProfile = { + contract: 'ace.domain-pack.intelligence-onboarding-profile/v1alpha1', + display_name: 'AI Command Center', + prompt: 'What do you need to stay ahead of?', + description: 'Choose the AI decision context. ACE recommends the sources, concepts, watches, and briefing system.', + outcomes: [ + { outcome_id: 'strategy', label: 'Set strategy or evaluate investments', description: 'See which capital and capability moves are becoming durable advantage.', icon_hint: 'strategy', recommended_topic_labels: ['Capital', 'Capabilities'], recommended_intelligence_labels: ['Capital-to-capability'] }, + { outcome_id: 'frontier', label: 'Track frontier research and products', description: 'Follow advances into evaluated products.', icon_hint: 'research', recommended_topic_labels: ['Open research', 'Models & capabilities'], recommended_intelligence_labels: ['Research-to-product diffusion'] }, + ], + cadences: [ + { cadence_id: 'daily', label: 'Daily pulse', description: 'A concise daily orientation.' }, + { cadence_id: 'weekly', label: 'Weekly briefing', description: "The week's movement." }, + ], + default_cadence_id: 'weekly', + first_value: { completion_label: 'Open my first briefing' }, + } + const contextManifest = { + ...resource('context_manifest', 'world-ai-onboarding', 'AI intelligence setup', 'The reviewed first-run profile.'), + payload: { onboarding_profile: onboardingProfile }, + } + await page.route('**/auth/token', (route) => + route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ token: 'test-token' }) }), + ) + await page.route('**/v1/intelligence/resources/query', (route) => + route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + contract: 'ace.intelligence.resource-plane-page/v1alpha1', + query_id: 'resource_query:empty', + query_digest: `sha256:${'d'.repeat(64)}`, + product_id: 'product:world-ai-command-center', + actor_ref: 'principal:demo-analyst', + as_of: availableAt, + available_at: availableAt, + evaluated_at: availableAt, + state: 'complete', + items: [contextManifest], + next_cursor: null, + degraded_reason_refs: [], + page_id: 'resource_page:empty', + page_digest: `sha256:${'e'.repeat(64)}`, + }), + }), + ) + + await page.goto('/atrium') + await expect(page.getByRole('heading', { name: 'What do you need to stay ahead of?' })).toBeVisible() + await page.getByRole('button', { name: 'Build my intelligence' }).click() + + await expect(page.getByRole('heading', { name: 'What do you need to stay ahead of?' })).toBeVisible() + if (process.env.ACE_CAPTURE_ATRIUM === '1') { + await page.screenshot({ path: testInfo.outputPath('atrium-onboarding-outcome.png'), fullPage: true }) + } + await page.getByRole('button', { name: /Track frontier research and products/ }).click() + await page.getByRole('button', { name: 'Continue' }).click() + await expect(page.getByRole('heading', { name: 'Tune the picture' })).toBeVisible() + await page.getByRole('button', { name: 'Daily pulse' }).click() + await page.getByRole('button', { name: 'Continue' }).click() + await expect(page.getByRole('heading', { name: 'Review what ACE will build' })).toBeVisible() + await expect(page.getByText('Nothing is connected or activated silently.')).toBeVisible() + await page.getByRole('button', { name: 'Start watching' }).click() + await expect(page.getByRole('heading', { name: 'Your first picture is assembling' })).toBeVisible() + await expect(page.getByText('First value')).toBeVisible() + await page.getByRole('button', { name: 'Open my first briefing' }).click() + await expect(page.getByRole('dialog')).not.toBeVisible() +}) diff --git a/docs/README.md b/docs/README.md index 6b79235..1304cba 100644 --- a/docs/README.md +++ b/docs/README.md @@ -69,6 +69,9 @@ support the public roadmap but do not compete with it for outcome state or dispa - [State Engine Core-boundary readiness addendum](design/state-engine-core-boundary-readiness-v1.md) — K2/K3 bounded readiness delta while preserving the frozen TP8 boundary input. - [Intelligence Builder onboarding sequence](design/guided-intelligence-bootstrap-v0.7.0-work-packet-v1.md) +- [Atrium JTBD onboarding and AI command-center reference lock](design/atrium-jtbd-onboarding-reference-lock-v1.md) + — outcome-led first use, AI command-center jobs, domain onboarding-profile boundary, researched + UI references, and the clean-install-to-first-Brief acceptance journey. — the cumulative 0.7A–0.7E Connect → Map → Watch → Brief → Activate contracts, authority boundaries, state machine, and full-demo acceptance. - [Intelligence OS Realignment](design/intelligence-os-realignment-v0.8.0-work-packet-v1.md) — the diff --git a/docs/design/atrium-command-center-reference-lock-v1.md b/docs/design/atrium-command-center-reference-lock-v1.md index 937842a..600eaea 100644 --- a/docs/design/atrium-command-center-reference-lock-v1.md +++ b/docs/design/atrium-command-center-reference-lock-v1.md @@ -15,6 +15,11 @@ The primary user is an executive or analyst who needs to answer four questions: The golden path is `connect -> orient -> watch -> brief -> inspect -> decide`. +The outcome-led first-run journey and the AI command-center jobs are frozen separately in +[Atrium JTBD onboarding and AI command-center reference lock](atrium-jtbd-onboarding-reference-lock-v1.md). +That document supersedes agent-led or connector-grid onboarding concepts while preserving this +command-center visual direction. + ## Reference lock The visual system is anchored in three researched patterns: @@ -51,6 +56,8 @@ timestamps, counts, and provenance. - command-line cosplay; - a World-specific route or hard-coded World vocabulary inside Core; - a separate World dashboard that forks the Atrium experience. +- a source-catalog wall or agent-by-agent configuration wizard during first use; +- a blank dashboard or a second guided tour after the first Brief is ready. ## Media strategy diff --git a/docs/design/atrium-intelligence-experience-v0.8.0-work-packet-v1.md b/docs/design/atrium-intelligence-experience-v0.8.0-work-packet-v1.md index fd4e7f8..7176afa 100644 --- a/docs/design/atrium-intelligence-experience-v0.8.0-work-packet-v1.md +++ b/docs/design/atrium-intelligence-experience-v0.8.0-work-packet-v1.md @@ -54,6 +54,10 @@ Research was intentionally locked before implementation: Connect → Map → Watch path. It never substitutes prepared intelligence for an empty live system. 7. **Investigation remains downstream.** The existing deliberation and board experiences remain reachable as investigation tools; neither becomes another intelligence source of truth. +8. **Jobs before agents.** First use asks what decision or landscape the user needs to stay ahead + of. A Domain Pack proposes sources, concepts, watches, and cadence; agent work appears as one + inspectable assembly story. The full reference is the + [Atrium JTBD onboarding and AI command-center lock](atrium-jtbd-onboarding-reference-lock-v1.md). ## Implemented slice diff --git a/docs/design/atrium-jtbd-onboarding-reference-lock-v1.md b/docs/design/atrium-jtbd-onboarding-reference-lock-v1.md new file mode 100644 index 0000000..d7ac17e --- /dev/null +++ b/docs/design/atrium-jtbd-onboarding-reference-lock-v1.md @@ -0,0 +1,207 @@ +# Atrium JTBD onboarding and AI command-center reference lock v1 + +## Product outcome + +Atrium helps a person keep a trustworthy, continuously updated picture of a subject that changes +faster than they can follow it. The first flagship experience is World Intelligence focused on AI, +but the shell and onboarding contract remain domain-neutral. + +The primary job is: + +> When AI changes faster than I can follow, help me understand what materially changed, why it +> matters to me, what deserves attention, and what I can trust so I can decide without reading +> everything. + +The user does not install agents, author an ontology, or choose reasoning machinery. They state the +decision context; ACE proposes the evidence, concepts, watches, and briefing system required to +serve it. + +## Jobs to be done + +| Job | Question Atrium must answer | Product response | +|---|---|---| +| Orient | What changed since I last looked? | A short `Since your last visit` narrative and the three to five most material Shifts. | +| Prioritize | What deserves my attention now? | An attention rail ordered by materiality, relevance, recency, and evidence quality. | +| Explain | Why does this matter to my role or decision? | Each Shift carries a plain-language implication and affected watch or decision. | +| Compare | How did providers, models, costs, reliability, and adoption move relative to each other? | Domain-configured intelligence products such as capability-per-dollar and claim-versus-reality. | +| Verify | Is this fact, a first-party claim, an inference, disputed, or unknown? | Evidence roles, corroboration, conflicts, uncertainty, citations, and exact lineage remain inspectable. | +| Track | What entities, topics, commitments, and thresholds am I watching? | A concise watchlist with status, next catalyst, and editable relevance. | +| Anticipate | What weak signals may precede an announcement or constraint? | Leading-indicator Cases that remain visibly distinct from established Shifts. | +| Decide | What opportunity, risk, or investigation follows? | A bounded next step into Opportunities, Strategy, or downstream investigation. | +| Learn | Can this become more useful without silently changing truth or authority? | Useful/not-useful feedback reweights relevance; authority, evidence, and policy do not self-widen. | + +## First-run journey + +The magic moment is `choose a job -> accept a recommended watch system -> see the first cited +briefing`. Public evidence can produce first value before the user connects private data. + +### 1. Choose the outcome + +The first screen asks one question: **What do you need to stay ahead of?** A Domain Pack supplies a +small set of outcome choices. The World AI pack starts with: + +- choose or buy AI; +- set strategy or evaluate investments; +- track frontier research and products; +- manage policy, safety, and operational risk; +- understand the competitive landscape; and +- build a custom picture. + +These are decision contexts, not personas, agent names, or feature categories. + +### 2. Tune the picture + +ACE recommends topics, entities, and a delivery cadence for the selected outcome. The user may edit +the recommendation, but the default is complete enough to continue. AI topics include models and +capabilities, independent evaluations, economics, reliability, open research, security, policy, +capital, compute, talent, adoption, and executive narratives. + +The UI asks only for information ACE cannot safely infer: + +- named entities or technologies the user specifically cares about; +- desired cadence: urgent only, daily pulse, or weekly briefing; and +- optional private sources the user is authorized to connect. + +### 3. Review what ACE will build + +The review is plain language: + +- sources ACE recommends and why each evidence role is needed; +- concepts and relationships ACE proposes to map; +- watches and materiality rules ACE proposes to activate; +- expected first intelligence products; and +- permissions, gaps, and sources that remain proposed rather than connected. + +One primary action—**Start watching**—admits the reviewed plan through the existing governed +onboarding and activation lifecycle. + +### 4. Watch the system assemble + +Agent work appears as one compact progress story, not as five configuration screens: + +1. finding and validating sources; +2. mapping entities and concepts; +3. building watches; +4. checking coverage and contradictions; and +5. assembling the first cited Brief. + +Progress is expressed as outcomes such as `18 sources ready`, `42 entities mapped`, `6 watches +active`, and `first Brief ready`. Agent identity, receipts, permissions, and failures remain +available in an inspection drawer. + +### 5. Land in a populated Atrium + +The completion action opens the first Brief inside the normal command center. There is no separate +tour and no blank dashboard. Onboarding remains resumable from Connections and editable from +Agents, but it stops interrupting normal use. + +## AI command-center information architecture + +### Above the fold + +1. **Since your last visit** — a concise orientation narrative scoped to the selected job. +2. **Material movement** — three to five ranked Shifts with `why it matters to you`. +3. **Attention** — conflicts, weakly supported claims, expiring evidence, material Cases, and + upcoming catalysts. +4. **Ask ACE** — grounded search over the current governed picture, with cited revisions and an + explicit insufficient-evidence response. +5. **Picture health** — a single compact strip for active watches, admitted evidence, freshness, + conflicts, and unknowns. + +### Intelligence modules + +The World AI Domain Pack configures modules; Core renders them as projections over canonical +resources: + +- capability-per-dollar frontier; +- claim versus independent reality; +- research-to-product diffusion; +- capital-to-capability conversion; +- infrastructure bottlenecks; +- regulation-to-implementation gap; +- strategy before announcement; +- executive promise tracker; and +- adoption-versus-trust gap. + +Supporting views include a watchlist, an evidence timeline, upcoming catalysts, entity comparison, +and a source/provenance drawer. Atrium never turns a source catalog into a feed wall. Sources are +visible through coverage, citations, disagreements, and health. + +## Agent experience model + +| User-facing role | Responsibility | What the user sees | +|---|---|---| +| Setup Guide | Elicits the job and drafts the complete onboarding plan. | One recommendation and the questions ACE could not infer. | +| Source Scout | Proposes public and authorized private sources; tests access and coverage. | Recommended source roles, permission requests, gaps, and connection state. | +| Ontology Mapper | Resolves entities, concepts, aliases, and relationships. | Proposed mappings and only the ambiguities that need review. | +| Watch Builder | Converts the job into monitors, materiality rules, and cadence. | Watches in plain language with editable scope. | +| Briefing Agent | Synthesizes supported Shifts and Cases into a cited Brief. | The first Brief and its exact supporting revisions. | +| Quality Challenger | Tests corroboration, contradiction, uncertainty, and unsupported conclusions. | Warnings, competing evidence, and honest insufficient-evidence states. | +| Learning Agent | Uses explicit feedback and outcomes to reweight relevance. | Better ranking and a visible explanation of what changed; no authority widening. | + +These roles are governed compositions over ACE capabilities. They are not autonomous personalities, +new sources of truth, or separate products. + +## Domain onboarding-profile boundary + +Core owns a versioned, domain-neutral presentation contract for a Domain Pack to declare: + +- outcome choices; +- selectable topics and entity classes; +- cadence choices; +- recommended source roles; +- proposed watches and intelligence products; and +- user-facing labels and descriptions. + +The profile is declarative and non-authorizing. Selecting it creates an onboarding proposal; the +existing Connect, Map, Watch, Brief, and Activate lifecycle remains the only path to live authority. +Domain Packs cannot provide imperative UI code, bypass consent, claim proposed sources are +connected, or change Core navigation. + +## Research synthesis and reference lock + +The design direction was researched before implementation: + +- Linear Changelog provides the midnight surface hierarchy, compact editorial rhythm, precise + dividers, restrained radii, and calm typography. +- Oxide contributes the technical credibility of sharp graphite surfaces and a single disciplined + live-state accent rather than decorative gradients. +- Checkly contributes operational status language and dense monitoring panels that remain legible. +- Rox contributes a connection checklist, recommended integrations, clear permission explanation, + and visible connected/connecting state. +- Gemini contributes the centered readable answer with a dedicated source panel one interaction + away. +- Spyglass contributes one dominant orientation view with a narrow contextual rail instead of an + equal-weight dashboard grid. +- Nextdoor and Product Hunt onboarding demonstrate outcome/topic selection followed immediately by + a populated personalized feed. + +Primary direction: Linear's compact midnight command center. Borrowed details: Rox's recommended +connection/status pattern and Gemini's source side sheet. Preserve ACE mint only for live, +confirmed, or selected state. Use mono typography only for time, count, status, and provenance. + +Explicit rejections: + +- a 70-source integration wall; +- a parade of named agent personalities; +- architecture, ontology, or prompt configuration during first use; +- a seven-step coach-mark tour after setup; +- a blank dashboard while connectors run; +- rainbow category and severity systems; +- unsourced AI answers; and +- domain-specific UI branches inside Core. + +## Acceptance journey + +1. A clean install opens with no live intelligence and offers one outcome-led start action. +2. A user selects an AI decision context, accepts recommended topics and cadence, and sees proposed + public evidence before granting authority. +3. ACE explains every requested permission and keeps failed or skipped connections resumable. +4. The governed agents produce an inspectable Connect -> Map -> Watch -> Brief -> Activate trace. +5. The first cited Brief appears without hand-authored ontology work or knowledge of ACE internals. +6. The user can ask a grounded question, inspect exact evidence, mark relevance, and see that + feedback changes ranking without changing evidence or authority. +7. Restart/reopen returns to the same active watches, latest Brief, onboarding history, and user + preferences. +8. The same Core shell reproduces a materially different Market Intelligence profile without code + changes. From 6967ce5eda49f14323781a198fbf91aded080f83 Mon Sep 17 00:00:00 2001 From: Edwin Amirian Date: Thu, 13 Aug 2026 07:12:31 -0700 Subject: [PATCH 3/3] fix(atrium): use governed button primitives --- core/ui/canvas/src/app/atrium/OnboardingPreview.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/ui/canvas/src/app/atrium/OnboardingPreview.tsx b/core/ui/canvas/src/app/atrium/OnboardingPreview.tsx index 3b62a7e..9792457 100644 --- a/core/ui/canvas/src/app/atrium/OnboardingPreview.tsx +++ b/core/ui/canvas/src/app/atrium/OnboardingPreview.tsx @@ -68,13 +68,13 @@ export function OnboardingPreview({ open, onOpenChange, profile }: { readonly op {profile.outcomes.map((item) => { const selected = item.outcome_id === outcomeId return ( - + ) })}
@@ -89,7 +89,7 @@ export function OnboardingPreview({ open, onOpenChange, profile }: { readonly op
Recommended coverage
{outcome.recommended_topic_labels.length > 0 ? outcome.recommended_topic_labels.map((topic) => {topic}) : Choose topics after continuing.}
You can add specific entities, organizations, products, policies, or technologies next.

ACE asks only for details it cannot safely infer.

-
How often should ACE orient you?
{profile.cadences.map((cadence) => { const selected = cadence.cadence_id === cadenceId; return })}
+
How often should ACE orient you?
{profile.cadences.map((cadence) => { const selected = cadence.cadence_id === cadenceId; return })}
)}