From 47a02f463738adef6a4ee0bc34308004cf8db3f5 Mon Sep 17 00:00:00 2001 From: Florian <45694132+flo-bit@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:40:00 +0200 Subject: [PATCH] web: attach resolved data onto node.loaded (not a side map) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the resolved source data was harvested into a separate WebsiteData.loaded map while result.nodes stayed un-enriched, so data.nodes[i].loaded was empty. Hang loaded onto the nodes themselves (WebsiteData.nodes is now ResolvedNode[]); ContextProvider derives the id→loaded lookup the Item render path needs. loaded stays runtime-only (nodeToRecord drops it). Render byte-identical to the prior impl (0px old-vs-new local). Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/web/src/lib/types.ts | 12 ++++-------- apps/web/src/lib/website/data/load.ts | 16 +++++++++++----- .../src/lib/website/view/ContextProvider.svelte | 8 +++++++- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/apps/web/src/lib/types.ts b/apps/web/src/lib/types.ts index 66b4d24..1f0b2f5 100644 --- a/apps/web/src/lib/types.ts +++ b/apps/web/src/lib/types.ts @@ -1,7 +1,6 @@ import type { Blob } from '@atcute/lexicons'; import type { AppBskyActorDefs } from '@atcute/bluesky'; -import type { Node } from '@blento/schema'; -import type { ResolveResult } from '@blento/sources'; +import type { ResolvedNode } from '@blento/sources'; export type Item = { id: string; @@ -99,8 +98,9 @@ export type WebsiteData = { sections: SectionRecord[]; - /** The node graph (migrate-on-read). Source of truth going forward; render still uses cards/sections. */ - nodes?: Node[]; + /** The node graph (migrate-on-read). Source of truth going forward; render still uses cards/sections. + * Each node carries its resolved `source` data on `node.loaded` (runtime-only, never persisted). */ + nodes?: ResolvedNode[]; /** True when `nodes` came from stored app.blento.node records (page already migrated), vs rebuilt * on read from legacy card/section. Save uses this to know whether to retire the legacy records. */ @@ -108,10 +108,6 @@ export type WebsiteData = { additionalData: Record; - /** Resolved `node.source` data, keyed by node id (== card id). Runtime-only, never persisted; - * renderers read their node's loaded data here. See `@blento/sources`. */ - loaded?: Record; - updatedAt: number; version?: number; diff --git a/apps/web/src/lib/website/data/load.ts b/apps/web/src/lib/website/data/load.ts index 0ddda64..2776672 100644 --- a/apps/web/src/lib/website/data/load.ts +++ b/apps/web/src/lib/website/data/load.ts @@ -10,7 +10,8 @@ import { type Source, type SourceContext, type CacheAdapter, - type ResolveResult + type ResolveResult, + type ResolvedNode } from '@blento/sources'; import { error } from '@sveltejs/kit'; import type { ActorIdentifier, Did } from '@atcute/lexicons'; @@ -350,8 +351,9 @@ export async function loadData( : buildGraph(sectionRecords, cards, fullPage, { order: 'input' }); // Declarative-source path: stamp/keep node.source, then resolve the graph by node id via - // @blento/sources. Loaded data travels with the node (result.loaded[nodeId]); its cards skip the - // bespoke loadAdditionalData below. Only the source spec is persisted — loaded is runtime-only. + // @blento/sources. Loaded data is attached onto the nodes themselves (node.loaded); sourced cards + // skip the bespoke loadAdditionalData below. Only the source spec is persisted — loaded is + // runtime-only (schema's allowlist serialize drops it). stampNodeSources(graph); const sourcedIds = new Set(graph.filter((n) => n.source).map((n) => n.id)); const loadedPromise = loadNodeSources(graph, { did, cache }); @@ -383,11 +385,15 @@ export async function loadData( }); // Attach the canonical node graph for downstream consumers: the stored records when present, // else the migrate-on-read graph rebuilt from the post-collision-fix state. - result.nodes = nodesFromRecords.length + const nodes: ResolvedNode[] = nodesFromRecords.length ? nodesFromRecords : buildGraph(result.sections, result.cards, fullPage, { order: 'input' }); + // Hang the resolved data on each node (keyed by id) — renderers get source + loaded on one node. + for (const node of nodes) { + if (node.id in loaded) node.loaded = loaded[node.id]; + } + result.nodes = nodes; result.migratedStorage = nodesFromRecords.length > 0; - result.loaded = loaded; return result; } diff --git a/apps/web/src/lib/website/view/ContextProvider.svelte b/apps/web/src/lib/website/view/ContextProvider.svelte index 8761e00..bf306e9 100644 --- a/apps/web/src/lib/website/view/ContextProvider.svelte +++ b/apps/web/src/lib/website/view/ContextProvider.svelte @@ -24,8 +24,14 @@ // svelte-ignore state_referenced_locally setAdditionalUserData(data.additionalData); + // Loaded data lives on each node (node.loaded); expose an id→loaded lookup for the Item-based + // render path. // svelte-ignore state_referenced_locally - setLoadedData(data.loaded ?? {}); + setLoadedData( + Object.fromEntries( + (data.nodes ?? []).filter((n) => n.loaded != null).map((n) => [n.id, n.loaded!]) + ) + ); setCanEdit( () => -- 2.51.2