Second-pass review surfaced eight issues worth pinning down before
we start coding. Each addressed in-place rather than as a TODO so
the implementation has a clear target:
1. §5.1 — Persisted vs in-memory fields. The host's serialized
`content` was implicitly inflating with `fallbackPreview`,
`lastResolvedAt`, `lastResolvedSize` from the initial schema.
Now explicit: only `kind` / `ref` / `mode` / `pinnedEventId` /
`pinnedSnapshot` persist; the rest are runtime-only and stripped
at publish time. A linked layer footprint is ~110 bytes serialized.
2. §6.1 — Pinned + source deletion. New section covering the case
where the source author kind-5-deletes a pinned event. Adds a
`pinnedSnapshot` field that the renderer prefers-live-falls-back-
to-snapshot, plus the existing `fallbackPreview` for visual-only
fallback. Default = pin without snapshot, user opts in for
permanence.
3. §8.1 — Sync vs async render decision. The renderer is synchronous
today; this section explicitly picks "sync placeholder + async
upgrade" over "block render" or "pre-fetch all". DOM tagging +
abort-on-detach behaviour documented so the implementation
doesn't end up re-deciding mid-build.
4. §8.2 — Autosave + cold start. localStorage restore loses in-memory
resolver state, so on `restoreAutosave` we kick off the resolver
for every linked layer in the background.
5. §10.3 — Embedded → linked attribution: corrected an inaccurate
claim. Embedded layers don't carry source-author metadata, so
"Publish + link" credits the CURRENT user, not whoever originally
authored the embedded content. Action copy should warn explicitly.
6. §12.1 — Premium-link access check: clarified that the gate is the
host viewer's purchase-vault entry (not technical decryption
capability, since the soft-gate KDF is deterministic).
7. §13 — Added a per-tree cap (1 MB total resolved payload per host
render) on top of the per-event cap. Depth-3 fan-out could
theoretically multiply fetches; the tree cap stops a coordinated
DOS at the host level.
8. §11.5 — Picking initial layer dimensions for a new linked layer
(sync resolver lookup with a 1.5 s timeout, falls back to 16:9
default). Plus a link-creation modal preview so the user can
confirm what they're linking before commit.
Also tightened the Phase 1 acceptance criteria around serialized
size: explicit budgets for 10-link host events and verification that
in-memory fields are stripped at publish.
Spec for the next big arc: reference-based cross-design imports.
Adds a `kind: 'linked'` flavor of the existing design layer that
stores only a NIP-01 addressable-event coordinate
(30078:<pubkey>:<dTag>) and resolves to the latest source on
render. Sits alongside the existing embedded import path; user
picks at import time.
Key decisions baked in (per scoping conversation):
- Versioning: always-latest by default, with an opt-in "Pin to
this version" toggle exposed per linked layer.
- Nesting: up to 3 levels deep with cycle detection + visible
depth-exceeded placeholders.
- Encrypted: Phase 1 ships public-only; Phase 2 adds premium
(renders if viewer's unlocked) and private (share-list aware).
- Attribution chain is first-class — host publish emits one
['p', '<contributor pubkey>', '<relay-hint>', 'design-contributor']
tag per distinct linked-tree creator, plus an in-app Credits
panel surfacing co-creators. Lets larger collaborative projects
carry honest attribution natively.
Architectural shape:
- Resolver IIFE (linkedDesigns) with session cache, multi-relay
union (mirrors _vaultRelayUnion), and a manual Refresh button
to bypass cache.
- renderDesignObjectIntoElement gets a linked branch that calls
the resolver before the existing render path.
- Layer-options panel exposes pin / refresh / unlink / replace.
- "Publish + link" action converts an embedded layer to a freshly-
published source + linked reference (Phase 3 polish).
- Convert-back-to-embed action snapshots the current resolution.
Phased implementation:
- Phase 1: Public-only linking, schema, resolver, rendering, UI.
- Phase 2: Premium + private link resolution with access control.
- Phase 3: Disk cache, attribution `p`-tag emission, Credits panel,
Publish + link workflow.
Acceptance criterion for Phase 1: a public cover design linked into
a public slide deck renders correctly; editing the source updates
the host's render on next refresh. 10-link host event stays under
typical relay size limits.
Outstanding open questions to revisit during implementation:
- Whether the depth limit should be 3 forever or configurable.
- Whether the 256 KB per-source payload cap is the right number.
- Whether to surface "last resolved" times in the layer name itself
or only on hover.