feat: resolve evidence figures from backend asset descriptors
Public Preview and Publication Snapshot now resolve evidence figures from the ResolvedAsset descriptor the server already attaches to each EVIDENCE_FIGURE block, instead of a local literal that only knew one hardcoded key and threw on anything else. Instant Preview gains an `assets` prop (defaults to `[]`, unwired until the Asset Picker task) and stops throwing when a key has no catalog match yet. Builds on Task 1's existing seam (resolveCaseEvidenceAssets / ResolveEvidenceAsset) via a new asset-resolvers.ts rather than a parallel path. Kept out of adapters/ (presentation may not import it) by carrying a small local copy of the one legacy fixture entry, checked before any descriptor match so the pre-existing key keeps rendering byte-identically across surfaces during the migration window. Non-READY assets never resolve; unresolvable keys return a placeholder instead of throwing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0c071aaabc
commit
9d91001a31
@@ -0,0 +1,116 @@
|
||||
import type { Asset } from "../../../contracts/studio/contract.ts";
|
||||
import type {
|
||||
EvidenceAsset,
|
||||
ResolveEvidenceAsset,
|
||||
} from "../../../domain/public-render-content.ts";
|
||||
|
||||
/**
|
||||
* `presentation/` may not import `adapters/` (the
|
||||
* `feature-presentation-does-not-know-outbound-adapters` architecture rule), so this
|
||||
* cannot read `adapters/static/evidence-assets.ts` directly. It carries its own copy of
|
||||
* that one legacy entry instead -- the same pattern `instant-preview.tsx`,
|
||||
* `public-preview-screen.tsx`, `publication-event-preview-screen.tsx`, and
|
||||
* `case-document-page.tsx` already each carry independently. This is not a second
|
||||
* source of truth for new assets; it exists solely so the one pre-existing hardcoded
|
||||
* key keeps resolving to byte-identical output once screens move onto this resolver.
|
||||
*/
|
||||
const LEGACY_STATIC_EVIDENCE_ASSETS: Readonly<Record<string, EvidenceAsset>> = {
|
||||
"fetch-strategy-boundary": Object.freeze({
|
||||
src: "/media/fetch-strategy-boundary.svg",
|
||||
width: 1080,
|
||||
height: 420,
|
||||
triggerLabel: "Fetch Join과 Batch Fetch 비교 다이어그램 크게 보기",
|
||||
dialogLabel: "Fetch Join과 Batch Fetch의 페이징 경계 확대",
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* `ResolveEvidenceAsset` is a total function. Throwing on an unresolved key would
|
||||
* take down the whole preview for one bad or not-yet-synced reference, so every
|
||||
* resolver here returns this placeholder instead. Blocking a genuinely bad
|
||||
* reference is Publish validation's job, not the renderer's.
|
||||
*/
|
||||
export const MISSING_EVIDENCE_ASSET: EvidenceAsset = Object.freeze({
|
||||
src: "",
|
||||
width: 1,
|
||||
height: 1,
|
||||
triggerLabel: "사용할 수 없는 Evidence",
|
||||
dialogLabel: "사용할 수 없는 Evidence",
|
||||
});
|
||||
|
||||
/** Structural shape of `components["schemas"]["ResolvedAsset"]`, loosened for reuse. */
|
||||
export type ResolvedAssetLike = Readonly<{
|
||||
assetId: string;
|
||||
assetKey: string;
|
||||
mediaType: string;
|
||||
publicPath: string;
|
||||
width: number | null;
|
||||
height: number | null;
|
||||
decorative: boolean;
|
||||
}>;
|
||||
|
||||
function fromDescriptor(descriptor: ResolvedAssetLike): EvidenceAsset {
|
||||
return Object.freeze({
|
||||
src: descriptor.publicPath,
|
||||
width: descriptor.width ?? 1,
|
||||
height: descriptor.height ?? 1,
|
||||
triggerLabel: `${descriptor.assetKey} 이미지 크게 보기`,
|
||||
dialogLabel: `${descriptor.assetKey} 확대`,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared resolution order for both factories below: the legacy static key always
|
||||
* wins on a match (protects the one pre-existing hardcoded fixture from splitting
|
||||
* across render surfaces mid-migration), then the caller-supplied lookup (the real
|
||||
* backend-sourced descriptor), then the safe placeholder.
|
||||
*/
|
||||
function resolveWith(
|
||||
key: string,
|
||||
lookup: (key: string) => ResolvedAssetLike | undefined,
|
||||
): EvidenceAsset {
|
||||
const legacy = LEGACY_STATIC_EVIDENCE_ASSETS[key];
|
||||
if (legacy) return legacy;
|
||||
const descriptor = lookup(key);
|
||||
return descriptor ? fromDescriptor(descriptor) : MISSING_EVIDENCE_ASSET;
|
||||
}
|
||||
|
||||
/** Instant Preview: resolves against the Asset list the editor has loaded. */
|
||||
export function createAssetCatalogResolver(
|
||||
assets: readonly Asset[],
|
||||
): ResolveEvidenceAsset {
|
||||
const byKey = new Map<string, Asset>();
|
||||
for (const asset of assets) {
|
||||
// QUARANTINED/REJECTED must never render on any surface.
|
||||
if (asset.managementStatus !== "READY") continue;
|
||||
byKey.set(asset.assetKey, asset);
|
||||
}
|
||||
|
||||
return (key: string) =>
|
||||
resolveWith(key, (lookupKey) => {
|
||||
const asset = byKey.get(lookupKey);
|
||||
if (!asset || asset.publicPath === null) return undefined;
|
||||
return {
|
||||
assetId: asset.id,
|
||||
assetKey: asset.assetKey,
|
||||
mediaType: asset.mediaType,
|
||||
publicPath: asset.publicPath,
|
||||
width: asset.width,
|
||||
height: asset.height,
|
||||
decorative: asset.decorative,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** Public Preview · Snapshot: resolves from the descriptor the server already attached to the block. */
|
||||
export function createResolvedAssetResolver(
|
||||
blocks: readonly Readonly<{ type: string; key?: string; asset?: ResolvedAssetLike }>[],
|
||||
): ResolveEvidenceAsset {
|
||||
const byKey = new Map<string, ResolvedAssetLike>();
|
||||
for (const block of blocks) {
|
||||
if (block.type !== "EVIDENCE_FIGURE" || !block.asset) continue;
|
||||
byKey.set(block.asset.assetKey, block.asset);
|
||||
}
|
||||
|
||||
return (key: string) => resolveWith(key, (lookupKey) => byKey.get(lookupKey));
|
||||
}
|
||||
Reference in New Issue
Block a user