← Financial Cloud Cloud Cloud Club · Builder Articles

Vertex Macro | Financial Cloud Cloud · Builder Articles

Kiro: Hands-On Lab — Build a Typed Factory Risk Portal from Scratch

Series: Kiro workshop

Article: 13

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.

Lab overview

In this lab, developers use Kiro to build a TypeScript React single-page application from an empty folder. The app visualizes factory machinery risk using deterministic local fixtures, typed domain logic, live simulated updates, search, sort, and SVG charts. Kiro is used as the single AI engineering service throughout the lab: steering, specs, chat, hooks, code generation, refactoring, and documentation.

Prerequisites

● Kiro installed and opened to an empty workspace.

● Node.js 20+ recommended.

● npm available.

● Familiarity with React and TypeScript.

Step 1 — Create the Vite TypeScript project

Run this in the terminal:

npm create vite@latest factory-risk-portal -- --template react-ts
cd factory-risk-portal
npm install
npm run dev

Expected result: a default React + TypeScript application runs locally in the browser.

Kiro prompt sample

Inspect this new React TypeScript workspace. Summarize the current structure and propose the minimal files we should add for a typed factory machinery risk portal. Do not write implementation code yet. Focus on architecture and type boundaries.

System design rationale

● Start with a known build system. Vite with React TypeScript gives the workshop a small, predictable baseline. This lets developers spend the session on Kiro workflows and engineering design instead of bundler configuration. A stable project scaffold also makes hooks easier because npm run build, tsc, and local dev commands have predictable names.

● Separate inspection from implementation. The first Kiro interaction asks for a structure proposal, not code. This is important because agentic coding quality improves when the assistant first reads the workspace and forms a plan. For factory engineering software, rushing from blank folder to UI tends to bury domain assumptions in JSX, which is exactly what causes typed React false-starts.

● Create a repeatable classroom baseline. Everyone begins with the same project template. That makes troubleshooting easier and ensures the Kiro behavior being taught relates to specs, steering, and implementation tasks rather than differences in local frameworks. It also mirrors field work: establish a stable control baseline before adjusting process parameters.

Step 2 — Add Kiro steering files

Create .kiro/steering/product.md:

# Product overview

Build a local-only factory machinery risk portal for professional engineers. The portal helps review machinery drift, sensor-health symptoms, and risk recommendations. The application is advisory-only and must not imply autonomous equipment control, machine state changes, or bypassing human approval.

Create .kiro/steering/tech.md:

# Technology stack

- React with TypeScript.
- Strict TypeScript; no `any`.
- Vite build system.
- CSS modules are not required; use plain CSS with clear class names.
- Inline SVG is allowed for sparklines and bar charts.
- No charting dependencies unless explicitly approved.
- No backend calls in the lab.
- Use local deterministic fixture data.

Create .kiro/steering/structure.md:

# Project structure

- `src/types.ts` contains shared domain types.
- `src/data/fixtures.ts` contains deterministic local data.
- `src/domain/risk.ts` contains pure risk and formatting functions.
- `src/components/` contains presentational React components.
- `src/App.tsx` owns top-level state and tab switching.
- Event handlers and props must be typed.
- Action labels, tab names, and sort keys must be union types.

Kiro prompt sample

Read the steering files and confirm the constraints you will follow. Then identify the first three implementation tasks for the factory risk portal. Do not create files yet.

System design rationale

● Steering converts tribal standards into persistent context. Field teams often have standards that live in senior engineers' heads: never imply autonomous control, do not use untyped state, keep risk logic reviewable, and avoid network assumptions in demos. Steering files make those standards available to Kiro on every interaction, reducing the need to repeat instructions.

● Type policy is placed before code generation. TypeScript failures are cheaper to prevent than to fix after a component tree exists. By requiring union types for actions, tabs, and sort keys upfront, Kiro is less likely to create permissive string props or unsafe indexing logic. This is the main control against React projects drifting into untyped code.

● Structure files create maintainability pressure. If Kiro knows that domain logic belongs in risk.ts, fixtures in fixtures.ts, and UI in components, it is less likely to produce a monolithic App.tsx. That matters for professional developers because maintainability is a system property, not a formatting preference.

Step 3 — Generate a Kiro spec

Ask Kiro to create a spec with requirements, design, and tasks.

Kiro prompt sample

Create a Kiro spec for a TypeScript React factory machinery risk portal.

The app must include:
- typed machinery fixtures
- top KPI strip
- tabs for Overview, Live Risk Board, Health Matrix, Dynamic Matching, Triage, and Runbook
- search by machine, layer, or symptom
- sorting by risk, blind-window age, matching gap, slope, fleet sigma, or residual
- expandable evidence panels
- SVG sparklines and a larger SVG trajectory chart
- simulated 5-second live updates
- advisory-only wording

Use strict TypeScript and include acceptance criteria that can be tested.

Expected result: Kiro creates or proposes a spec with a requirements section, design section, and task list.

System design rationale

● The spec is the engineering contract. A professional developer should not let an AI assistant infer control behavior from UI labels alone. The spec states which behavior is available, what is local-only, what must remain advisory, and which acceptance criteria prove correctness. This reduces ambiguity and provides a review artifact.

● Acceptance criteria control scope. Without acceptance criteria, Kiro may overbuild or underbuild. For example, “make a live dashboard” could become a backend architecture, a WebSocket client, or a static table. The acceptance criteria narrow the system to deterministic fixtures, explicit search, explicit sort keys, accessible controls, and simulated updates.

● The spec preserves traceability. Factory machinery applications often need review by software, equipment, process, and safety owners. A spec-driven workflow creates artifacts that show why a feature exists and what it is allowed to do. That is more reliable than a chat transcript containing scattered decisions.

Step 4 — Create the domain types

Create src/types.ts:

export type Recommendation =
    | 'RELEASE'
    | 'WATCH'
    | 'RUN SPC'
    | 'GOLDEN WAFER'
    | 'ROUTE LIMIT'
    | 'HOLD REVIEW'
    | 'APC GUARD';

export type RiskTone = 'pass' | 'warn' | 'danger';

export type SortKey = 'risk' | 'blindHours' | 'matchingGapNm' | 'slope' | 'fleetSigma' | 'residual3SigmaNm';

export type TabKey =
    | 'OVERVIEW'
    | 'LIVE RISK BOARD'
    | 'HEALTH MATRIX'
    | 'DYNAMIC MATCHING'
    | 'TRIAGE'
    | 'RUNBOOK';

export type MachineRecord = {
    id: string;
    layer: string;
    symptom: string;
    risk: number;
    blindHours: number;
    matchingGapNm: number;
    slope: number;
    fleetSigma: number;
    residual3SigmaNm: number;
    recommendation: Recommendation;
    deltaMeanSeries: number[];
};

export type HealthLink = {
    name: string;
    sensor: string;
    metrologyImpact: string;
    yieldRisk: string;
    advisoryRule: string;
};

Business logic explanation

The domain types define the vocabulary of the risk portal. MachineRecord represents one machinery or metrology tool state. Recommendation is a closed set of review actions so the UI cannot accidentally present an unsupported command. SortKey protects sorting from arbitrary strings. RiskTone lets the UI show color plus text status. HealthLink maps sensor symptoms to engineering review logic.

Code logic explanation

Each type is exported from a central file so components and domain functions share the same contract. Union types are deliberately used for recommendations, tabs, and sorting. That allows TypeScript to detect invalid labels, unsafe sort keys, and misspelled tabs at build time.

Expected result

Kiro and the TypeScript compiler should now understand the core domain vocabulary. Later code can import these types instead of inventing implicit object shapes.

System design rationale

● Closed sets prevent unsafe language drift. In a factory context, a label can be interpreted as an operation. A free-form action string might accidentally become “STOP TOOL” or “AUTO ROUTE.” A union type restricts the application to advisory recommendations that are known, reviewed, and intentionally worded.

● Typed sort keys remove runtime guesswork. Sorting user interfaces often start with generic strings and dynamic object indexing. That is fragile. A SortKey union creates a contract between controls, data fields, and sort logic. If a field changes, TypeScript flags every affected area before the app reaches users.

● Domain-first coding guides Kiro. Kiro can generate better components once it sees the data contracts. This avoids the common false-start where an AI assistant generates attractive JSX with implicit shapes, then struggles to retrofit type declarations around already-written state and props.

Step 5 — Add deterministic fixtures

Create src/data/fixtures.ts:

import type { HealthLink, MachineRecord } from '../types';

export const initialMachines: MachineRecord[] = [
    {
        id: 'CDSEM-01',
        layer: 'Gate ADI',
        symptom: 'Stable master, production anchor',
        risk: 18,
        blindHours: 1.2,
        matchingGapNm: 0.12,
        slope: 1.0,
        fleetSigma: 0.4,
        residual3SigmaNm: 0.08,
        recommendation: 'RELEASE',
        deltaMeanSeries: [0.02, 0.01, 0.03, 0.01, 0.02, 0, 0.01, 0.02, 0.01, 0.02]
    },
    {
        id: 'CDSEM-03',
        layer: 'Fin dense CD',
        symptom: 'Deflector DAC wobble, slope mismatch',
        risk: 91,
        blindHours: 7.4,
        matchingGapNm: 0.23,
        slope: 1.031,
        fleetSigma: 3.2,
        residual3SigmaNm: 0.16,
        recommendation: 'HOLD REVIEW',
        deltaMeanSeries: [0.02, 0.03, 0.06, 0.05, 0.08, 0.12, 0.16, 0.2, 0.22, 0.24]
    }
];

export const healthLinks: HealthLink[] = [
    {
        name: 'Deflector DAC',
        sensor: 'Deflector linearity / DAC stability',
        metrologyImpact: 'Mandel Slope moves away from 1',
        yieldRisk: 'Dense/isolated feature bias and process window narrowing',
        advisoryRule: 'If slope <0.98 or >1.02, request engineering review before critical-layer routing.'
    }
];

Business logic explanation

Fixtures represent a realistic but safe learning dataset. One record is stable and one is high risk. This lets developers immediately validate pass/warn/danger behavior, sorting, chart geometry, and recommendation display without connecting to factory systems.

Code logic explanation

The arrays are explicitly typed as MachineRecord[] and HealthLink[]. TypeScript now validates every field name, numeric metric, and recommendation label. If a developer writes HOLD instead of HOLD REVIEW, the compiler catches it.

Expected result

The app has deterministic data that can be rendered, sorted, searched, simulated, and tested.

System design rationale

● Fixtures are safer than live machinery data. The workshop is about software engineering with Kiro, not operational integration. Deterministic local data avoids credentials, network failures, sensitive production values, and accidental control implications. It also allows every participant to see the same expected result.

● Representative extremes support validation. Including both a low-risk stable machine and a high-risk drift case helps validate color thresholds, recommendation wording, expanded detail panels, and chart scaling. A dataset with only normal values would hide risk-path defects until later.

● Typed fixtures act as contract tests. The fixture file is not just mock data; it is a compile-time check that the domain model is usable. If the model is overcomplicated or missing fields, Kiro and TypeScript surface the mismatch immediately when the fixtures are written.

Step 6 — Add pure risk logic

Create src/domain/risk.ts:

import type { MachineRecord, RiskTone, SortKey } from '../types';

export function getRiskTone(risk: number): RiskTone {
    if (risk >= 80) return 'danger';
    if (risk >= 55) return 'warn';
    return 'pass';
}

export function getStatusLabel(machine: MachineRecord): string {
    if (machine.risk >= 80) return 'HOLD CANDIDATE';
    if (machine.risk >= 55) return 'WATCH / REVIEW';
    return 'NORMAL RELEASE';
}

export function filterMachines(machines: MachineRecord[], query: string): MachineRecord[] {
    const q = query.trim().toLowerCase();
    if (!q) return machines;
    return machines.filter(machine =>
        `${machine.id} ${machine.layer} ${machine.symptom}`.toLowerCase().includes(q)
    );
}

export function sortMachines(machines: MachineRecord[], sortKey: SortKey): MachineRecord[] {
    return [...machines].sort((a, b) => Math.abs(b[sortKey]) - Math.abs(a[sortKey]));
}

Business logic explanation

Risk thresholds are centralized. Green/pass is below 55, yellow/watch is 55-79, and red/danger is 80+. Search supports machine ID, process layer, or symptom. Sorting ranks the most severe values first for whichever metric the engineer selects.

Code logic explanation

The functions are pure: the same inputs always produce the same outputs. sortMachines copies the array before sorting to avoid mutating React state. SortKey ensures that dynamic indexing only uses valid numeric fields.

Expected result

Kiro can now build UI components on top of testable business logic. Developers can add unit tests later without rendering the whole application.

System design rationale

● Pure functions make AI-generated code reviewable. When business logic is mixed directly inside JSX, reviewers must mentally separate rendering, state, thresholds, and formatting. Pure functions isolate the decisions that matter: risk thresholds, filtering, sorting, and status language.

● Immutability protects React state. Sorting arrays in place is a common bug in dashboards. Copying before sorting avoids subtle UI issues and makes live update behavior easier to reason about. Kiro should be guided to preserve immutability whenever it transforms state.

● Centralized thresholds support governance. Risk thresholds are engineering policy. Keeping them in one domain file makes future review and change control easier. If a process owner changes the warning threshold, the implementation has one obvious location and fewer hidden UI dependencies.

Step 7 — Build the SVG sparkline component

Create src/components/Sparkline.tsx:

type SparklineProps = {
    data: number[];
    width?: number;
    height?: number;
    color?: string;
};

export function Sparkline({ data, width = 130, height = 38, color = '#35d5ff' }: SparklineProps) {
    const min = Math.min(...data, 0);
    const max = Math.max(...data, 0.24);
    const points = data.map((value, index) => {
        const x = (index / Math.max(1, data.length - 1)) * width;
        const y = height - ((value - min) / (max - min || 1)) * (height - 6) - 3;
        return `${x.toFixed(1)},${y.toFixed(1)}`;
    }).join(' ');

    return (
        <svg className="spark" viewBox={`0 0 ${width} ${height}`} role="img" aria-label="DeltaMean sparkline">
            <polyline points={points} fill="none" stroke={color} strokeWidth="2" />
            <line x1="0" y1={height / 2} x2={width} y2={height / 2} stroke="#49657c" strokeDasharray="4 4" />
        </svg>
    );
}

Business logic explanation

The sparkline gives fast visual context for recent drift without adding a third-party chart package. Engineers can spot steady, rising, or noisy behavior directly in the risk table.

Code logic explanation

The component maps numeric values to SVG coordinates. It normalizes values between a minimum and maximum, builds a polyline string, and draws a center reference line. Props are explicitly typed and optional dimensions have defaults.

Expected result

Each machine row can display a compact trend line with accessible image metadata.

System design rationale

● Inline SVG keeps the dependency surface small. For the workshop, a charting library would add API learning overhead and version risk. SVG is native to the browser, typed well enough for React, and sufficient for sparklines, trajectory charts, and sigma bars.

● A chart component isolates coordinate math. Coordinate transformation is not UI decoration; it is logic. Putting it in a component with typed props keeps the table simpler and makes the rendering decision reusable across compact and expanded views.

● Accessibility remains explicit. The sparkline uses role="img" and an aria-label. This prevents visual-only status communication. In engineering tools, users should be able to interpret the status through text and labels even if color or visual detail is unavailable.

Step 8 — Ask Kiro to implement the app shell

Kiro prompt sample

Implement the app shell using the existing types, fixtures, risk functions, and Sparkline component.

Requirements:
- top header with title and live local clock
- KPI strip
- tab navigation controlled by typed TabKey state
- Live Risk Board with search, sort buttons, and expandable details
- simulated live updates every 5 seconds
- advisory-only footer

Keep App.tsx readable. If it becomes too large, propose component extraction before writing more code.

Expected result: Kiro creates a working top-level app with typed state and renders the core board.

System design rationale

● Kiro receives constraints and assets together. At this point, the domain model, fixtures, pure functions, and sparkline component exist. The prompt tells Kiro to compose existing pieces, not invent new ones. That dramatically improves type consistency and reduces duplicate logic.

● State ownership is intentional. The top-level app owns active tab, query, sort key, expanded row, live clock, and machine records. This is appropriate for a small single-page lab. If the app grows, Kiro should extract state or components rather than bury everything in deeply nested children.

● Simulated updates model real telemetry safely. Field software often needs to demonstrate time-varying behavior. A deterministic local update loop is safer than connecting to equipment. It validates re-rendering, chart updates, and risk changes while preserving the advisory-only boundary.

Step 9 — Add hooks for type discipline

Create .kiro/hooks/typecheck-on-save.json:

{
  "version": "v1",
  "hooks": [
    {
      "name": "typecheck-on-save",
      "description": "Run TypeScript build checks when TS or TSX files are saved.",
      "trigger": "PostFileSave",
      "matcher": "\\.(ts|tsx)$",
      "action": {
        "type": "command",
        "command": "npm run build"
      },
      "timeout": 60,
      "enabled": true
    }
  ]
}

Create .kiro/hooks/review-react-change.json:

{
  "version": "v1",
  "hooks": [
    {
      "name": "review-react-change",
      "description": "Ask Kiro to review React changes for typing, state mutation, accessibility, and advisory-only wording.",
      "trigger": "PostFileSave",
      "matcher": "src/.*\\.(ts|tsx)$",
      "action": {
        "type": "agent",
        "prompt": "Review the changed TypeScript React file. Check for any, implicit object shapes, unsafe state mutation, untyped props, inaccessible controls, color-only status, and wording that implies autonomous machinery control. Suggest minimal fixes."
      },
      "timeout": 60,
      "enabled": true
    }
  ]
}

Business logic explanation

The hooks act like an engineering quality gate. They do not control machinery and they do not approve production behavior. They help developers catch unsafe implementation patterns early: type drift, mutation, UI-only status, and language that could be misread as an operational command.

Code logic explanation

The first hook runs a shell command after TypeScript or TSX files are saved. The second hook invokes Kiro’s agent review after TypeScript React source changes. The matcher limits automation to relevant files.

Expected result

Saving a TypeScript file triggers checks or review automation, depending on the Kiro hook configuration in the local environment.

System design rationale

● Hooks convert standards into workflow. Steering tells Kiro what standards exist; hooks make those standards active during development. This is critical when developers iterate quickly and may not manually ask for reviews every time.

● Type checking is automated at the point of change. The moment a TS or TSX file is saved, the build can catch invalid labels, missing props, and broken imports. That reduces the chance of discovering type errors only at the end of the lab.

● Agent review catches semantic issues. TypeScript cannot detect whether a button label implies autonomous equipment control. An agent hook can review language, accessibility, and engineering intent. This complements compiler checks instead of replacing them.

Step 10 — Final hardening prompt

Kiro prompt sample

Perform a final technical review of the project.

Check:
- strict typing and no `any`
- no mutation of React state arrays
- all controls keyboard accessible
- search and sorting use typed contracts
- risk status includes text, not only color
- app wording remains advisory-only
- README explains how to run the app and states that fixture data must be replaced with validated data before operational use

