mirror of
https://github.com/samvallad33/vestige.git
synced 2026-07-24 23:41:01 +02:00
feat(journey): wire Receipt → centered Graph → auto-Cinema → Explore, all base-safe
The hero journey now flows through visible controls with cognitive context that
survives navigation (os-nav.ts gets its first real consumers):
- /dashboard/ redirects to /dashboard/palace (the home), base-safe (was a bare
goto('/graph') that escaped the base + landed on the wrong organ).
- ReceiptCard 'Open in Cinema' uses osGoto with {memory, focus, receipt, cinema}
— fixes the base-escape 404 AND carries the full evidence set.
- Graph consumes ?memory= (canonical; ?center= legacy alias), centers on it, and
a Graph-OWNED one-shot bridge auto-launches the PROTECTED Cinema by clicking its
own .cinema-launch control (MemoryCinema untouched).
- Graph 'Explore Connections' now carries the selected memory; Explore seeds the
walk from THAT memory instead of re-seeding from the newest.
Verified live: /→palace, graph?memory=X&cinema=1 centers + auto-opens Cinema (0
errors), explore?memory=X seeds a walk. check 0/0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
3200f04de2
commit
4c6a24a35a
4 changed files with 71 additions and 18 deletions
|
|
@ -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
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -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=<id>), 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();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { base } from '$app/paths';
|
||||
import { osHref } from '$lib/os-nav';
|
||||
import Graph3D from '$components/Graph3D.svelte';
|
||||
import RetentionCurve from '$components/RetentionCurve.svelte';
|
||||
import TimeSlider from '$components/TimeSlider.svelte';
|
||||
|
|
@ -223,14 +224,37 @@
|
|||
if (isColorMode(requestedMode)) {
|
||||
colorMode = requestedMode;
|
||||
}
|
||||
// "Open receipt in Cinema" deep-links here with ?center=<memoryId>, so
|
||||
// the graph loads centered on the receipt's primary memory and the
|
||||
// (protected) Cinema flythrough starts from that exact node. We do not
|
||||
// touch MemoryCinema itself — only seed the graph it renders.
|
||||
const center = sp.get('center');
|
||||
void loadGraph(undefined, center || undefined);
|
||||
// The hero journey deep-links here carrying cognitive context: ?memory=<id>
|
||||
// (canonical; ?center= is the legacy alias) centers the graph on the
|
||||
// receipt's primary memory, and ?cinema=1 AUTO-LAUNCHES the (protected)
|
||||
// Cinema flythrough over that exact evidence set. We NEVER touch
|
||||
// MemoryCinema — a Graph-owned one-shot bridge just clicks its own launch
|
||||
// control once the graph has loaded, so the component stays untouched.
|
||||
const center = sp.get('memory') ?? sp.get('center');
|
||||
const autoCinema = sp.get('cinema') === '1';
|
||||
void loadGraph(undefined, center || undefined).then(() => {
|
||||
if (autoCinema) launchCinemaOnce();
|
||||
});
|
||||
});
|
||||
|
||||
// One-shot bridge: after the graph has real nodes, programmatically trigger
|
||||
// the protected Cinema's own launch button exactly once. Read-only w.r.t.
|
||||
// MemoryCinema — we click its rendered control, never import/modify its state.
|
||||
function launchCinemaOnce() {
|
||||
let tries = 0;
|
||||
const timer = setInterval(() => {
|
||||
tries += 1;
|
||||
const btn = document.querySelector<HTMLButtonElement>('.cinema-launch');
|
||||
if (btn) {
|
||||
btn.click();
|
||||
clearInterval(timer);
|
||||
} else if (tries > 40) {
|
||||
// ~6s: nodes never materialized (empty/errored graph) — give up quietly.
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
|
||||
function isColorMode(value: string | null): value is ColorMode {
|
||||
return value === 'type' || value === 'state' || value === 'ahagraph';
|
||||
}
|
||||
|
|
@ -708,9 +732,10 @@ disown</code>
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Explore from this node -->
|
||||
<!-- Explore from this node — carries the selected memory so the walk
|
||||
starts from THIS thought, not a re-seed from the newest memory. -->
|
||||
<a
|
||||
href="{base}/explore"
|
||||
href={osHref('/explore', { memory: selectedMemory.id })}
|
||||
class="flex items-center justify-center gap-2 px-3 py-2 rounded-xl bg-dream/10 text-dream-glow text-xs hover:bg-dream/20 transition border border-dream/20"
|
||||
>
|
||||
<Icon name="explore" size={14} /> Explore Connections
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
import { base } from '$app/paths';
|
||||
import { HOME_ROUTE } from '$lib/os-routes';
|
||||
|
||||
onMount(() => goto('/graph', { replaceState: true }));
|
||||
// The VestigeOS home is the Palace (the spatial launcher), NOT /graph. Use the
|
||||
// base-safe path so /dashboard/ lands on /dashboard/palace — a bare
|
||||
// goto('/graph') escapes the configured base and 404s on the deployed site.
|
||||
onMount(() => goto(`${base}${HOME_ROUTE}`, { replaceState: true }));
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue