← Financial Cloud Cloud Cloud Club · Builder Articles

Vertex Macro | Financial Cloud Cloud · Builder Articles

Kiro: Build the Fab SPC Drift Synchronization Portal from Scratch

Series: Kiro workshop

Article: 22

Article
Kiro workshop
01 Build with Kiro: Prompt-First Product Design for a Tagalog Learning App
Kiro workshop
02 Build with Kiro: Educational-First Dev Tips for a Tagalog Learning App
Kiro workshop
03 Build with Kiro: Deep-Dive Development Flow for a Tagalog Learning App
Kiro workshop
04 Build with Kiro: Localize a Tagalog Learning App into Chinese Variants Workshop
Kiro workshop
05 Build with Kiro: Grammar and Pronunciation Enrichment Pipeline for Tagalog Cards Workshop
Kiro workshop
06 Build with Kiro: Unique and Reviewable Extra Examples in a Tagalog Learning App Workshop
Kiro workshop
07 Build with Kiro: Factory Engineering Health Hooks Workshop
Kiro workshop
08 Build with Kiro: Etch Process Window Risk Test Automation Workshop
Kiro workshop
09 Build with Kiro: Photolithography Drift Risk Development Workshop
Kiro workshop
10 Engineering Team Get Started — Daily Fab-Duty Use of fab spc drift sync portal
Kiro workshop
11 Engineering Team Addendum — Daily Fab-Duty Use of fab spc drift sync portal
Kiro workshop
12 Kiro: Field Engineering Workshop for Spec-Driven Factory Software
Kiro workshop
13 Kiro: Hands-On Lab — Build a Typed Factory Risk Portal from Scratch
Kiro workshop
14 Kiro: Prompt, Code, and Type Standards Playbook for Engineering Developers
Kiro workshop
15 Kiro: Why a Strong React Prompt Prevents Type Declaration False-Starts
Kiro workshop
17 Build with Kiro: Create a Factory Automation Portal React UI
Kiro workshop
18 Build with Kiro: Create the Automation Analytics Engine Behind a Factory Automation Portal
Kiro workshop
19 Build with Kiro: Add an AI Factory Automation Assistant to a Factory Automation Portal
Kiro workshop
21 Kiro: 2-Hour Professional Developer Workshop Guide
Kiro workshop
22 Kiro: Build the Fab SPC Drift Synchronization Portal from Scratch
Kiro workshop
23 Kiro: Prompt Library and Deep Code Explanation Appendix
Kiro workshop
30 Build with Kiro: Create a Factory Automation Portal UI
Kiro workshop
31 Build with Kiro: Create the Automation Analytics Engine Behind a Factory Automation Portal
Kiro workshop
32 Build with Kiro: Add an AI Factory Automation Assistant to a Factory Automation Portal
Kiro workshop
33 Build with Kiro: Rebuild the CME Direct-Style Quant P&L Leaderboard UI
Kiro workshop
34 Build with Kiro: Recreate the Quant Analytics Engine Behind the P&L Board
Kiro workshop
35 Build with Kiro: AWS AI-Powered Trading Desk Assistant for the Quant Board
Kiro workshop
36 One-Page Trading Portal SOP
Kiro workshop
AgentCore
A1 Build with AgentCore & Strands: Gateway MCP Tool Fabric Developer Workshop
AgentCore
A2 Build with AgentCore & Strands: Governed Multi-Agent Risk System Developer Workshop
AgentCore
A3 Build with AgentCore & Strands: Runtime Sovereign Risk Agent Developer Workshop
AgentCore
Exam practice
E1 Build a Multilingual AWS Exam Practice Launch System with Vibe Coding
Exam practice
E2 Build an AWS Exam Practice Room with Vibe Coding Dev Tips
Exam practice
E3 Build the Practice Engine Behind a Static AWS Exam Room
Exam practice
Amazon Q
Q1 Amazon Q: CloudShell-First Developer Workshop for ACM Certificate Auto Renewal
Amazon Q
Tagalog Practice Room
T1 Build a Tagalog Learning App for AWS Manila Community Day with Prompt-First Product Design
Tagalog Practice Room
T2 Build Tagalog Learning App for AWS Manila Community Day with Educational-First Dev Tips
Tagalog Practice Room
T3 Deep Dive Development Flow for a Tagalog Learning App for AWS Manila Community Day
Tagalog Practice Room
T4 Build Localize a Tagalog Learning App into Chinese Variants for AWS Manila Community Day
Tagalog Practice Room
T5 Build a Grammar and Pronunciation Enrichment Pipeline for Tagalog Cards for AWS Manila Community Day
Tagalog Practice Room
T6 Make Extra Examples Unique and Reviewable in a Tagalog Learning App for AWS Manila Community Day
Tagalog Practice Room
Roadmap
R1 Enterprise Data Analytics Roadmap: 100 Deep Scenario Questions
Roadmap
R2 Front-End Development Roadmap: Real-World Enterprise Scenarios
Roadmap
Hong Kong Community Day
C1 A Hong Kong Weekend with AWS Community Day: From Cloud Sessions to Harbour Lights
Hong Kong Community Day
C2 The Speaker’s Luxury Weekend: Present an AWS Story, Then Let Hong Kong Take the Stage
Hong Kong Community Day
C3 Seventy-Two Hours in Hong Kong: The Grand Tour for an AWS Community Day Speaker
Hong Kong Community Day
Manila Community Day
C4 AWS Community Day Manila: A Joyful Weekend of Cloud, Culture, and True Friendship
Manila Community Day
C5 AWS Community Day Manila: Where Cloud Builders Find the Happiest Spirit of the Philippines
Manila Community Day
C6 AWS Community Day Manila: Build, Break, Repeat, and Belong in a City of Joy
Manila Community Day
C7 First-Time Visitor Tips for Manila, Philippines
Manila Community Day
Philippines × Hong Kong
C8 Philippines Hong Kong Capital Market Upgrade
Philippines × Hong Kong
Backtest
B1 Build Institutional Amazon Long-Only Backtesting Agents With Bedrock AgentCore And Strands Agents
Long-only AMZN agents with AgentCore, Strands, and a governed Backtrader ledger.
B2 Build Regime-Aware Amazon Position Management With Backtrader, AgentCore, And Strands Agents
Treat market regime as a position control, not a chart comment.
B3 Build Benchmark-Relative Amazon Timing Systems Using Nasdaq, S&P 500, Dow, AgentCore, And Strands
Time AMZN against Nasdaq, S&P 500, and Dow context.
B4 Build A Governed Amazon Trade-History Factory With Bedrock AgentCore, Strands Agents, And Backtrader
Turn backtests into an auditable trade-history factory.
B5 Build An Agentic Amazon Backtest Operating Model With Bedrock AgentCore And Strands Agents [Part 1]
Build the operating model before debating the result.
B6 Build A Custom Cerebro Code Talk For Amazon Timing And Position Management [Part 2]
Explain the Cerebro engine before explaining the chart.
B7 Build Trader Review Records For Amazon Strategy Results And Lessons Learned [Part 3]
Turn strategy ranks into trader review records.
B8 Build A Governed FSI Amazon Position Management Playbook With AgentCore And Strands [Part 4]
An FSI playbook for governed Amazon position management.
B9 Build a Sovereign Risk Trading Agent with Amazon Bedrock AgentCore for Yield Spreads, FX Hedging, and Debt Repricing
Sovereign-risk agent for yield spreads, FX hedges, and debt repricing.
B11 Build Modern Volatility Trading & Lawful Thailand Recovery Planning Agents: A Memory-Driven Strands Multi-Agent Risk Protection System
Memory-driven Strands agents for volatility and Thailand recovery.
B12 Build Short Straddle Trading-Risk Governance with Amazon Bedrock AgentCore Memory
Short-straddle risk governance with AgentCore Memory.
B13 Building Production-Ready Credit & Yield Staking AI Agents on Amazon EKS
Production credit and yield-staking agents on Amazon EKS.
Challenge
01 Weekend Productivity Challenge: Fab SPC Drift Synchronization Portal
Fab SPC drift review and recommendation portal.
02 Weekend Productivity Challenge: Quant P&L Commander — An AI-Powered Trading Productivity Portal on AWS
Quant P&L leaderboard and trading productivity portal.
03 Weekend Annoying Task Challenge: Trading Desk Execute Summary On Cloud, On Chain, On Air
DeskPulse daily execution communication.
04 Weekend Agent Challenge: The 6 AM Trading Risk Review
An unattended, evidence-backed morning credit and trading risk brief.
05 Weekend Creative Challenge: Leadership Card Game
A browser-based creative facilitation deck.
06 Full Stack Challenge: Community Day Board App
A browser-based event communication room.
Leadership Card Game
01 Leadership Card Game: Last Skill Cloud Did Not Automate
A field essay for Builders on language, courage, and the Leadership Card Game
02 Anatomy of a Leadership Round: How the Leadership Card Game Actually Plays
A facilitator’s field guide for Builders who want drills that fit inside real meetings
03 Leadership Card Game: When the Opportunity Stops Belonging to the Organizer
A field essay for Builders on power transfer, multilingual practice nights, and career arcs that complete Entrance, Resource, and Narrative
04 Weekend Creative Challenge: Leadership Card Game
Master high-stakes workplace conversations before they happen.
05 From a Weekend Challenge Project to $1,386 Crowdfunding: The Leadership Practice That Changes How You Show Up at Work
A weekend build became a live 600-card leadership practice room and reached $1,386 in crowdfunding.
06 From a Weekend Challenge Project to $1,386 Crowdfunding: A Day 1 Path Into the Tech Industry
How did a weekend challenge become a multilingual AWS-powered product with 600 cards and $1,386 in crowdfunding?
07 From a Weekend Challenge Project to $1,386 Crowdfunding: Build a Professional Brand by Transferring Opportunity
A weekend challenge reached $1,386 in crowdfunding by turning leadership ideas into a working multilingual product.
08 Leadership Card Game — Crowdfunding Campaign
Speak leadership before the room decides your career.
09 PR/FAQ 01 — Leadership Card Game launches for community builders
Working Backwards document · External press release + FAQ Product: Leadership Card Game Audience: Community managers, volunteer organizers, early-career…
10 PR/FAQ 02 — Enterprise facilitators adopt Leadership Card Game for live leadership drills
Working Backwards document · External press release + FAQ Product: Leadership Card Game Audience: Learning & development leads, people managers, agile…
10 PR/FAQ 03 — Multilingual Leadership Card Game opens global practice rooms for builder ownership
Working Backwards document · External press release + FAQ Product: Leadership Card Game Audience: Global AWS builders, bilingual communities, cross-border…
AWS Builder Center
01 AWS Builder Center, its community spirit, and AWS Builder Jacket
There are destinations you reach by plane, destinations you enter through a door, and destinations that begin with a sign-in screen and quickly feel like a…
02 Inside AWS Builder Center, where a global technical platform becomes a place to learn, contribute, and belong
A great journey does not always begin at an airport.
03 AWS Community Builder huge success
When builders share openly, the entire community moves forward.
04 AWS Builder Center huge success
A vibrant global district built for curiosity, public learning, and the AWS Builder Jacket.
05 A weekend inside AWS Builder Center, from community inspiration to unmistakable AWS Builder Jacket
Friday evening begins with a familiar builder feeling: there is an idea waiting somewhere between a problem and a possibility.

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 test has 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 .tab buttons and .portal panels.
  • tools array containing CD-SEM records.
  • fdc array containing FDC health-link records.
  • runbook array 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(), and fleetSvg() 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 REVIEW and GOLDEN 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.ts allows 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("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;")
    .replaceAll("'", "&#039;");
}

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 demoTicker prevents 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.