Return a prioritized fix list, then implement only the safe low-risk fixes.

System design rationale

● Final review separates detection from action. Asking Kiro for a prioritized list before implementation avoids unreviewed sweeping changes. This is important in engineering environments where a broad refactor can introduce regressions late in a workshop or sprint.

● Safety language is reviewed as part of technical quality. In factory applications, wording is part of the system. “Hold candidate” and “recommend route limit” are different from “hold tool” or “reroute lot.” The review makes sure the software remains within advisory scope.

● Low-risk fixes preserve workshop completion. The last minutes should stabilize the project, not open architectural churn. Kiro is useful for identifying many improvements, but professional developers must decide what to implement now versus backlog.

Completion checklist

● [ ] Project runs locally.

● [ ] Kiro steering files exist.

● [ ] Kiro spec exists or has been reviewed.

● [ ] Domain types exist before UI logic.

● [ ] No any in source files.

● [ ] Search works for machine, layer, and symptom.

● [ ] Sorting works for all configured keys.

● [ ] Expand/collapse evidence panel works.

● [ ] Live update loop changes risk and trend values.

● [ ] SVG charts render without chart libraries.

● [ ] Advisory-only disclaimer is visible.

● [ ] Hooks exist for type checks and AI review.


lab section: create complete fixture coverage

The short fixture in the main lab is intentionally small for teaching. For a richer demo, replace it with a broader set of records that covers low, medium, and high risk. This gives Kiro and participants more cases for sorting, filtering, display color, and expanded evidence.

import type { HealthLink, MachineRecord } from '../types';

export const initialMachines: MachineRecord[] = [
  {
    id: 'CDSEM-01',
    layer: 'Gate ADI',
    symptom: 'Stable master, production anchor',
    risk: 18,
    blindHours: 1.2,
    matchingGapNm: 0.12,
    slope: 1.0,
    fleetSigma: 0.4,
    residual3SigmaNm: 0.08,
    recommendation: 'RELEASE',
    deltaMeanSeries: [0.02, 0.01, 0.03, 0.01, 0.02, 0.0, 0.01, 0.02, 0.01, 0.02]
  },
  {
    id: 'CDSEM-02',
    layer: 'Gate AEI',
    symptom: 'Emission current ramp, small mean offset',
    risk: 64,
    blindHours: 5.8,
    matchingGapNm: 0.18,
    slope: 1.012,
    fleetSigma: 1.8,
    residual3SigmaNm: 0.14,
    recommendation: 'WATCH',
    deltaMeanSeries: [0.02, 0.04, 0.05, 0.08, 0.10, 0.12, 0.14, 0.16, 0.17, 0.18]
  },
  {
    id: 'CDSEM-03',
    layer: 'Fin dense CD',
    symptom: 'Deflector DAC wobble, slope mismatch',
    risk: 91,
    blindHours: 7.4,
    matchingGapNm: 0.23,
    slope: 1.031,
    fleetSigma: 3.2,
    residual3SigmaNm: 0.16,
    recommendation: 'HOLD REVIEW',
    deltaMeanSeries: [0.02, 0.03, 0.06, 0.05, 0.08, 0.12, 0.16, 0.20, 0.22, 0.24]
  },
  {
    id: 'CDSEM-05',
    layer: 'Contact/Via',
    symptom: 'Vacuum slow degradation, residual rising',
    risk: 76,
    blindHours: 6.9,
    matchingGapNm: 0.19,
    slope: 1.006,
    fleetSigma: 2.4,
    residual3SigmaNm: 0.21,
    recommendation: 'GOLDEN WAFER',
    deltaMeanSeries: [0.02, 0.01, 0.04, 0.06, 0.08, 0.10, 0.12, 0.13, 0.16, 0.18]
  },
  {
    id: 'CDSEM-10',
    layer: 'Risk ramp product',
    symptom: 'Yield-loss watch lots routed here',
    risk: 82,
    blindHours: 6.2,
    matchingGapNm: 0.21,
    slope: 0.976,
    fleetSigma: 2.9,
    residual3SigmaNm: 0.17,
    recommendation: 'APC GUARD',
    deltaMeanSeries: [0.02, 0.04, 0.07, 0.09, 0.11, 0.13, 0.15, 0.18, 0.20, 0.23]
  }
];

export const healthLinks: HealthLink[] = [
  {
    name: 'Gun Vacuum',
    sensor: 'Gun chamber vacuum / Torr',
    metrologyImpact: 'Residual 3 sigma and TMP rise',
    yieldRisk: 'False alarms and random CD noise',
    advisoryRule: 'If vacuum degrades while residual 3 sigma rises, recommend golden-wafer review.'
  },
  {
    name: 'Emission Current',
    sensor: 'Extraction voltage / emission current',
    metrologyImpact: 'Mean offset step jump',
    yieldRisk: 'APC receives biased CD and may correct in the wrong direction',
    advisoryRule: 'If emission slope changes for three lots, recommend virtual OOC review before next routine SPC.'
  },
  {
    name: 'Deflector DAC',
    sensor: 'Deflector linearity / DAC stability',
    metrologyImpact: 'Mandel Slope moves away from 1',
    yieldRisk: 'Dense/isolated feature bias and process-window narrowing',
    advisoryRule: 'If slope <0.98 or >1.02, request engineering review before critical-layer routing.'
  },
  {
    name: 'Stage Vibration',
    sensor: 'Stage vibration / facility vibration mG',
    metrologyImpact: 'Site-to-site Delta Max-Min expands',
    yieldRisk: 'False local variation signatures can hide real yield patterns',
    advisoryRule: 'If residual and site range rise after a vibration burst, recommend routing review for tight CD layers.'
  }
];

Business logic explanation

This expanded fixture set includes multiple risk profiles: stable release, watch, high fleet deviation, residual rise, slope drift, and yield-watch exposure. That variety is important because engineering dashboards must make abnormal conditions obvious without losing normal baseline context.

Code logic explanation

The fields match MachineRecord exactly. The fixture names use engineering-friendly labels while preserving type-safe property names. Each series has the same length so SVG scaling and live update logic remain predictable.

Expected result

The risk board can demonstrate all major user paths: search for a tool, sort by different metrics, expand a high-risk row, inspect the trend chart, and confirm advisory recommendation language.

System design rationale

● Coverage prevents misleading demos. A dashboard with only one or two rows can appear correct even when sorting, threshold coloring, and row expansion contain defects. A broader fixture set creates enough variation for participants to verify behavior across multiple paths.

