Files
tech-log-frontend/docs/superpowers/plans/2026-07-31-platform-overview-completion.md
T

29 KiB

플랫폼 구성 화면 완성 구현 계획

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/presentationsrc/adapterssrc/bootstrap을 import 할 수 없다 (presentation-does-not-know-adapters).
  • src/applicationsrc/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:

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를 재사용해 활성 판정 규칙이 한 곳에만 존재하게 한다.

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<RuntimeCapabilityId, Readonly<{ selected: number; active: number }>>
  > = {
    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: 게이트와 커밋
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.tsdescribe 블록 안에 추가한다.

  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 기대: runtimeCapabilitiesoutputPorts에 없어 타입/런타임 FAIL

  • Step 3: 포트 정의

src/application/ports/runtime-capabilities-port.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.tsApplicationOutputPorts에 필드를 더하고, runtime 그룹에 접근자를 추가한다.

import type { RuntimeCapabilitiesPort } from "./ports/runtime-capabilities-port.ts";

ApplicationOutputPorts 안:

  runtimeCapabilities: RuntimeCapabilitiesPort;

runtime 그룹 안, getReleaseSummary 다음:

      getCapabilitySnapshot() {
        return ports.runtimeCapabilities.getSnapshot();
      },

src/application/ports/in/application-api.tsruntime 그룹에 선언을 추가하고 타입을 import 한다.

import type { RuntimeCapabilitySnapshot } from "../runtime-capabilities-port.ts";

export type { RuntimeCapabilitySnapshot };
  runtime: Readonly<{
    getReleaseSummary(): Promise<ReleaseSummary>;
    getCapabilitySnapshot(): RuntimeCapabilitySnapshot;
  }>;
  • Step 5: 합성 루트에서 포트 생성

src/bootstrap/runtime-adapters.ts에 import를 추가한다.

import {
  describeRuntimeCapabilities,
} from "../contracts/runtime-capabilities.ts";
import { INSTALLED_RUNTIME_CAPABILITIES } from "../features/installed-runtime-capabilities.ts";

releaseInfo 정의 다음에 포트를 만들고 outputPorts에 넣는다.

  const runtimeCapabilities = Object.freeze({
    getSnapshot() {
      return describeRuntimeCapabilities(
        INSTALLED_RUNTIME_CAPABILITIES,
        config.CAPABILITY_OVERRIDES,
      );
    },
  });
  • Step 6: 테스트 헬퍼 기본값

tests/helpers/create-test-application.tscreateApplication 첫 인자에 추가한다.

      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: 게이트와 커밋
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의 기존 능력 테스트를 다음 두 개로 교체한다.

  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(
      <ApplicationProvider
        application={createTestApplication({
          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: 1,
                  active: 0,
                  override: "DISABLED" as const,
                }),
                Object.freeze({
                  capabilityId: "OFFLINE_COMMANDS" as const,
                  selected: 1,
                  active: 1,
                  override: "DEFAULT" as const,
                }),
              ]),
          },
        })}
      >
        <LocaleProvider>
          <PlatformOverviewPage />
        </LocaleProvider>
      </ApplicationProvider>,
    );

    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로 찾는다.

const CAPABILITY_COPY: Readonly<Record<RuntimeCapabilityId, Readonly<{
  label: string;
  description: string;
}>>> = 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" };
}

컴포넌트 본문에서:

  const capabilities = runtime.getCapabilitySnapshot();
  const activeCapabilityCount = capabilities.filter(
    (status) => status.active > 0,
  ).length;

카드 렌더링을 바꾼다.

          {capabilities.map((status) => {
            const copy = CAPABILITY_COPY[status.capabilityId];
            const badge = capabilityBadge(status);
            return (
              <Card
                key={status.capabilityId}
                title={copy.label}
                footer={<Badge variant={badge.variant}>{badge.text}</Badge>}
              >
                <p>{copy.description}</p>
              </Card>
            );
          })}

설치 요약의 능력 지표 힌트를 "런타임 오버라이드 반영"으로 바꾸고, 5번 섹션 설명 마지막 문장을 다음으로 교체한다.

여기 표시되는 상태는 정적 선택에 런타임 오버라이드를 적용한 결과입니다.
  • Step 4: 통과 확인

실행: corepack pnpm exec vitest run tests/component/platform-overview-page.test.tsx 기대: 7 passed

  • Step 5: 게이트와 커밋
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에 추가한다.

  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.tsxbutton-row에 첫 번째 링크로 추가한다.

          <Link className="ui-button" to={routePath("EXAMPLES_PLATFORM")}>
            플랫폼 구성 보기
          </Link>

기존 "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 구성요소 보기"로 단언하므로 함께 바꾼다.

  const action = page.getByRole("link", { name: "플랫폼 구성 보기" });
  • Step 5: 게이트와 커밋
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:

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 끝에 추가한다.

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: 기준선 생성과 검증
corepack pnpm test:visual:update
corepack pnpm test:visual

기대: 5 passed, 새 PNG 기준선이 tests/visual/__snapshots__/platform.visual.spec.ts-snapshots/에 생긴다.

  • Step 5: 커밋
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절 "의도적 제외"를 다음으로 교체한다.

## 5. 런타임 해석 결과

초판은 런타임 오버라이드 해석 결과를 제외했다. 해석이 bootstrap에서 일어나고 표현 계층이 그 계층을
import 할 수 없었기 때문이다.

이후 `RuntimeCapabilitiesPort`가 추가되어 합성 루트가 경계된 스냅샷을 애플리케이션으로 전달한다.
화면은 정적 선택 수와 활성 수를 함께 표시하므로, 운영자가 능력을 껐다는 사실과 애초에 선택되지
않았다는 사실이 구분된다.
  • Step 2: 전체 게이트
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종
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: 커밋
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가 세 태스크에서 동일하다.