# 플랫폼 구성 화면 완성 구현 계획 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** `/examples/platform` 화면이 정적 선택뿐 아니라 런타임 해석 결과까지 보여주고, 진입 경로와 브라우저 회귀 커버리지를 갖춘다. **Architecture:** 능력 상태 파생은 `src/contracts`의 순수 함수가 소유한다. 합성 루트가 런타임 오버라이드를 그 함수에 넣어 경계된 스냅샷을 만들고, 애플리케이션 포트를 통해 표현 계층에 전달한다. 표현 계층은 bootstrap을 알지 못한 채 해석 결과를 읽는다. **Tech Stack:** TypeScript 7 (strict), React 19, Zod 4, Vitest 4, Playwright 1.62 ## Global Constraints - `src/presentation`은 `src/adapters`와 `src/bootstrap`을 import 할 수 없다 (`presentation-does-not-know-adapters`). - `src/application`은 `src/presentation`, `src/adapters`, `src/bootstrap`, `react`, `@tanstack`을 import 할 수 없다. - 모든 소스는 `.ts` / `.tsx`. `allowJs: false`. - `src/presentation/{boundaries,components,design-system,forms,layouts,routes,templates}`에는 한글 리터럴을 둘 수 없다. `src/presentation/examples`는 대상이 아니다. - i18n 카탈로그는 ko-KR / en-US / pseudo 키 집합이 완전히 일치해야 한다. - 화면은 레지스트리에서 파생한 값만 렌더한다. 샘플 기능 이름을 하드코딩하면 `test:sample-removal`의 잔재 스캔이 실패한다. - 각 태스크는 `corepack pnpm check:types`, `corepack pnpm lint`, `corepack pnpm check:architecture`를 통과해야 한다. ## 배경: 왜 이 작업이 남았나 `docs/superpowers/specs/2026-07-31-platform-overview-page-design.md` 5절이 런타임 해석 결과를 의도적으로 제외했다. 해석은 `createRuntimeComposition`에서 일어나고 그 결과가 애플리케이션 계층으로 전달되지 않기 때문이다. 그래서 화면은 "정적 선택 기준"이라고만 말할 수 있고, 운영자가 `CAPABILITY_OVERRIDES`로 능력을 껐는지는 화면에서 알 수 없다. 이 계획은 그 경로를 만든다. 오버라이드는 이미 선택된 능력을 끄는 것만 가능하므로, 스냅샷은 선택 수와 활성 수를 함께 실어 둘의 차이가 곧 오버라이드 효과가 되게 한다. ## 파일 구조 | 파일 | 책임 | | --- | --- | | `src/contracts/runtime-capabilities.ts` (수정) | 능력 스냅샷 파생 순수 함수를 추가한다. 기존 `resolveRuntimeCapabilities`와 같은 입력을 받는다. | | `src/application/ports/runtime-capabilities-port.ts` (생성) | 애플리케이션이 능력 상태를 얻는 출력 포트. | | `src/application/create-application.ts` (수정) | `runtime.getCapabilitySnapshot()`을 인바운드 API에 노출한다. | | `src/application/ports/in/application-api.ts` (수정) | 인바운드 계약에 스냅샷 접근자를 선언한다. | | `src/bootstrap/runtime-adapters.ts` (수정) | 런타임 설정의 오버라이드로 포트를 합성한다. | | `src/presentation/examples/platform-overview-page.tsx` (수정) | 5번 섹션이 선택과 활성을 함께 표시한다. | | `src/presentation/pages/home-page.tsx` (수정) | 플랫폼 구성 화면 진입점. | | `tests/unit/runtime-capability-snapshot.test.ts` (생성) | 순수 함수 검증. | | `tests/unit/runtime-adapters.test.ts` (수정) | 포트 합성 검증. | | `tests/component/platform-overview-page.test.tsx` (수정) | 화면이 해석 결과를 표시하는지 검증. | | `tests/e2e/platform-overview.spec.ts` (생성) | 실제 번들에서 파생 불변식 검증. | | `tests/visual/platform.visual.spec.ts` (수정) | 시각 회귀 기준선. | --- ### Task 1: 능력 스냅샷 파생 계약 **Files:** - Modify: `src/contracts/runtime-capabilities.ts` - Test: `tests/unit/runtime-capability-snapshot.test.ts` **Interfaces:** - Consumes: 같은 파일의 `InstalledRuntimeCapabilities`, `CapabilityOverrideMap`, `RuntimeCapabilityOverride` - Produces: `RuntimeCapabilityId`, `RuntimeCapabilityStatus`, `RuntimeCapabilitySnapshot`, `describeRuntimeCapabilities(installed, overrides)` - [ ] **Step 1: 실패하는 테스트 작성** `tests/unit/runtime-capability-snapshot.test.ts`: ```ts import { describe, expect, it } from "vitest"; import { describeRuntimeCapabilities, type CapabilityOverrideMap, type InstalledRuntimeCapabilities, } from "../../src/contracts/runtime-capabilities.ts"; const DEFAULTS: CapabilityOverrideMap = Object.freeze({ REALTIME: "DEFAULT", WEB_WORKER: "DEFAULT", SERVICE_WORKER: "DEFAULT", OFFLINE_COMMANDS: "DEFAULT", }); const EMPTY: InstalledRuntimeCapabilities = Object.freeze({ realtime: Object.freeze([]), webWorkers: Object.freeze([]), serviceWorker: null, offlineCommands: null, }); const SELECTED: InstalledRuntimeCapabilities = Object.freeze({ realtime: Object.freeze([]), webWorkers: Object.freeze([]), serviceWorker: Object.freeze({ mode: "ACTIVE" as const }), offlineCommands: null, }); describe("runtime capability snapshot", () => { it("describes every capability id in a fixed order", () => { const snapshot = describeRuntimeCapabilities(EMPTY, DEFAULTS); expect(snapshot.map((status) => status.capabilityId)).toEqual([ "REALTIME", "WEB_WORKER", "SERVICE_WORKER", "OFFLINE_COMMANDS", ]); }); it("reports nothing selected and nothing active for an empty selection", () => { const snapshot = describeRuntimeCapabilities(EMPTY, DEFAULTS); for (const status of snapshot) { expect(status.selected).toBe(0); expect(status.active).toBe(0); expect(status.override).toBe("DEFAULT"); } }); it("keeps a selected capability active while the override is DEFAULT", () => { const snapshot = describeRuntimeCapabilities(SELECTED, DEFAULTS); const serviceWorker = snapshot.find( (status) => status.capabilityId === "SERVICE_WORKER", ); expect(serviceWorker).toMatchObject({ selected: 1, active: 1, override: "DEFAULT", }); }); it("keeps a disabled capability selected but not active", () => { const snapshot = describeRuntimeCapabilities(SELECTED, { ...DEFAULTS, SERVICE_WORKER: "DISABLED", }); const serviceWorker = snapshot.find( (status) => status.capabilityId === "SERVICE_WORKER", ); expect(serviceWorker).toMatchObject({ selected: 1, active: 0, override: "DISABLED", }); }); it("cannot activate a capability that was never selected", () => { const snapshot = describeRuntimeCapabilities(EMPTY, { ...DEFAULTS, REALTIME: "DEFAULT", }); const realtime = snapshot.find( (status) => status.capabilityId === "REALTIME", ); expect(realtime).toMatchObject({ selected: 0, active: 0 }); }); it("returns a frozen snapshot and frozen entries", () => { const snapshot = describeRuntimeCapabilities(EMPTY, DEFAULTS); expect(Object.isFrozen(snapshot)).toBe(true); expect(snapshot.every((status) => Object.isFrozen(status))).toBe(true); }); }); ``` - [ ] **Step 2: 실패 확인** 실행: `corepack pnpm exec vitest run tests/unit/runtime-capability-snapshot.test.ts` 기대: `describeRuntimeCapabilities` is not exported 로 FAIL - [ ] **Step 3: 최소 구현** `src/contracts/runtime-capabilities.ts` 끝에 추가한다. `resolveRuntimeCapabilities`를 재사용해 활성 판정 규칙이 한 곳에만 존재하게 한다. ```ts export type RuntimeCapabilityId = | "REALTIME" | "WEB_WORKER" | "SERVICE_WORKER" | "OFFLINE_COMMANDS"; /** * §3.5. A bounded, serialisable view of what a capability is. `selected` is the * static SSOT count and `active` is what survived the runtime override, so the * difference between the two is exactly the operator's effect. */ export type RuntimeCapabilityStatus = Readonly<{ capabilityId: RuntimeCapabilityId; selected: number; active: number; override: RuntimeCapabilityOverride; }>; export type RuntimeCapabilitySnapshot = readonly RuntimeCapabilityStatus[]; export function describeRuntimeCapabilities( installed: InstalledRuntimeCapabilities, overrides: CapabilityOverrideMap, ): RuntimeCapabilitySnapshot { const resolved = resolveRuntimeCapabilities(installed, overrides); const counts: Readonly< Record> > = { REALTIME: { selected: installed.realtime.length, active: resolved.realtime.length, }, WEB_WORKER: { selected: installed.webWorkers.length, active: resolved.webWorkers.length, }, SERVICE_WORKER: { selected: installed.serviceWorker === null ? 0 : 1, active: resolved.serviceWorker === null ? 0 : 1, }, OFFLINE_COMMANDS: { selected: installed.offlineCommands === null ? 0 : 1, active: resolved.offlineCommands === null ? 0 : 1, }, }; return Object.freeze( ( [ "REALTIME", "WEB_WORKER", "SERVICE_WORKER", "OFFLINE_COMMANDS", ] as const ).map((capabilityId) => Object.freeze({ capabilityId, selected: counts[capabilityId].selected, active: counts[capabilityId].active, override: overrides[capabilityId], }), ), ); } ``` - [ ] **Step 4: 통과 확인** 실행: `corepack pnpm exec vitest run tests/unit/runtime-capability-snapshot.test.ts` 기대: 6 passed - [ ] **Step 5: 게이트와 커밋** ```bash corepack pnpm check:types && corepack pnpm lint && corepack pnpm check:architecture git add src/contracts/runtime-capabilities.ts tests/unit/runtime-capability-snapshot.test.ts git commit -m "feat: derive a bounded runtime capability snapshot" ``` --- ### Task 2: 능력 스냅샷을 애플리케이션 포트로 노출 **Files:** - Create: `src/application/ports/runtime-capabilities-port.ts` - Modify: `src/application/create-application.ts` - Modify: `src/application/ports/in/application-api.ts` - Modify: `src/bootstrap/runtime-adapters.ts` - Modify: `tests/helpers/create-test-application.ts` - Test: `tests/unit/runtime-adapters.test.ts` **Interfaces:** - Consumes: Task 1의 `describeRuntimeCapabilities`, `RuntimeCapabilitySnapshot` - Produces: `RuntimeCapabilitiesPort` (`getSnapshot(): RuntimeCapabilitySnapshot`), `ApplicationApi.runtime.getCapabilitySnapshot(): RuntimeCapabilitySnapshot` - [ ] **Step 1: 실패하는 테스트 작성** `tests/unit/runtime-adapters.test.ts`의 `describe` 블록 안에 추가한다. ```ts it("reports the static selection when no capability override disables it", async () => { const adapters = await createRuntimeAdapters({ runtime, release, host: {} }); const snapshot = adapters.outputPorts.runtimeCapabilities.getSnapshot(); expect(snapshot.map((status) => status.capabilityId)).toEqual([ "REALTIME", "WEB_WORKER", "SERVICE_WORKER", "OFFLINE_COMMANDS", ]); expect(snapshot.every((status) => status.override === "DEFAULT")).toBe(true); }); it("carries a disabling override into the capability snapshot", async () => { const adapters = await createRuntimeAdapters({ runtime: { ...runtime, config: { ...runtime.config, CAPABILITY_OVERRIDES: { ...runtime.config.CAPABILITY_OVERRIDES, SERVICE_WORKER: "DISABLED", }, }, }, release, host: {}, }); const serviceWorker = adapters.outputPorts.runtimeCapabilities .getSnapshot() .find((status) => status.capabilityId === "SERVICE_WORKER"); expect(serviceWorker?.override).toBe("DISABLED"); expect(serviceWorker?.active).toBe(0); }); ``` - [ ] **Step 2: 실패 확인** 실행: `corepack pnpm exec vitest run tests/unit/runtime-adapters.test.ts` 기대: `runtimeCapabilities` 가 `outputPorts`에 없어 타입/런타임 FAIL - [ ] **Step 3: 포트 정의** `src/application/ports/runtime-capabilities-port.ts` 생성: ```ts import type { RuntimeCapabilitySnapshot } from "../../contracts/runtime-capabilities.ts"; export type { RuntimeCapabilitySnapshot }; /** * §3.5. The application reads capability state; it never resolves it. Only the * composition root knows the runtime overrides. */ export type RuntimeCapabilitiesPort = Readonly<{ getSnapshot(): RuntimeCapabilitySnapshot; }>; ``` - [ ] **Step 4: 출력 포트 등록과 인바운드 노출** `src/application/create-application.ts`의 `ApplicationOutputPorts`에 필드를 더하고, `runtime` 그룹에 접근자를 추가한다. ```ts import type { RuntimeCapabilitiesPort } from "./ports/runtime-capabilities-port.ts"; ``` `ApplicationOutputPorts` 안: ```ts runtimeCapabilities: RuntimeCapabilitiesPort; ``` `runtime` 그룹 안, `getReleaseSummary` 다음: ```ts getCapabilitySnapshot() { return ports.runtimeCapabilities.getSnapshot(); }, ``` `src/application/ports/in/application-api.ts`의 `runtime` 그룹에 선언을 추가하고 타입을 import 한다. ```ts import type { RuntimeCapabilitySnapshot } from "../runtime-capabilities-port.ts"; export type { RuntimeCapabilitySnapshot }; ``` ```ts runtime: Readonly<{ getReleaseSummary(): Promise; getCapabilitySnapshot(): RuntimeCapabilitySnapshot; }>; ``` - [ ] **Step 5: 합성 루트에서 포트 생성** `src/bootstrap/runtime-adapters.ts`에 import를 추가한다. ```ts import { describeRuntimeCapabilities, } from "../contracts/runtime-capabilities.ts"; import { INSTALLED_RUNTIME_CAPABILITIES } from "../features/installed-runtime-capabilities.ts"; ``` `releaseInfo` 정의 다음에 포트를 만들고 `outputPorts`에 넣는다. ```ts const runtimeCapabilities = Object.freeze({ getSnapshot() { return describeRuntimeCapabilities( INSTALLED_RUNTIME_CAPABILITIES, config.CAPABILITY_OVERRIDES, ); }, }); ``` - [ ] **Step 6: 테스트 헬퍼 기본값** `tests/helpers/create-test-application.ts`의 `createApplication` 첫 인자에 추가한다. ```ts runtimeCapabilities: overrides.runtimeCapabilities ?? { getSnapshot: () => Object.freeze([ Object.freeze({ capabilityId: "REALTIME" as const, selected: 0, active: 0, override: "DEFAULT" as const, }), Object.freeze({ capabilityId: "WEB_WORKER" as const, selected: 0, active: 0, override: "DEFAULT" as const, }), Object.freeze({ capabilityId: "SERVICE_WORKER" as const, selected: 0, active: 0, override: "DEFAULT" as const, }), Object.freeze({ capabilityId: "OFFLINE_COMMANDS" as const, selected: 0, active: 0, override: "DEFAULT" as const, }), ]), }, ``` - [ ] **Step 7: 통과 확인** 실행: `corepack pnpm exec vitest run tests/unit/runtime-adapters.test.ts` 기대: 9 passed - [ ] **Step 8: 게이트와 커밋** ```bash corepack pnpm check:types && corepack pnpm lint && corepack pnpm check:architecture && corepack pnpm test:all git add src/application src/bootstrap/runtime-adapters.ts tests/helpers/create-test-application.ts tests/unit/runtime-adapters.test.ts git commit -m "feat: expose the resolved runtime capability snapshot through the application port" ``` --- ### Task 3: 화면이 선택과 활성을 함께 보여준다 **Files:** - Modify: `src/presentation/examples/platform-overview-page.tsx` - Test: `tests/component/platform-overview-page.test.tsx` **Interfaces:** - Consumes: Task 2의 `useApplication().runtime.getCapabilitySnapshot()` - Produces: 없음 (화면 종단) - [ ] **Step 1: 실패하는 테스트 작성** `tests/component/platform-overview-page.test.tsx`의 기존 능력 테스트를 다음 두 개로 교체한다. ```ts it("labels a capability that was never selected as unselected", () => { renderPage(); const region = screen.getByRole("region", { name: "선택적 런타임 능력" }); expect(within(region).getAllByText("미선택")).toHaveLength(4); expect(within(region).queryByText("운영자가 비활성화함")).toBeNull(); }); it("separates an operator disable from a capability that was never selected", () => { render( Object.freeze([ Object.freeze({ capabilityId: "REALTIME" as const, selected: 0, active: 0, override: "DEFAULT" as const, }), Object.freeze({ capabilityId: "WEB_WORKER" as const, selected: 0, active: 0, override: "DEFAULT" as const, }), Object.freeze({ capabilityId: "SERVICE_WORKER" as const, selected: 1, active: 0, override: "DISABLED" as const, }), Object.freeze({ capabilityId: "OFFLINE_COMMANDS" as const, selected: 1, active: 1, override: "DEFAULT" as const, }), ]), }, })} > , ); const region = screen.getByRole("region", { name: "선택적 런타임 능력" }); expect(within(region).getByText("운영자가 비활성화함")).toBeVisible(); expect(within(region).getByText("활성 (1)")).toBeVisible(); expect(within(region).getAllByText("미선택")).toHaveLength(2); }); ``` - [ ] **Step 2: 실패 확인** 실행: `corepack pnpm exec vitest run tests/component/platform-overview-page.test.tsx` 기대: "운영자가 비활성화함" 을 찾지 못해 FAIL - [ ] **Step 3: 화면 구현** `src/presentation/examples/platform-overview-page.tsx`에서 `INSTALLED_RUNTIME_CAPABILITIES` import를 제거하고 스냅샷 소비로 바꾼다. 능력 설명은 정적 표이므로 id로 찾는다. ```ts const CAPABILITY_COPY: Readonly>> = Object.freeze({ REALTIME: Object.freeze({ label: "실시간 수신", description: "WebSocket, SSE, 경계 폴링 런타임은 구현되어 있습니다. 제품 기여물이 엔드포인트와 이벤트 서술자를 제공해야 설치됩니다.", }), WEB_WORKER: Object.freeze({ label: "웹 워커", description: "워커 실행 계약과 전용 타입 프로젝트가 준비되어 있습니다. 프로파일링으로 확인된 CPU 작업이 있어야 설치됩니다.", }), SERVICE_WORKER: Object.freeze({ label: "서비스 워커", description: "참조 런타임과 두 단계 빌드가 준비되어 있습니다. 설치하면 검증된 정적 자산 캐시와 등록 해제 경로가 함께 켜집니다.", }), OFFLINE_COMMANDS: Object.freeze({ label: "오프라인 명령", description: "명령 큐 상태 기계가 준비되어 있습니다. 복구 서술자를 가진 KEYED 오퍼레이션이 있어야 설치됩니다.", }), }); function capabilityBadge( status: RuntimeCapabilityStatus, ): Readonly<{ text: string; variant: "success" | "warning" | "neutral" }> { if (status.selected === 0) { return { text: "미선택", variant: "neutral" }; } if (status.active === 0) { return { text: "운영자가 비활성화함", variant: "warning" }; } return { text: `활성 (${status.active})`, variant: "success" }; } ``` 컴포넌트 본문에서: ```ts const capabilities = runtime.getCapabilitySnapshot(); const activeCapabilityCount = capabilities.filter( (status) => status.active > 0, ).length; ``` 카드 렌더링을 바꾼다. ```tsx {capabilities.map((status) => { const copy = CAPABILITY_COPY[status.capabilityId]; const badge = capabilityBadge(status); return ( {badge.text}} >