● Normal cases remain visible. Field engineering tools should not show only alarms. Including stable baseline tools helps users compare drift candidates against a healthy anchor. That context is valuable for both visual interpretation and software validation.

● Fixture consistency protects chart logic. Keeping series length consistent avoids distracting edge cases during the workshop. The facilitator can later introduce missing or uneven data as an advanced extension after the core typed design is understood.

Expanded lab section: implement top-level state in App.tsx

import { useEffect, useMemo, useState } from 'react';
import { initialMachines } from './data/fixtures';
import { filterMachines, sortMachines } from './domain/risk';
import type { MachineRecord, SortKey, TabKey } from './types';

const tabs: TabKey[] = ['OVERVIEW', 'LIVE RISK BOARD', 'HEALTH MATRIX', 'DYNAMIC MATCHING', 'TRIAGE', 'RUNBOOK'];

export function App() {
  const [activeTab, setActiveTab] = useState<TabKey>('OVERVIEW');
  const [query, setQuery] = useState('');
  const [sortKey, setSortKey] = useState<SortKey>('risk');
  const [expandedId, setExpandedId] = useState<string | null>(null);
  const [machines, setMachines] = useState<MachineRecord[]>(initialMachines);

  const visibleMachines = useMemo(() => {
    return sortMachines(filterMachines(machines, query), sortKey);
  }, [machines, query, sortKey]);

  useEffect(() => {
    let tick = 0;
    const timer = window.setInterval(() => {
      tick += 1;
      setMachines(previous => previous.map((machine, index) => {
        const wave = Math.sin((tick + index) * 0.7);
        const risk = Math.max(5, Math.min(99, machine.risk + wave * 1.7));
        const blindHours = Math.max(0.2, machine.blindHours + 0.05 + wave * 0.03);
        const latest = machine.deltaMeanSeries[machine.deltaMeanSeries.length - 1] ?? 0;
        const nextPoint = Math.max(0, Math.min(0.28, latest + wave * 0.006));

        return {
          ...machine,
          risk,
          blindHours,
          deltaMeanSeries: [...machine.deltaMeanSeries.slice(1), Number(nextPoint.toFixed(3))]
        };
      }));
    }, 5000);

    return () => window.clearInterval(timer);
  }, []);

  return (
    <div className="app-shell">
      <nav className="tabs" aria-label="Portal panels">
        {tabs.map(tab => (
          <button key={tab} className={activeTab === tab ? 'active' : ''} onClick={() => setActiveTab(tab)}>
            {tab}
          </button>
        ))}
      </nav>
      <pre>{JSON.stringify({ activeTab, query, sortKey, expandedId, visibleCount: visibleMachines.length }, null, 2)}</pre>
    </div>
  );
}

Business logic explanation

Top-level state captures the primary dashboard interactions: active panel, text search, selected sorting metric, expanded evidence row, and current machine records. Simulated updates make the portal feel live while remaining disconnected from real systems.

Code logic explanation

visibleMachines is derived with useMemo from machines, query, and sortKey. The update loop creates new machine objects instead of mutating the previous state. The tab list is typed as TabKey[], which prevents unsupported panels.

Expected result

The app should display changing derived state every five seconds. This is a checkpoint before building the full UI.

System design rationale

● State is explicit and minimal. The app stores only values that users control or that change over time. Filtered and sorted data is derived. This prevents duplicated state, which is a common source of dashboard bugs.

● Derived views are memoized. Search and sorting can be recalculated whenever inputs change. useMemo keeps that logic visible and prevents unnecessary recomputation as the app grows.

● The update loop is bounded and reversible. Simulated data changes are small, clamped, and local. The design demonstrates live behavior without pretending to be a production telemetry pipeline.

Expanded lab section: build a typed metric card

Create src/components/MetricCard.tsx:

type MetricCardProps = {
  label: string;
  value: string | number;
  detail: string;
  tone?: 'neutral' | 'pass' | 'warn' | 'danger';
};

export function MetricCard({ label, value, detail, tone = 'neutral' }: MetricCardProps) {
  return (
    <article className={`metric-card ${tone}`}>
      <span>{label}</span>
      <strong>{value}</strong>
      <em>{detail}</em>
    </article>
  );
}

Business logic explanation

Metric cards summarize the operational state before users inspect detail rows. Examples include tools watched, blind-window average, fleet out-of-control count, and yield-watch lots.

Code logic explanation

The component accepts a small typed prop set. tone is optional and constrained to known display styles. The component does not calculate metrics; it only renders them.

Expected result

KPI cards can be reused in the header or summary rows without duplicating markup.

System design rationale

● Presentational components should stay simple. A metric card should render a metric, not decide how fleet risk is calculated. Keeping it presentation-only reduces hidden logic and makes Kiro-generated UI easier to review.

● Typed visual tone prevents CSS drift. Instead of arbitrary class strings, the component supports a closed set of tones. This mirrors the risk-tone pattern and makes future styling safer.

● Reusable layout reduces duplication. Dashboards often repeat cards across panels. A small typed component lets Kiro build consistent UI faster without creating inconsistent markup each time.

Expanded lab section: create the risk board component

Create src/components/RiskBoard.tsx:

import { getRiskTone, getStatusLabel } from '../domain/risk';
import type { MachineRecord, SortKey } from '../types';
import { Sparkline } from './Sparkline';

type RiskBoardProps = {
  machines: MachineRecord[];
  query: string;
  sortKey: SortKey;
  expandedId: string | null;
  onQueryChange: (query: string) => void;
  onSortChange: (key: SortKey) => void;
  onExpandedChange: (id: string | null) => void;
};

const sortOptions: Array<[SortKey, string]> = [
  ['risk', 'Risk'],
  ['blindHours', 'Blind Window'],
  ['matchingGapNm', 'TMG'],
  ['slope', 'Slope'],
  ['fleetSigma', 'Fleet sigma'],
  ['residual3SigmaNm', 'Residual']
];

