build: generate the TechLog Studio contract from canonical source

Vendors the canonical studio-v1.yaml, generates types via an isolated
`pnpm dlx` toolchain (openapi-typescript needs TypeScript 5's classic
compiler API; this repo pins TypeScript 7.0.2 per VD-01, whose root
export has none), and adds an offline drift gate that checks the
vendored yaml/generated types/canonical-source.json against each
other without touching the sibling design-package repo or the network.

Regenerating from canonical surfaces real, new required fields on
existing schemas (WorkingCopyDetail.nextAction, PreviewDetail/PublicPreview
.dependencyRevision, StudioDashboard.totals.needsValidation,
PublicationSnapshot.contentFormatVersion/rendererContractVersion) and a
new required EvidenceFigureBlock.asset. The mock gateway and fixtures
are updated to satisfy the former; the latter exposes a real authoring-
vs-rendering conflation in the content-format parser (it declared its
output as the server's fully-resolved PublicRenderModel type, which it
has no asset catalog to satisfy). Split that boundary: the parser now
produces an authoring block type omitting the resolved asset, and each
of its three consumers (the mock gateway, the Studio instant preview,
and the static Case demo page) attaches the resolved descriptor from
its own asset source through a shared, pure domain-level resolver.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-08-18 00:27:59 +09:00
co-authored by Claude Opus 5
parent ac555a85e8
commit 639e1a49c9
18 changed files with 1994 additions and 224 deletions
+25 -4
View File
@@ -4,7 +4,11 @@ import { act, fireEvent, render, screen, within } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { getEvidenceAsset } from "../../../src/features/tech-log/adapters/static/evidence-assets.ts";
import {
getEvidenceAsset,
isSupportedEvidenceKey,
resolveEvidenceAssetDescriptor,
} from "../../../src/features/tech-log/adapters/static/evidence-assets.ts";
import type { components } from "../../../src/features/tech-log/contracts/studio/generated.ts";
import { parseCaseContent } from "../../../src/features/tech-log/domain/content-format/parse-case-content.ts";
import { Callout } from "../../../src/features/tech-log/presentation/shared/public-render/callout.tsx";
@@ -72,6 +76,21 @@ const renderDependencies = {
: undefined,
};
// `parseCaseContent` returns authoring blocks (no asset catalog access); the
// renderer under test expects the resolved `CaseRenderBlock` shape, so this
// mirrors what `adapters/mock/project-public-render-model.ts` does in production.
function resolvedBlocks(
blocks: ReturnType<typeof parseCaseContent>,
): components["schemas"]["CaseRenderBlock"][] {
return blocks.map((block) => {
if (block.type !== "EVIDENCE_FIGURE") return block;
if (!isSupportedEvidenceKey(block.key)) {
throw new Error(`Unknown local evidence asset: ${block.key}`);
}
return { ...block, asset: resolveEvidenceAssetDescriptor(block.key) };
});
}
function caseModel(
overrides: Partial<components["schemas"]["CasePublicRenderModel"]> = {},
): components["schemas"]["CasePublicRenderModel"] {
@@ -151,7 +170,7 @@ select * from feed_item;
const view = render(
<CaseBodyRenderer
blocks={blocks}
blocks={resolvedBlocks(blocks)}
resolveEvidenceAsset={getEvidenceAsset}
/>,
);
@@ -338,8 +357,10 @@ describe("shared Public record renderer", () => {
title: "컬렉션 Fetch Join과 페이징은 왜 충돌하는가",
publicPath: "/cases/collection-fetch-join-pagination",
reproduction: "Dataset: 수정 중인 데이터셋",
bodyBlocks: parseCaseContent(
"## *강조* **강함** `코드` {#rich-heading}\n\n본문",
bodyBlocks: resolvedBlocks(
parseCaseContent(
"## *강조* **강함** `코드` {#rich-heading}\n\n본문",
),
),
})}
/>,