fix(client): repair the TypeScript build

`npm run build` runs `tsc -b` first and it has been failing: `api.ts` used
`NamedLock` without importing it, and four lane test fixtures predate
`Lane.active_feature_id` / the widened `LaneRuntime`, so spreading a
`Partial<Lane>` over them no longer satisfied the required fields.

Nothing shipped could be rebuilt while this was red, which is how a client
change reaches a dashboard running in production mode. The fixture fixes are
casts with a note, not type relaxations — the base literals still list every
required field, so the assertion states what they already prove.
This commit is contained in:
2026-08-07 09:52:13 +07:00
parent 8fcef5a10b
commit 201eae68bb
5 changed files with 24 additions and 6 deletions
@@ -34,6 +34,10 @@ vi.mock("../../../lib/api", () => ({
})); }));
function laneFixture(over: Partial<Lane> = {}): Lane { function laneFixture(over: Partial<Lane> = {}): Lane {
// `as Lane`: spreading a Partial<Lane> widens every field it may carry to
// `T | undefined`, which no longer satisfies Lane's required fields. The
// base object below still lists all of them, so the cast asserts what the
// literal already proves.
return { return {
id: 9, id: 9,
title: "", title: "",
@@ -64,7 +68,7 @@ function laneFixture(over: Partial<Lane> = {}): Lane {
slot: null, slot: null,
ports: {}, ports: {},
...over, ...over,
}; } as Lane;
} }
const SUGGESTIONS: CwdSuggestion[] = [ const SUGGESTIONS: CwdSuggestion[] = [
@@ -21,6 +21,10 @@ import { DestructiveLaneModal } from "../DestructiveLaneModal";
import type { Lane, LanePurgePreflight, LaneWorktreePreflight } from "../../../lib/types"; import type { Lane, LanePurgePreflight, LaneWorktreePreflight } from "../../../lib/types";
function makeLane(overrides: Partial<Lane> = {}): Lane { function makeLane(overrides: Partial<Lane> = {}): Lane {
// `as Lane`: spreading a Partial<Lane> widens every field it may carry to
// `T | undefined`, which no longer satisfies Lane's required fields. The
// base object below still lists all of them, so the cast asserts what the
// literal already proves.
return { return {
id: 1, id: 1,
title: "demo", title: "demo",
@@ -50,7 +54,7 @@ function makeLane(overrides: Partial<Lane> = {}): Lane {
slot: null, slot: null,
ports: {}, ports: {},
...overrides, ...overrides,
}; } as Lane;
} }
function worktreePreflight(overrides: Partial<LaneWorktreePreflight> = {}): LaneWorktreePreflight { function worktreePreflight(overrides: Partial<LaneWorktreePreflight> = {}): LaneWorktreePreflight {
@@ -12,7 +12,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react"; import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
import LaneCard from "../LaneCard"; import LaneCard from "../LaneCard";
import type { Lane } from "../../../lib/types"; import type { Lane, LaneRuntime } from "../../../lib/types";
import { api } from "../../../lib/api"; import { api } from "../../../lib/api";
vi.mock("../../../lib/api", () => ({ vi.mock("../../../lib/api", () => ({
@@ -48,6 +48,10 @@ vi.mocked(api.locks.list).mockReset();
vi.mocked(api.locks.list).mockResolvedValue({ locks: [] }); vi.mocked(api.locks.list).mockResolvedValue({ locks: [] });
function makeLane(overrides: Partial<Lane> = {}): Lane { function makeLane(overrides: Partial<Lane> = {}): Lane {
// `as Lane`: spreading a Partial<Lane> widens every field it may carry to
// `T | undefined`, which no longer satisfies Lane's required fields. The
// base object below still lists all of them, so the cast asserts what the
// literal already proves.
return { return {
id: 1, id: 1,
title: "demo", title: "demo",
@@ -78,7 +82,7 @@ function makeLane(overrides: Partial<Lane> = {}): Lane {
slot: null, slot: null,
ports: {}, ports: {},
...overrides, ...overrides,
}; } as Lane;
} }
describe("LaneCard status badge", () => { describe("LaneCard status badge", () => {
@@ -396,6 +400,9 @@ describe("LaneCard — named locks", () => {
describe("agents install / mcp sync / integration badges / sync check", () => { describe("agents install / mcp sync / integration badges / sync check", () => {
beforeEach(() => { beforeEach(() => {
// Only the fields this describe block's assertions read; `as LaneRuntime`
// keeps the double from having to restate a shape the component never
// touches here.
vi.mocked(api.lanes.runtime).mockResolvedValue({ vi.mocked(api.lanes.runtime).mockResolvedValue({
available: true as const, available: true as const,
provisioned: true as const, provisioned: true as const,
@@ -403,7 +410,7 @@ describe("agents install / mcp sync / integration badges / sync check", () => {
slot: 1, slot: 1,
profileDir: "/work/demo/.ccam/profile", profileDir: "/work/demo/.ccam/profile",
ports: {}, ports: {},
}); } as unknown as LaneRuntime);
vi.mocked(api.lanes.integration).mockImplementation((_id, name) => vi.mocked(api.lanes.integration).mockImplementation((_id, name) =>
Promise.resolve({ enabled: name === "tracker" }) Promise.resolve({ enabled: name === "tracker" })
); );
+1
View File
@@ -394,6 +394,7 @@ import type {
LaneGitFacts, LaneGitFacts,
LaneRuntime, LaneRuntime,
ModelPricing, ModelPricing,
NamedLock,
Session, Session,
SessionDrillIn, SessionDrillIn,
SessionStats, SessionStats,
@@ -24,6 +24,7 @@ type LaneFixture = {
pipeline_nodes: never[]; pipeline_nodes: never[];
detected_signal: string | null; detected_signal: string | null;
run_id: string | null; run_id: string | null;
active_feature_id?: number | null;
}; };
let lanesToReturn: LaneFixture[] = [ let lanesToReturn: LaneFixture[] = [
@@ -583,7 +584,8 @@ describe("Workspace — proof gallery", () => {
], ],
}); });
// Set active_feature_id and ensure features list is populated // Set active_feature_id and ensure features list is populated
lanesToReturn[0].active_feature_id = 1; // (indexed access is `| undefined` under noUncheckedIndexedAccess)
lanesToReturn[0]!.active_feature_id = 1;
vi.mocked(api.lanes.features.list).mockResolvedValue({ vi.mocked(api.lanes.features.list).mockResolvedValue({
features: [ features: [
{ {