export function RiskBoard({ machines, query, sortKey, expandedId, onQueryChange, onSortChange, onExpandedChange }: RiskBoardProps) {
  return (
    <section className="risk-board">
      <div className="toolbar">
        <input
          value={query}
          onChange={event => onQueryChange(event.target.value)}
          placeholder="Search by machine, layer, or symptom"
          aria-label="Search by machine, layer, or symptom"
        />
        <div className="sorts">
          {sortOptions.map(([key, label]) => (
            <button key={key} className={sortKey === key ? 'active' : ''} onClick={() => onSortChange(key)}>
              Sort: {label}
            </button>
          ))}
        </div>
      </div>

      {machines.map(machine => {
        const tone = getRiskTone(machine.risk);
        const isExpanded = expandedId === machine.id;
        return (
          <article className="machine-row" key={machine.id}>
            <div className="row-main">
              <strong>{machine.id}</strong>
              <span>{machine.layer}</span>
              <span className={`risk ${tone}`}>{Math.round(machine.risk)} {tone.toUpperCase()}</span>
              <Sparkline data={machine.deltaMeanSeries} />
              <button onClick={() => onExpandedChange(isExpanded ? null : machine.id)} aria-expanded={isExpanded}>
                {isExpanded ? 'Hide evidence' : machine.recommendation}
              </button>
            </div>
            {isExpanded && (
              <div className="evidence-panel">
                <p><b>Status:</b> {getStatusLabel(machine)}</p>
                <p><b>Evidence:</b> {machine.symptom}</p>
                <p><b>Blind Window:</b> {machine.blindHours.toFixed(1)}h</p>
                <p><b>Fleet Sigma:</b> {machine.fleetSigma.toFixed(1)}σ</p>
              </div>
            )}
          </article>
        );
      })}
    </section>
  );
}

Business logic explanation

The Risk Board is where engineers search, rank, and inspect machine evidence. It shows risk status text, layer context, trend evidence, and advisory recommendations.

Code logic explanation

The component receives all state from its parent and emits changes through typed callbacks. It does not own the machine list. This makes the component easier to test and keeps state ownership in App.tsx.

Expected result

Users can search, sort, and expand rows. Each row shows a machine ID, layer, risk text, sparkline, and recommendation button.

System design rationale

● Controlled inputs preserve state traceability. Search and sort state live in the parent, so the current board state can be logged, tested, or passed to other panels later. This is more maintainable than burying local state inside a large table component.

● Callbacks are typed contracts. onSortChange accepts only SortKey, so the component cannot emit an unsupported field. This is another guard against Kiro creating stringly typed UI controls.

● Evidence is exposed on demand. Expanded panels keep the default board dense while still making the reasoning visible. Factory engineers need both fast triage and drill-down evidence.

Expanded lab section: add a larger SVG trajectory chart

Create src/components/TrajectoryChart.tsx:

type TrajectoryChartProps = {
  data: number[];
  advisoryLimit?: number;
};

export function TrajectoryChart({ data, advisoryLimit = 0.18 }: TrajectoryChartProps) {
  const width = 620;
  const height = 190;
  const pad = 24;
  const min = Math.min(...data, -0.02);
  const max = Math.max(...data, 0.26, advisoryLimit);

  const xFor = (index: number) => pad + (index / Math.max(1, data.length - 1)) * (width - pad * 2);
  const yFor = (value: number) => height - pad - ((value - min) / (max - min || 1)) * (height - pad * 2);
  const points = data.map((value, index) => `${xFor(index)},${yFor(value)}`).join(' ');

  return (
    <svg className="trajectory-chart" viewBox={`0 0 ${width} ${height}`} role="img" aria-label="DeltaMean trajectory chart">
      <line x1={pad} x2={width - pad} y1={yFor(advisoryLimit)} y2={yFor(advisoryLimit)} stroke="#ff5b6e" strokeDasharray="6 6" />
      <polyline points={points} fill="none" stroke="#35d5ff" strokeWidth="4" strokeLinecap="round" strokeLinejoin="round" />
      {data.map((value, index) => (
        <circle key={index} cx={xFor(index)} cy={yFor(value)} r="4" fill={value > advisoryLimit ? '#ff5b6e' : '#35d5ff'} />
      ))}
    </svg>
  );
}

Business logic explanation

The expanded chart shows whether recent DeltaMean values are approaching or crossing an advisory threshold. It supports engineering review by making trend direction and threshold crossings visible.

Code logic explanation

The component converts a numeric series into SVG coordinates. The advisory limit is a prop with a default value, which makes the chart reusable for different layers or process windows later.

Expected result

The evidence panel can show a larger trajectory chart with a dashed advisory limit line and colored points.

System design rationale

● Thresholds are visible in context. A number alone does not show trend velocity. A chart with an advisory line helps engineers distinguish a stable high value from a fast-rising risk signal.

● The chart remains deterministic. No external rendering library or asynchronous data source is required. The math is transparent and easy for participants to inspect.

● The limit is configurable but bounded. Making advisoryLimit a typed prop avoids hard-coding every threshold while still preventing hidden global behavior.

Expanded lab section: add unit-testable update logic

Move the update function into src/domain/risk.ts:

export function simulateMachineUpdate(machine: MachineRecord, tick: number, index: number): MachineRecord {
  const wave = Math.sin((tick + index) * 0.7);
  const risk = Math.max(5, Math.min(99, machine.risk + wave * 1.7));
  const blindHours = Math.max(0.2, machine.blindHours + 0.05 + wave * 0.03);
  const latest = machine.deltaMeanSeries[machine.deltaMeanSeries.length - 1] ?? 0;
  const nextPoint = Math.max(0, Math.min(0.28, latest + wave * 0.006));

  return {
    ...machine,
    risk,
    blindHours,
    deltaMeanSeries: [...machine.deltaMeanSeries.slice(1), Number(nextPoint.toFixed(3))]
  };
}

Then simplify the effect:

useEffect(() => {
  let tick = 0;
  const timer = window.setInterval(() => {
    tick += 1;
    setMachines(previous => previous.map((machine, index) => simulateMachineUpdate(machine, tick, index)));
  }, 5000);

  return () => window.clearInterval(timer);
}, []);

Business logic explanation

The update function simulates minor risk and trend changes to keep the dashboard active. It does not represent actual equipment telemetry.

Code logic explanation

Moving the function out of App.tsx makes it testable. The React effect becomes responsible only for scheduling updates.

Expected result

Behavior remains the same, but the code is easier to validate and maintain.

System design rationale

● Scheduling and calculation are separate concerns. React effects should manage time and lifecycle. Domain functions should calculate new values. This separation makes both parts easier to reason about.

● Pure simulation supports testing. A pure function can be tested with known inputs. That is useful because live update bugs often appear as gradual drift or state mutation, which are hard to diagnose in UI-only tests.

● The model remains honest. Naming the function simulateMachineUpdate makes clear that values are synthetic. This avoids confusing demo behavior with validated factory data.

Expanded lab section: README content

