Vertex Macro | Financial Cloud Cloud · Builder Articles
Kiro: Build the Fab SPC Drift Synchronization Portal from Scratch
Educational engineering purpose only. This is a software architecture exercise and not process-release advice.
Step 7 — Render the portal
Kiro prompt
Create src/ui/render.ts and src/main.ts to render a dark engineering portal. Include summary cards, search, sort buttons, a risk board, FDC health-link cards, dynamic matching notes, yield triage notes, and runbook steps. Use assessFleet from the domain layer.
Code sample — src/main.ts
import { cdSemTools, fdcHealthLinks, runbookSteps } from "./data/sampleData";
import { assessFleet } from "./domain/risk";
import { renderApp } from "./ui/render";
import "./styles.css";
const root = document.querySelector<HTMLDivElement>("#app");
if (!root) {
throw new Error("Missing #app root element");
}
renderApp(root, {
tools: cdSemTools,
assessments: assessFleet(cdSemTools),
fdcHealthLinks,
runbookSteps
});
Explanation
Business logic: The app starts with known CD-SEM records, calculates risk assessments, and passes all display-ready data to the rendering layer.
Code logic: main.ts is intentionally thin. It imports data, invokes the domain engine, checks the DOM root, and delegates UI construction to renderApp.
Expected result: Running npm run dev displays the portal. If #app is missing, the app fails fast with a clear error.
System design decision
- The entry point has orchestration only. It should not contain business rules or HTML complexity. This makes generated code easier to review and keeps the app startup path obvious.
- Fail-fast root validation avoids silent blank screens. If the HTML shell is broken, developers get an explicit error rather than debugging an empty page. This improves workshop troubleshooting.
- Assessments are calculated before rendering. The UI receives already interpreted domain data. This separation allows risk rules to evolve independently from visual layout and interaction code.
Step 8 — Tests
Kiro prompt
Create tests/risk.test.ts using Vitest. Test Release, ApcGuard, HoldReview for slope, HoldReview for fleet deviation, RunGoldenWafer for residual noise, and Watch for stale blind-window conditions.
Code sample — tests/risk.test.ts
import { describe, expect, it } from "vitest";
import { assessTool } from "../src/domain/risk";
import type { CdSemTool } from "../src/domain/types";
function tool(overrides: Partial<CdSemTool> = {}): CdSemTool {
return {
id: "CDSEM-T",
layer: "Gate ADI",
symptom: "baseline",
blindWindowHours: 1,
tmgNm: 0.1,
mandelSlope: 1,
fleetDeviationSigma: 0.5,
residualThreeSigmaNm: 0.08,
deltaMeanSeries: [0.01, 0.02],
...overrides
};
}
describe("assessTool", () => {
it("releases a healthy tool", () => {
expect(assessTool(tool()).action).toBe("Release");
});
it("guards APC when TMG exceeds limit", () => {
expect(assessTool(tool({ tmgNm: 0.25 })).action).toBe("ApcGuard");
});
it("requires hold review when Mandel Slope is out of range", () => {
expect(assessTool(tool({ mandelSlope: 1.031 })).action).toBe("HoldReview");
});
it("requires hold review when Fleet deviation exceeds 3 sigma", () => {
expect(assessTool(tool({ fleetDeviationSigma: 3.2 })).action).toBe("HoldReview");
});
it("runs golden wafer when residual noise exceeds limit", () => {
expect(assessTool(tool({ residualThreeSigmaNm: 0.21 })).action).toBe("RunGoldenWafer");
});
it("watches a stale blind window", () => {
expect(assessTool(tool({ blindWindowHours: 8 })).action).toBe("Watch");
});
});
Explanation
Business logic: The tests encode the expected advisory result for each metrology condition.
Code logic: A fixture factory creates a healthy default tool and overrides one field per test. Each assertion verifies a specific rule.
Expected result: npm test passes when the risk engine follows the spec. A future rule change that breaks advisory behavior will fail tests.
System design decision
- One field changes per test. This isolates cause and effect, making failures easy to understand. Kiro-generated tests should avoid mixing many unrelated risk factors unless explicitly testing priority behavior.
- The fixture factory reduces duplication. Developers can add new tests quickly without copying full tool objects. This improves maintainability and encourages more edge-case coverage.
- Tests assert actions rather than implementation details. The business outcome matters more than the exact scoring arithmetic. This allows internal scoring to evolve while preserving operational behavior.
Step 9 — Hooks
Kiro prompt
Create .kiro/hooks/test-domain-on-save.json. Run npm test when files under src/domain or tests are saved. Also create a documentation agent hook that updates docs/decision-log.md after spec task execution.
Code sample — .kiro/hooks/test-domain-on-save.json
{
"version": "v1",
"hooks": [
{
"name": "test-domain-on-save",
"description": "Run the deterministic risk tests when domain or test files are saved.",
"trigger": "PostFileSave",
"matcher": "^(src/domain/.*\\.ts|tests/.*\\.ts)$",
"action": {
"type": "command",
"command": "npm test"
},
"timeout": 60,
"enabled": true
}
]
}
Explanation
Business logic: If risk logic or tests change, the project immediately checks whether advisory behavior remains valid.
Code logic: The JSON hook listens to file-save events matching domain or test TypeScript files and runs npm test.
Expected result: Saving src/domain/risk.ts triggers the test suite and surfaces regressions quickly.
System design decision
- The hook targets high-value files only. Running tests after every CSS edit wastes time. Matching domain and test files keeps automation relevant and fast.
- The action is deterministic.
npm testhas predictable pass/fail output. This makes it safer than a broad agent action that might modify files unexpectedly. - Timeout protects the developer loop. A hook should not hang the IDE. A 60-second timeout is enough for the workshop suite and teaches participants to bound automation.
Step 10 — Final Kiro review
Kiro prompt
Review the implementation against the fab-spc-portal spec. Check domain correctness, safety language, test coverage, accessibility, project structure, and hook safety. Produce a prioritized review report without modifying files.
Expected review themes
- Are advisory actions safe and non-autonomous?
- Are thresholds centralized?
- Are risk reasons explainable?
- Does every critical rule have a test?
- Can a new developer understand the project from steering and spec files?
- Are hooks scoped narrowly enough?
System design decision
- The final review makes Kiro an engineering partner, not an unchecked code generator. Developers ask for structured critique, then decide which findings to accept. This reinforces professional accountability.
- Review without modification prevents surprise changes. The goal is to inspect coverage and risk before further edits. This mirrors pull-request review, where analysis and implementation are separate moments.
- The review closes the spec loop. The workshop starts with intent and ends by comparing code against that intent. That is the core habit professional teams should build with Kiro.
# Expanded Lab: Coding from the HTML Demo in Kiro
This expanded lab keeps the original build guide and adds more implementation detail from the HTML demo. The aim is to train developers to use Kiro to convert a working single-file prototype into maintainable TypeScript while preserving as much demo behavior as possible.
Step 11 — Ask Kiro to analyze the monolithic HTML demo
Kiro prompt
Analyze the uploaded Fab SPC Drift Synchronization HTML demo. Identify all major UI sections, JavaScript data structures, functions, event handlers, CSS design tokens, and state transitions. Return a module extraction plan for a TypeScript/Vite implementation. Do not modify files yet.
Expected Kiro analysis output
Kiro should identify these demo elements:
- Header and hero section.
- Summary statistic cards.
- Tab navigation using
.tabbuttons and.portalpanels. toolsarray containing CD-SEM records.fdcarray containing FDC health-link records.runbookarray containing project implementation steps.renderStatic()for FDC cards, runbook steps, and market cards.render()for risk-board rows.panel()for expandable tool-detail evidence panels.path(),spark(), andfleetSvg()for inline SVG visualizations.clock()for live time display.setInterval()simulation that mutates blind-window and risk values.
System design decision
- Analysis happens before rewriting. Professional developers should ask Kiro to explain and classify a prototype before allowing it to generate replacement code. This avoids losing important behavior hidden in the original JavaScript.
- The output becomes a migration checklist. Each identified function maps to a target module and test strategy. This makes it clear which demo behavior has been preserved and which behavior was intentionally redesigned.
- Simulation is separated from production logic. The demo mutates values with
setInterval(). In a professional implementation, simulation belongs in a demo adapter, not the domain model. Kiro should isolate it so tests remain deterministic.
Step 12 — Extract demo data into typed records
Kiro prompt
Convert the demo's tools, fdc, and runbook arrays into typed TypeScript fixture modules. Preserve the values and labels from the HTML demo, but use camelCase fields and explicit interfaces. Put tools in src/data/tools.ts, FDC links in src/data/fdc.ts, and runbook steps in src/data/runbook.ts.
Code sample — src/data/tools.ts
import type { CdSemTool } from "../domain/types";
export const cdSemTools: CdSemTool[] = [
{
id: "CDSEM-01",
layer: "Gate ADI",
symptom: "Stable master, production anchor",
risk: 18,
blindWindowHours: 1.2,
tmgNm: 0.12,
mandelSlope: 1.0,
fleetDeviationSigma: 0.4,
residualThreeSigmaNm: 0.08,
action: "Release",
deltaMeanSeries: [0.02, 0.01, 0.03, 0.01, 0.02, 0.0, 0.01, 0.02, 0.01, 0.02]
},
{
id: "CDSEM-03",
layer: "Fin dense CD",
symptom: "Deflector DAC wobble, slope mismatch",
risk: 91,
blindWindowHours: 7.4,
tmgNm: 0.23,
mandelSlope: 1.031,
fleetDeviationSigma: 3.2,
residualThreeSigmaNm: 0.16,
action: "HoldReview",
deltaMeanSeries: [0.02, 0.03, 0.06, 0.05, 0.08, 0.12, 0.16, 0.2, 0.22, 0.24]
},
{
id: "CDSEM-05",
layer: "Contact/Via",
symptom: "Vacuum slow degradation, residual rising",
risk: 76,
blindWindowHours: 6.9,
tmgNm: 0.19,
mandelSlope: 1.006,
fleetDeviationSigma: 2.4,
residualThreeSigmaNm: 0.21,
action: "RunGoldenWafer",
deltaMeanSeries: [0.02, 0.01, 0.04, 0.06, 0.08, 0.1, 0.12, 0.13, 0.16, 0.18]
}
];
Explanation
Business logic: These records preserve the demo's production-like cases: healthy master tool, slope mismatch, vacuum degradation, residual noise, Fleet deviation, and blind-window aging.
Code logic: The monolithic demo used short field names such as blind, tmg, slope, fleet, and resid. The TypeScript version switches to explicit names such as blindWindowHours, tmgNm, mandelSlope, and residualThreeSigmaNm.
Expected result: The app can render the same scenario as the HTML demo while allowing TypeScript to validate the shape of every record.
System design decision
- Explicit field names reduce cognitive load. The original demo used compact field names for brevity. In production-style code, domain names should be self-documenting because multiple developers and reviewers will touch them.
- Fixture data remains close to the prototype. Preserving original values lets developers compare the TypeScript app against the HTML demo. This makes visual and behavioral parity easier to verify.
- Actions are normalized as typed values. The demo labels are display strings such as
HOLD REVIEWandGOLDEN WAFER. The TypeScript model uses stable enum-like action values and lets UI formatting handle labels.
Step 13 — Add action label mapping from demo labels
Kiro prompt
Create a display mapping for advisory actions. Domain actions must be stable TypeScript values, while UI labels should match the HTML demo labels such as RELEASE, WATCH, HOLD REVIEW, GOLDEN WAFER, ROUTE LIMIT, RUN SPC, and APC GUARD.
Code sample — src/domain/actions.ts
import type { AdvisoryAction } from "./types";
export const actionLabels: Record<AdvisoryAction, string> = {
Release: "RELEASE",
Watch: "WATCH",
RunGoldenWafer: "GOLDEN WAFER",
RouteLimit: "ROUTE LIMIT",
RunSpc: "RUN SPC",
ApcGuard: "APC GUARD",
HoldReview: "HOLD REVIEW"
};
export function isHighAttentionAction(action: AdvisoryAction): boolean {
return action === "ApcGuard" || action === "HoldReview";
}
export function isWatchAction(action: AdvisoryAction): boolean {
return action === "Watch" || action === "RunSpc" || action === "RunGoldenWafer" || action === "RouteLimit";
}
Explanation
Business logic: Fab engineers see familiar labels from the demo, while internal code keeps stable values for tests and logic.
Code logic: actionLabels converts domain actions to display labels. Helper functions classify actions for styling.
Expected result: UI output matches the demo, and tests can assert stable action values without depending on label capitalization.
System design decision
- Display text is not domain logic. Labels may change for localization or UX reasons. Domain values should remain stable so tests and business rules do not break when UI text changes.
- Styling helpers avoid string scattering. Without helper functions, every renderer might check different label strings. Centralized helpers keep color and severity logic consistent.
- The mapping supports future localization. The same action value can map to English, Traditional Chinese, or internal fab terminology without rewriting the risk engine.
Step 14 — Convert demo search and sort logic into pure helpers
Kiro prompt
Extract search and sort behavior from the HTML demo into pure TypeScript functions. Search should match tool id, layer, or symptom. Sort should support risk, blindWindowHours, tmgNm, mandelSlope, fleetDeviationSigma, and residualThreeSigmaNm. Add unit tests.
Code sample — src/domain/filters.ts
import type { CdSemTool } from "./types";
export type ToolSortKey =
| "risk"
| "blindWindowHours"
| "tmgNm"
| "mandelSlope"
| "fleetDeviationSigma"
| "residualThreeSigmaNm";
export function filterTools(tools: CdSemTool[], query: string): CdSemTool[] {
const normalized = query.trim().toLowerCase();
if (!normalized) {
return tools;
}
return tools.filter((tool) => {
return [tool.id, tool.layer, tool.symptom]
.join(" ")
.toLowerCase()
.includes(normalized);
});
}
export function sortTools(tools: CdSemTool[], sortKey: ToolSortKey): CdSemTool[] {
return [...tools].sort((a, b) => b[sortKey] - a[sortKey]);
}
export function filterAndSortTools(
tools: CdSemTool[],
query: string,
sortKey: ToolSortKey
): CdSemTool[] {
return sortTools(filterTools(tools, query), sortKey);
}
Explanation
Business logic: Engineers need to find a specific CD-SEM, layer, or symptom quickly and prioritize tools by risk, stale SPC window, TMG, slope, Fleet deviation, or residual noise.
Code logic: filterTools performs case-insensitive matching across searchable fields. sortTools returns a copy instead of mutating the original array. filterAndSortTools composes both behaviors.
Expected result: The TypeScript app reproduces the HTML demo's search and sort behavior while making it unit-testable.
System design decision
- Filtering and sorting are pure domain/UI-support functions. They do not need DOM access. Extracting them enables unit tests and prevents event handlers from becoming business logic containers.
- Sorting returns a new array. Mutating shared fixture data can produce confusing UI bugs, especially when combined with repeated renders. Immutable output keeps state transitions predictable.
- Search fields are deliberately constrained. Matching every property can produce surprising results. The workshop chooses id, layer, and symptom because those correspond to how engineers look for tools in the demo.
Step 15 — Convert demo SVG helpers into chart utilities
Kiro prompt
Extract the demo SVG path, sparkline, and fleet bar chart logic into src/ui/chart.ts. Keep the functions deterministic and return strings. Add tests for path generation with flat and increasing series.
Code sample — src/ui/chart.ts
export function svgPath(values: number[], width: number, height: number, padding = 6): string {
if (values.length === 0) {
return "";
}
if (values.length === 1) {
const x = width / 2;
const y = height / 2;
return `M${x.toFixed(1)} ${y.toFixed(1)}`;
}
const min = Math.min(...values);
const max = Math.max(...values);
const range = max - min || 1;
const innerWidth = width - padding * 2;
const innerHeight = height - padding * 2;
return values
.map((value, index) => {
const x = padding + (index * innerWidth) / (values.length - 1);
const y = padding + innerHeight - ((value - min) / range) * innerHeight;
return `${index === 0 ? "M" : "L"}${x.toFixed(1)} ${y.toFixed(1)}`;
})
.join(" ");
}
export function sparkline(values: number[], className = "line"): string {
return `
<svg class="spark" viewBox="0 0 120 32" preserveAspectRatio="none" aria-hidden="true">
<path class="${className}" d="${svgPath(values, 120, 32, 2)}"></path>
</svg>
`;
}
Explanation
Business logic: Small charts make drift trends visible without requiring a full charting library. Engineers can see whether ΔMean is stable, trending, or noisy.
Code logic: svgPath scales a numeric series into SVG path coordinates. sparkline wraps the path in SVG markup and marks it as decorative with aria-hidden.
Expected result: The generated TypeScript app can reproduce the demo's inline spark charts and larger detail-panel trajectories.
System design decision
- Chart helpers return strings because the app is framework-free. This keeps the rendering model consistent with the demo and avoids introducing a charting dependency into a two-hour workshop.
- Edge cases are handled explicitly. Empty and single-point series would produce invalid math in a naive path function. Kiro should generate safe helpers because production data may be incomplete.
- SVG helpers are isolated for testing. Rendering math is easy to break when refactoring. Keeping it in
chart.tsallows precise tests without launching a browser.
Step 16 — Convert tab switching into a reusable controller
Kiro prompt
Extract the demo tab switching behavior into src/ui/tabs.ts. Implement activateTab(tabId), bindTabs(container), and getActiveTab(container). Use data-tab attributes like the HTML demo. Keep it framework-free and accessible.
Code sample — src/ui/tabs.ts
export function activateTab(tabId: string, root: ParentNode = document): void {
root.querySelectorAll<HTMLElement>(".tab").forEach((button) => {
const isActive = button.dataset.tab === tabId;
button.classList.toggle("active", isActive);
button.setAttribute("aria-selected", String(isActive));
});
root.querySelectorAll<HTMLElement>(".portal").forEach((panel) => {
const isActive = panel.id === tabId;
panel.classList.toggle("active", isActive);
panel.toggleAttribute("hidden", !isActive);
});
}
export function bindTabs(root: ParentNode = document): void {
root.querySelectorAll<HTMLButtonElement>(".tab[data-tab]").forEach((button) => {
button.addEventListener("click", () => {
const tabId = button.dataset.tab;
if (tabId) {
activateTab(tabId, root);
}
});
});
}
export function getActiveTab(root: ParentNode = document): string | undefined {
return root.querySelector<HTMLElement>(".tab.active")?.dataset.tab;
}
Explanation
Business logic: The portal uses tabs to separate overview, live risk board, FDC, matching, triage, and runbook workflows.
Code logic: activateTab toggles button and panel classes, sets aria-selected, and uses hidden to remove inactive panels from assistive technology. bindTabs attaches click handlers once.
Expected result: The TypeScript app behaves like the demo but improves accessibility over class-only toggling.
System design decision
- Tab state is centralized. The original demo attached inline behavior directly in a query selector loop. A controller function makes tab behavior reusable and testable.
- ARIA state is updated with visual state. Professional UI code should not only toggle CSS classes. Screen readers need explicit active state and inactive panels should be hidden.
- The controller remains framework-free. The workshop keeps the original demo's simplicity while introducing better structure and accessibility.
Step 17 — Convert detail panel rendering
Kiro prompt
Convert the demo panel(p) function into a TypeScript renderer that receives a CdSemTool and RiskAssessment. It should render status boxes, TMG vs UCL, slope, Fleet σ, residual 3σ, blind window, recommendation, evidence, SVG trajectory, and root-cause hint.
Code sample — src/ui/board.ts
import type { CdSemTool, RiskAssessment } from "../domain/types";
import { actionLabels } from "../domain/actions";
import { svgPath } from "./chart";
export function renderDetailPanel(tool: CdSemTool, assessment: RiskAssessment): string {
const trajectory = svgPath(tool.deltaMeanSeries, 680, 190, 20);
return `
<div class="detail" id="detail-${tool.id}">
<div class="detail-in">
<div class="grid2">
<div class="chart-card">
<div class="chart-title">
<span>ΔMean trajectory between scheduled SPC</span>
<span>${escapeHtml(tool.symptom)}</span>
</div>
<svg class="svg eq" viewBox="0 0 680 190" preserveAspectRatio="none" role="img" aria-label="${tool.id} drift trajectory">
<line class="axis" x1="20" y1="150" x2="660" y2="150"></line>
<line class="gridline" x1="20" y1="80" x2="660" y2="80"></line>
<path class="fill" d="${trajectory} L 660 150 L 20 150 Z"></path>
<path class="${assessment.score > 70 ? "line2" : "line"}" d="${trajectory}"></path>
</svg>
</div>
<div class="metrics">
<div class="chart-title"><span>Tool Health Evidence</span><span>human review required</span></div>
<div class="metricgrid">
${metricBox("Status", assessment.level.toUpperCase(), assessment.level === "critical" ? "neg" : "yellow")}
${metricBox("Action", actionLabels[assessment.action], assessment.action === "Release" ? "pos" : "yellow")}
${metricBox("Score", String(assessment.score), assessment.score >= 65 ? "neg" : "pos")}
${metricBox("Reasons", String(assessment.reasons.length), "cyan")}
</div>
</div>
</div>
<div class="box" style="margin-top:12px">
<div class="boxl">Root-cause hint</div>
<div class="strategy">${escapeHtml(tool.id)} on ${escapeHtml(tool.layer)}: ${escapeHtml(tool.symptom)}.</div>
</div>
</div>
</div>
`;
}
function metricBox(label: string, value: string, tone: "pos" | "neg" | "yellow" | "cyan"): string {
return `<div class="box"><div class="boxl">${label}</div><div class="boxv ${tone}">${value}</div></div>`;
}
function escapeHtml(value: string): string {
return value
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
Explanation
Business logic: The detail panel gives engineers the evidence behind a recommendation: drift trajectory, score, action, severity, and root-cause hint.
Code logic: The renderer composes HTML from typed inputs. It uses svgPath for chart geometry, actionLabels for display strings, and escapeHtml to prevent fixture text from becoming unsafe markup.
Expected result: Clicking or expanding a tool can show a panel similar to the HTML demo but implemented as modular TypeScript.
System design decision
- Evidence panels increase trust. A risk score alone is not enough. Developers should preserve the demo's detail-panel concept because it explains the recommendation.
- Escaping is added during migration. The original demo controlled all strings, but professional code should not assume future data is always safe. Escaping protects against accidental HTML injection.
- Chart rendering remains lightweight. The panel reproduces the prototype's SVG approach without external dependencies. This is appropriate for a local Kiro workshop and keeps code review manageable.
Step 18 — Replace live random mutation with deterministic demo mode
Kiro prompt
The HTML demo uses setInterval and Math.random to mutate risk values. Replace this with an optional deterministic demo ticker that accepts a seeded sequence of deltas. Keep production state immutable and tests deterministic.
Code sample — src/domain/demoTicker.ts
import type { CdSemTool } from "./types";
export function applyDemoTick(tools: CdSemTool[], deltas: number[]): CdSemTool[] {
return tools.map((tool, index) => {
const delta = deltas[index % deltas.length] ?? 0;
const nextBlindWindowHours = Math.max(0, round(tool.blindWindowHours + 0.02));
const nextRisk = Math.max(0, Math.min(100, Math.round(tool.risk + delta)));
const lastDeltaMean = tool.deltaMeanSeries.at(-1) ?? 0;
return {
...tool,
blindWindowHours: nextBlindWindowHours,
risk: nextRisk,
deltaMeanSeries: [...tool.deltaMeanSeries.slice(1), round(Math.max(0, lastDeltaMean + delta / 1000))]
};
});
}
function round(value: number): number {
return Number(value.toFixed(3));
}
Explanation
Business logic: The demo can still show values changing over time, but changes are predictable for tests and demos.
Code logic: applyDemoTick returns a new array with updated blind-window duration, risk, and time-series data. It does not mutate the original tools.
Expected result: Developers can demonstrate live-like behavior while unit tests remain stable.
System design decision
- Randomness is removed from core logic. The original demo used
Math.random()for visual movement. Professional code should isolate randomness so tests and reviews do not depend on non-deterministic behavior. - Immutable updates simplify rendering. Returning new objects makes state changes easier to reason about and prevents hidden mutations across modules.
- Demo mode is explicit. A ticker is useful for workshops, but it should not look like real telemetry. Naming it
demoTickerprevents confusion with production ingestion.
Step 19 — Add tests for HTML-demo-derived helpers
Kiro prompt
Generate tests for the helper functions extracted from the HTML demo: filterTools, sortTools, svgPath, applyDemoTick, action label mapping, and risk priority. Include edge cases for empty query, unknown query, flat series, single-point series, and immutable updates.
Code sample — tests/demoHelpers.test.ts
import { describe, expect, it } from "vitest";
import { filterTools, sortTools } from "../src/domain/filters";
import { applyDemoTick } from "../src/domain/demoTicker";
import { svgPath } from "../src/ui/chart";
import { cdSemTools } from "../src/data/tools";
describe("demo-derived helpers", () => {
it("filters by layer or symptom", () => {
expect(filterTools(cdSemTools, "deflector")).toHaveLength(1);
expect(filterTools(cdSemTools, "gate").length).toBeGreaterThan(0);
});
it("returns all tools for empty search", () => {
expect(filterTools(cdSemTools, "")).toHaveLength(cdSemTools.length);
});
it("sorts without mutating the original array", () => {
const before = cdSemTools.map((tool) => tool.id).join(",");
const sorted = sortTools(cdSemTools, "risk");
const after = cdSemTools.map((tool) => tool.id).join(",");
expect(sorted[0].risk).toBeGreaterThanOrEqual(sorted.at(-1)?.risk ?? 0);
expect(after).toBe(before);
});
it("creates an SVG path for a flat series", () => {
expect(svgPath([1, 1, 1], 120, 32, 2)).toContain("M");
});
it("applies deterministic demo ticks immutably", () => {
const updated = applyDemoTick(cdSemTools, [1, -1]);
expect(updated).not.toBe(cdSemTools);
expect(updated[0]).not.toBe(cdSemTools[0]);
expect(updated[0].blindWindowHours).toBeGreaterThan(cdSemTools[0].blindWindowHours);
});
});
Explanation
Business logic: The tests verify that demo-derived behavior is preserved while becoming safer and more maintainable.
Code logic: The suite checks filtering, sorting, SVG path creation, and deterministic state updates. It also verifies immutability.
Expected result: When Kiro refactors rendering or state logic, these tests catch accidental behavior regressions.
System design decision
- Helper tests protect extracted behavior. Migration from HTML to TypeScript can subtly break sorting, filtering, or charting. These tests preserve the original demo’s core interactions.
- Immutability tests prevent hidden coupling. Mutation was acceptable in a compact demo but creates defects in larger apps. Testing immutability forces cleaner state management.
- Chart tests focus on validity, not pixels. SVG path tests should ensure output is well-formed for edge cases. Pixel-perfect rendering belongs to visual review, not unit tests.
Step 20 — Ask Kiro to generate a demo parity checklist
Kiro prompt
Create a demo parity checklist comparing the TypeScript implementation against the original HTML demo. Include sections for layout, navigation, data, risk board, detail panel, FDC cards, runbook, charts, responsive behavior, and safety language.
Expected checklist
# Demo Parity Checklist
## Layout
- [ ] Header contains AWS logo block and live status.
- [ ] Hero contains SPC Frequency / Mass Production Sync message.
- [ ] Summary cards show tools watched, blind window, Fleet OOC risk, FDC links, dynamic limits, yield watch lots.
## Navigation
- [ ] Tabs switch active panel without page reload.
- [ ] Active tab state is visually clear.
- [ ] Inactive panels are hidden from assistive technology.
## Risk Board
- [ ] Search matches tool id, layer, and symptom.
- [ ] Sort works for risk, blind window, TMG, slope, Fleet σ, and residual.
- [ ] Detail panels show evidence and trajectory.
## Safety
- [ ] No button issues real equipment commands.
- [ ] Footer states advisory-only behavior.
- [ ] APC Guard is represented as review guidance, not an automated APC write.
System design decision
- Parity checklists prevent accidental scope loss. When Kiro modularizes code, it may omit small but important prototype behaviors. A checklist gives developers a concise validation tool.
- Safety has its own checklist section. The portal's domain requires explicit advisory-only language. Treating safety as a separate review area makes it harder to miss.
- Accessibility improves on the original demo. Parity does not mean copying every limitation. The TypeScript implementation should preserve behavior while improving ARIA state and keyboard usability.