diff --git a/apps/dashboard/src/lib/components/ReceiptCard.svelte b/apps/dashboard/src/lib/components/ReceiptCard.svelte index 71ea574..afafe7f 100644 --- a/apps/dashboard/src/lib/components/ReceiptCard.svelte +++ b/apps/dashboard/src/lib/components/ReceiptCard.svelte @@ -8,9 +8,9 @@ // the receipt's primary memory, starting the (protected) Cinema flythrough // over the exact memory set the receipt names. // ═══════════════════════════════════════════════════════════════════════ - import { goto } from '$app/navigation'; import Icon from './Icon.svelte'; import type { Receipt } from '$lib/stores/api'; + import { osGoto } from '$lib/os-nav'; interface Props { receipt: Receipt; @@ -24,11 +24,20 @@ high: '#f43f5e' }; + // Hero-journey handoff: open the exact receipt in the Graph, centered on its + // primary memory, with the full retrieved set focused, then AUTO-LAUNCH Cinema + // over that evidence. Base-safe via osGoto (a bare goto('/graph') escaped the + // /dashboard base and 404'd); the cognitive context (memory/focus/receipt/ + // cinema) rides the URL so the whole set survives the navigation. function openInCinema() { const primary = receipt.retrieved[0]; if (!primary) return; - const focus = receipt.retrieved.join(','); - goto(`/graph?center=${encodeURIComponent(primary)}&focus=${encodeURIComponent(focus)}`); + osGoto('/graph', { + memory: primary, + focus: receipt.retrieved, + receipt: receipt.receipt_id, + cinema: true + }); } diff --git a/apps/dashboard/src/routes/(app)/explore/+page.svelte b/apps/dashboard/src/routes/(app)/explore/+page.svelte index a8cb2c0..214f7bf 100644 --- a/apps/dashboard/src/routes/(app)/explore/+page.svelte +++ b/apps/dashboard/src/routes/(app)/explore/+page.svelte @@ -52,9 +52,16 @@ // may apply results / clear the walking label. (GPT-5.6-sol cross-review.) let loadGen = 0; + // A memory id passed from another organ (e.g. Graph's "Explore Connections") + // seeds the walk from THAT thought instead of re-seeding from the newest one, + // so the selected memory survives the Graph → Explore handoff. + let seedMemoryId: string | null = $state(null); + onMount(() => { const params = new URLSearchParams(window.location.search); searchQuery = params.get('q') ?? ''; + // `memory` is the canonical cross-organ selection contract (center = legacy). + seedMemoryId = params.get('memory') ?? params.get('center'); void loadNeighbors(); }); @@ -145,13 +152,20 @@ // immediately alive, and every click walks onward from a real thought. if (!searchQuery.trim()) { try { - const seed = await api.memories.list({ limit: '1' }); + // If another organ handed us a memory (?memory=), seed the walk + // from THAT thought so the selection survives the handoff. Otherwise + // auto-seed from the newest memory so landing here is always alive. + const seedMemory = seedMemoryId + ? await api.memories.get(seedMemoryId).catch(() => null) + : null; + const start = seedMemory ?? (await api.memories.list({ limit: '1' })).memories?.[0] ?? null; if (gen !== loadGen) return; // a newer walk superseded this load - const newest = seed.memories?.[0]; - if (newest?.content) { - searchQuery = walkQuery(newest.content); - centerId = newest.id; - seededFrom = newest.id.slice(0, 8); + if (start?.content) { + searchQuery = walkQuery(start.content); + centerId = start.id; + seededFrom = start.id.slice(0, 8); + // Consume the one-shot seed so a later re-walk doesn't re-pin it. + seedMemoryId = null; // The seeded expedition must be shareable/reproducible too — a // reload mid-ingest would otherwise seed from a different memory. syncQueryToUrl(); diff --git a/apps/dashboard/src/routes/(app)/graph/+page.svelte b/apps/dashboard/src/routes/(app)/graph/+page.svelte index c865350..686d2ff 100644 --- a/apps/dashboard/src/routes/(app)/graph/+page.svelte +++ b/apps/dashboard/src/routes/(app)/graph/+page.svelte @@ -1,6 +1,7 @@