Ask Kiro to generate a README with this prompt:

Create a README for this local-only TypeScript React factory risk portal. Include install commands, run commands, architecture summary, Kiro steering and hook notes, advisory-only disclaimer, and a warning that fixture data must be replaced with validated engineering data before operational use.

Minimum README content:

# Factory Risk Portal

Local-only TypeScript React workshop project built with Kiro.

## Run

npm install
npm run dev

## Build

npm run build

## Safety boundary

This application is advisory-only. It does not command equipment, change route state, write to dispatch systems, modify APC feedback, or bypass human approval. Fixture data is for learning only and must be replaced with validated engineering data before operational use.

System design rationale

● Documentation is part of the deliverable. Engineering software should explain how to run it and what it does not do. This is especially important when the UI resembles a factory control dashboard.

● The safety boundary travels with the code. A README can be read by future developers who did not attend the workshop. It preserves the advisory-only constraint outside the live Kiro session.

● Kiro can generate useful maintenance artifacts. Documentation generation is a practical use of Kiro after implementation, but it should be based on the actual project structure and constraints rather than generic boilerplate.

Debugging guide for participants

Problem: TypeScript says sort key cannot index machine record

Likely cause: SortKey includes a field that is not numeric or does not exist on MachineRecord.

Fix prompt:

Inspect SortKey and MachineRecord. Make the sort key union include only numeric MachineRecord fields. Update sort buttons to match. Do not use type assertions to hide the problem.

Problem: Risk colors render but screen readers have no status

Likely cause: status is communicated only by CSS.

Fix prompt:

Update risk display so it includes visible text and an aria-label with the numeric risk and risk tone. Keep the existing colors.

Problem: Kiro generated a new action label

Likely cause: the model treated recommendations as ordinary strings.

Fix prompt:

The recommendation field must use the existing Recommendation union. Replace unsupported labels with the closest existing advisory recommendation or propose a union update before changing fixtures.

Problem: The final code is all in one file

Likely cause: the implementation prompt did not constrain structure enough.

Fix prompt:

Refactor the existing behavior into the agreed structure. Preserve runtime behavior. Move types, fixtures, pure domain functions, and reusable components into separate files. Explain each movement before editing.

Additional lab section — Build fuller fixture coverage

The earlier lab uses a minimal fixture to teach the concept quickly. For a richer workshop demo, expand src/data/fixtures.ts with additional records. This gives sorting, searching, color thresholds, charting, and KPI calculations more realistic behavior.

export const additionalMachines: MachineRecord[] = [
  {
    id: 'CDSEM-05',
    layer: 'Contact/Via',
    symptom: 'Vacuum slow degradation, residual rising',
    risk: 76,
    blindHours: 6.9,
    matchingGapNm: 0.19,
    slope: 1.006,
    fleetSigma: 2.4,
    residual3SigmaNm: 0.21,
    recommendation: 'GOLDEN WAFER',
    deltaMeanSeries: [0.02, 0.01, 0.04, 0.06, 0.08, 0.10, 0.12, 0.13, 0.16, 0.18]
  },
  {
    id: 'CDSEM-06',
    layer: 'Overlay support',
    symptom: 'Vibration burst after facility event',
    risk: 72,
    blindHours: 4.3,
    matchingGapNm: 0.17,
    slope: 0.989,
    fleetSigma: 2.2,
    residual3SigmaNm: 0.19,
    recommendation: 'ROUTE LIMIT',
    deltaMeanSeries: [0.01, 0.00, 0.03, 0.02, 0.12, 0.03, 0.13, 0.05, 0.15, 0.06]
  },
  {
    id: 'CDSEM-10',
    layer: 'Risk ramp product',
    symptom: 'Yield-loss watch lots routed here',
    risk: 82,
    blindHours: 6.2,
    matchingGapNm: 0.21,
    slope: 0.976,
    fleetSigma: 2.9,
    residual3SigmaNm: 0.17,
    recommendation: 'APC GUARD',
    deltaMeanSeries: [0.02, 0.04, 0.07, 0.09, 0.11, 0.13, 0.15, 0.18, 0.20, 0.23]
  }
];

Business logic explanation

The expanded records introduce three different risk patterns: residual-driven review, vibration-related route advisory, and APC guard evidence. This gives developers examples of different machine symptoms without connecting to real telemetry.

Code logic explanation

The records use the same MachineRecord type. They can be merged into initialMachines with the spread operator. TypeScript validates every recommendation and metric field.

Expected result

The Live Risk Board becomes more useful for demonstration. Sorting by risk, fleet sigma, residual, or blind-window age now changes the order in visible ways.

System design rationale

● A richer dataset exposes UI edge cases. With only two records, developers cannot fully validate sort behavior, row wrapping, warning colors, or multiple recommendation labels. Additional records create realistic layout pressure while still keeping the lab safe and deterministic.

● Different symptoms test domain language. Vacuum, vibration, and APC guard examples force the UI to show evidence text, not just a risk number. This is important because engineering reviewers need the reason behind a recommendation before acting on it.

● Typed fixture expansion proves scalability. Adding records should not require component changes. If the app breaks when new records are added, the design is too tightly coupled. This teaches developers that data-driven rendering is a system design goal.

Additional lab section — Add KPI calculations as pure functions

Create src/domain/kpi.ts:

import type { MachineRecord } from '../types';

export type KpiSummary = {
  toolsWatched: number;
  averageBlindHours: number;
  fleetOocRiskCount: number;
  yieldWatchCount: number;
  fdcHealthLinks: number;
  dynamicLimitsEnabled: boolean;
};

export function calculateKpis(machines: MachineRecord[], healthLinkCount: number): KpiSummary {
  const averageBlindHours = machines.reduce((sum, machine) => sum + machine.blindHours, 0) / machines.length;
  const fleetOocRiskCount = machines.filter(machine => machine.fleetSigma > 2).length;
  const yieldWatchCount = machines.filter(machine =>
    machine.recommendation === 'HOLD REVIEW' ||
    machine.recommendation === 'GOLDEN WAFER' ||
    machine.recommendation === 'APC GUARD'
  ).length;

  return {
    toolsWatched: machines.length,
    averageBlindHours,
    fleetOocRiskCount,
    yieldWatchCount,
    fdcHealthLinks: healthLinkCount,
    dynamicLimitsEnabled: true
  };
}

Business logic explanation

KPI cards summarize operational review pressure: how many tools are watched, average blind-window age, fleet out-of-control exposure, health-link coverage, dynamic limit status, and yield-watch load.