{copy.description}

); })} ``` 설치 요약의 능력 지표 힌트를 `"런타임 오버라이드 반영"`으로 바꾸고, 5번 섹션 설명 마지막 문장을 다음으로 교체한다. ``` 여기 표시되는 상태는 정적 선택에 런타임 오버라이드를 적용한 결과입니다. ``` - [ ] **Step 4: 통과 확인** 실행: `corepack pnpm exec vitest run tests/component/platform-overview-page.test.tsx` 기대: 7 passed - [ ] **Step 5: 게이트와 커밋** ```bash corepack pnpm check:types && corepack pnpm lint && corepack pnpm check:i18n && corepack pnpm check:design-system && corepack pnpm test:all git add src/presentation/examples/platform-overview-page.tsx tests/component/platform-overview-page.test.tsx git commit -m "feat: show resolved capability state on the platform overview" ``` --- ### Task 4: 홈에서 플랫폼 구성 화면으로 가는 진입점 **Files:** - Modify: `src/presentation/pages/home-page.tsx` - Test: `tests/component/router.test.tsx` **Interfaces:** - Consumes: `routePath("EXAMPLES_PLATFORM")` - Produces: 없음 - [ ] **Step 1: 실패하는 테스트 작성** `tests/component/router.test.tsx`에 추가한다. ```ts it("reaches the platform overview from the home starter actions", async () => { const user = userEvent.setup(); window.history.pushState({}, "", "/"); renderRouter(); await user.click( await screen.findByRole("link", { name: "플랫폼 구성 보기" }), ); await waitFor(() => expect( screen.getByRole("heading", { name: "플랫폼 구성", level: 1 }), ).toHaveFocus(), ); }); ``` - [ ] **Step 2: 실패 확인** 실행: `corepack pnpm exec vitest run tests/component/router.test.tsx` 기대: "플랫폼 구성 보기" 링크가 없어 FAIL - [ ] **Step 3: 구현** `src/presentation/pages/home-page.tsx`의 `button-row`에 첫 번째 링크로 추가한다. ```tsx 플랫폼 구성 보기 ``` 기존 "UI 구성요소 보기" 링크의 클래스를 `ui-button ui-button--secondary`로 바꾸어 주 행동이 하나가 되게 한다. - [ ] **Step 4: 통과 확인** 실행: `corepack pnpm exec vitest run tests/component/router.test.tsx` 기대: 4 passed `tests/e2e/accessibility.spec.ts`가 홈의 첫 탭 대상을 `"UI 구성요소 보기"`로 단언하므로 함께 바꾼다. ```ts const action = page.getByRole("link", { name: "플랫폼 구성 보기" }); ``` - [ ] **Step 5: 게이트와 커밋** ```bash corepack pnpm check:types && corepack pnpm lint && corepack pnpm test:component git add src/presentation/pages/home-page.tsx tests/component/router.test.tsx tests/e2e/accessibility.spec.ts git commit -m "feat: link the platform overview from the home starter actions" ``` --- ### Task 5: 브라우저 회귀 커버리지 **Files:** - Create: `tests/e2e/platform-overview.spec.ts` - Modify: `tests/visual/platform.visual.spec.ts` - Create: `tests/visual/__snapshots__/platform.visual.spec.ts-snapshots/platform-overview-light-chromium-visual-linux.png` **Interfaces:** - Consumes: 빌드된 번들의 `/examples/platform` - Produces: 없음 **전제:** Playwright 브라우저가 설치되어 있어야 한다. 없으면 `corepack pnpm exec playwright install chromium`을 먼저 실행한다. 설치가 불가능한 환경이면 이 태스크를 건너뛰고 그 사실을 보고한다 — 기준선 없이 스냅샷 테스트를 커밋하면 안 된다. - [ ] **Step 1: e2e 스펙 작성** `tests/e2e/platform-overview.spec.ts`: ```ts import { expect, test } from "../support/browser/strict-browser-test.ts"; import { ROUTE_REGISTRY } from "../../src/features/installed-feature-contracts.ts"; import { SERVER_STATE_PROFILES } from "../../src/contracts/server-state.ts"; test("projects the installed route registry into the shipped bundle", async ({ page, }) => { await page.goto("/examples/platform"); await expect( page.getByRole("heading", { name: "플랫폼 구성", level: 1 }), ).toBeVisible(); const table = page.getByRole("table", { name: "설치된 라우트 목록" }); await expect(table.locator("tbody tr")).toHaveCount( Object.keys(ROUTE_REGISTRY).length, ); for (const routeId of Object.keys(ROUTE_REGISTRY)) { await expect(table.getByText(routeId, { exact: true })).toBeVisible(); } }); test("shows every fixed server-state profile with its budget", async ({ page, }) => { await page.goto("/examples/platform"); const table = page.getByRole("table", { name: "서버 상태 프로파일" }); await expect(table.locator("tbody tr")).toHaveCount( Object.keys(SERVER_STATE_PROFILES).length, ); }); test("states the release contract identity verified at boot", async ({ page, }) => { await page.goto("/examples/platform"); const release = page.locator( "section[aria-labelledby='platform-release-title']", ); await expect(release.getByText(/^sha256:/)).toBeVisible(); }); ``` - [ ] **Step 2: e2e 실행** 실행: `corepack pnpm exec playwright test tests/e2e/platform-overview.spec.ts --project=chromium` 기대: 3 passed - [ ] **Step 3: 시각 회귀 케이스 추가** `tests/visual/platform.visual.spec.ts` 끝에 추가한다. ```ts test("platform composition overview visual contract", async ({ page }) => { await page.setViewportSize({ width: 1280, height: 1100 }); await page.goto("/examples/platform"); await expect( page.locator("section[aria-labelledby='platform-capabilities-title']"), ).toBeVisible(); await expect(page.getByRole("main")).toHaveScreenshot( "platform-overview-light.png", ); }); ``` - [ ] **Step 4: 기준선 생성과 검증** ```bash corepack pnpm test:visual:update corepack pnpm test:visual ``` 기대: 5 passed, 새 PNG 기준선이 `tests/visual/__snapshots__/platform.visual.spec.ts-snapshots/`에 생긴다. - [ ] **Step 5: 커밋** ```bash git add tests/e2e/platform-overview.spec.ts tests/visual git commit -m "test: cover the platform overview with browser and visual regression" ``` --- ### Task 6: 최종 검증과 문서 갱신 **Files:** - Modify: `docs/superpowers/specs/2026-07-31-platform-overview-page-design.md` - [ ] **Step 1: 설계 문서의 제외 항목 갱신** 5절 "의도적 제외"를 다음으로 교체한다. ```markdown ## 5. 런타임 해석 결과 초판은 런타임 오버라이드 해석 결과를 제외했다. 해석이 bootstrap에서 일어나고 표현 계층이 그 계층을 import 할 수 없었기 때문이다. 이후 `RuntimeCapabilitiesPort`가 추가되어 합성 루트가 경계된 스냅샷을 애플리케이션으로 전달한다. 화면은 정적 선택 수와 활성 수를 함께 표시하므로, 운영자가 능력을 껐다는 사실과 애초에 선택되지 않았다는 사실이 구분된다. ``` - [ ] **Step 2: 전체 게이트** ```bash corepack pnpm check:types corepack pnpm lint corepack pnpm check:architecture corepack pnpm test:all corepack pnpm test:coverage corepack pnpm check:registries corepack pnpm check:i18n corepack pnpm check:design-system corepack pnpm check:diagnostics corepack pnpm check:ci corepack pnpm check:test-evidence:source corepack pnpm verify:compatibility corepack pnpm build ``` 기대: 전부 PASS - [ ] **Step 3: 제거 하네스 4종** ```bash corepack pnpm test:sample-removal corepack pnpm test:optional-recipe-removal corepack pnpm test:browser-file-storage-removal corepack pnpm test:realtime-removal ``` 기대: 전부 PASS. 특히 `test:sample-removal`은 화면이 여전히 파생 전용인지 확인한다. - [ ] **Step 4: 커밋** ```bash git add docs/superpowers/specs/2026-07-31-platform-overview-page-design.md git commit -m "docs: record the resolved capability path in the overview design" ``` ## Self-Review **Spec coverage:** 설계 문서 5절(의도적 제외)이 Task 1–3으로 닫힌다. 6절 변경 파일 목록은 이미 반영됐다. 7절 검증은 Task 6이 수행한다. 진입 경로와 브라우저 커버리지는 초판 범위 밖이었으므로 Task 4–5로 추가했다. **Placeholder scan:** TBD 없음. 모든 코드 단계에 실제 코드가 있다. **Type consistency:** `describeRuntimeCapabilities`(Task 1) → `RuntimeCapabilitiesPort.getSnapshot`(Task 2) → `runtime.getCapabilitySnapshot()`(Task 3)로 이름과 반환 타입이 일치한다. `RuntimeCapabilityStatus`의 필드명 `capabilityId` / `selected` / `active` / `override`가 세 태스크에서 동일하다.