Code logic explanation

The function accepts machine records and a health-link count, then returns a typed KpiSummary. It centralizes calculations outside React rendering so the same values can be tested and reused.

Expected result

App.tsx can calculate KPI values with one pure function call and pass the summary to a KpiStrip component.

System design rationale

● KPI calculation belongs outside JSX. Dashboards often bury KPI math inside render blocks. That makes review and testing harder. A pure KPI function makes the calculation visible and reusable.

● The summary type documents business meaning. KpiSummary gives names to the values shown in the UI. This is easier to review than an array of anonymous strings and numbers.

● Kpi logic supports future replacement of fixture data. When live validated data is introduced later, the KPI function can remain stable. Only the data source changes.

Additional lab section — Add a trajectory chart component

Create src/components/TrajectoryChart.tsx:

type TrajectoryChartProps = {
  data: number[];
  threshold?: number;
};

export function TrajectoryChart({ data, threshold = 0.18 }: TrajectoryChartProps) {
  const width = 620;
  const height = 188;
  const pad = 22;
  const min = Math.min(...data, -0.02);
  const max = Math.max(...data, 0.26);

  const xFor = (index: number) => pad + (index / Math.max(1, data.length - 1)) * (width - pad * 2);
  const yFor = (value: number) => height - pad - ((value - min) / (max - min || 1)) * (height - pad * 2);
  const points = data.map((value, index) => `${xFor(index)},${yFor(value)}`).join(' ');

  return (
    <svg className="big-chart" viewBox={`0 0 ${width} ${height}`} role="img" aria-label="DeltaMean trajectory chart">
      <line x1={pad} x2={width - pad} y1={yFor(threshold)} y2={yFor(threshold)} stroke="#ff5b6e" strokeDasharray="6 6" />
      <polyline points={points} fill="none" stroke="#35d5ff" strokeWidth="4" strokeLinejoin="round" strokeLinecap="round" />
      {data.map((value, index) => (
        <circle key={index} cx={xFor(index)} cy={yFor(value)} r="4" fill={value > threshold ? '#ff5b6e' : '#35d5ff'} />
      ))}
    </svg>
  );
}

Business logic explanation

The trajectory chart shows whether recent drift is approaching or crossing a review threshold. It gives an expanded evidence view for a selected machine row.

Code logic explanation

The component converts data points into SVG coordinates, draws a threshold line, plots the series, and colors points above threshold differently while still relying on text metrics elsewhere for status.

Expected result

Expanded rows can show a larger chart that supports engineering review.

System design rationale

● Expanded evidence separates summary from detail. A table row should stay compact. Detailed drift evidence belongs in an expandable panel where the user intentionally asks for more context.

● Threshold rendering supports traceability. Showing the threshold visually makes it easier to understand why a recommendation exists. However, the status must still be shown as text to avoid color-only interpretation.

● Typed chart props reduce accidental misuse. The chart accepts only numeric data and an optional numeric threshold. This keeps the component reusable and prevents Kiro from passing entire machine objects where a number array is expected.

Additional lab section — Add unit tests for pure logic

If your workshop environment allows adding Vitest, ask Kiro to add tests. If package installation is not desired, treat this as an optional read-through exercise.

Kiro prompt sample

Add Vitest tests for the pure domain functions only. Test risk tone thresholds, search filtering, sorting by risk, sorting by fleet sigma, KPI calculation, and simulated update bounds. Do not test CSS or implementation details of React components.

Example test file src/domain/risk.test.ts:

import { describe, expect, it } from 'vitest';
import { getRiskTone, sortMachines } from './risk';
import { initialMachines } from '../data/fixtures';

describe('risk domain logic', () => {
  it('classifies risk thresholds', () => {
    expect(getRiskTone(20)).toBe('pass');
    expect(getRiskTone(55)).toBe('warn');
    expect(getRiskTone(80)).toBe('danger');
  });

  it('sorts machines by highest risk first', () => {
    const sorted = sortMachines(initialMachines, 'risk');
    expect(sorted[0].risk).toBeGreaterThanOrEqual(sorted[1].risk);
  });
});

Business logic explanation

Tests capture the engineering rules behind the dashboard. A future refactor should not accidentally change risk thresholds or sort order.

Code logic explanation

The tests call pure functions directly. They do not depend on the browser or rendered components.

Expected result

Developers gain a fast safety net for the most important logic.

System design rationale

● Test pure logic before UI. Risk thresholds and sort order are more critical than whether a CSS class is applied. Unit tests should protect the engineering decisions first.

● Avoid brittle visual tests in a short workshop. Snapshot or pixel-style testing can distract participants. Pure function tests are easier to understand and maintain.

● Kiro can generate tests after contracts exist. AI-generated tests are better when types and functions are already stable. Asking for tests too early often creates tests around implementation details that later change.

Additional lab section — Add README content with operational boundaries

Ask Kiro to create or update README.md:

Update README.md with setup commands, project structure, Kiro workflow, type standards, hook descriptions, and advisory-only limitations. State clearly that fixture data is synthetic learning data and must be replaced with validated engineering data before any operational use.

Suggested README section:

## Operational boundary

This project is a learning application. It does not connect to factory equipment, dispatch systems, APC systems, or production databases. All records are local fixtures. Recommendations are advisory labels for review workflows only and must not be interpreted as autonomous machine commands.

System design rationale

● Documentation is part of the safety boundary. A developer may understand that an app is local-only, but a future reader may not. README text preserves the intended use.

● Run instructions reduce workshop friction. Participants often return to the project later. Clear setup commands let them restart without depending on memory.

● Kiro workflow notes make the lesson portable. Documenting steering, specs, and hooks helps participants reuse the pattern in their own repositories.

Advanced challenge — Make Kiro refactor without changing behavior

Kiro prompt sample

Refactor the Live Risk Board into `RiskBoard.tsx`, `MachineRow.tsx`, and `EvidencePanel.tsx`. Preserve behavior exactly. Keep all props typed. Do not change CSS class names. Show a summary of files changed before implementation.

System design rationale

● Refactoring is safer with explicit invariants. “Preserve behavior exactly” and “do not change CSS class names” are important constraints. Without them, Kiro may improve structure but unintentionally break styling or interactions.

● Typed props reveal component boundaries. Refactoring forces developers to decide what each component owns. This is a good test of whether the domain model is clean.

● A change summary improves review. Before implementation, Kiro should explain what files it plans to touch. This reduces surprise and models professional pull-request behavior.