Step 2 — TypeScript Essentials for React

Before touching JSX, let's cover the handful of TypeScript patterns that show up in nearly every iKanban component. We'll read them straight out of src/components/AgentModeBadge.tsx — a small, real component that renders a colored badge describing whether an AI agent runs via CLI, API, both, or neither.

1. A union of string literals

export type AvailabilityMode = 'cli_only' | 'api_only' | 'both' | 'none';

This is not an enum — it's a type that can only ever be one of these four exact strings. Pass anything else and TypeScript rejects it at compile time. This pattern shows up everywhere in iKanban: task status, issue priority, agent lifecycle state — any field with a small, fixed set of valid values is modeled this way instead of a free-form string.

2. Props as an interface

interface AgentModeBadgeProps {
  mode: AvailabilityMode;
  className?: string;
  showTooltip?: boolean;
}

export function AgentModeBadge({
  mode,
  className,
  showTooltip = true,
}: AgentModeBadgeProps) {
  // ...
}

Every component's inputs are an interface (or type), destructured directly in the function signature. className? and showTooltip? are optional — the ? means callers can omit them. showTooltip = true is a default value applied during destructuring, a plain JavaScript feature that pairs naturally with optional TS props.

3. A lookup object keyed by the union, typed as a const

const MODE_CONFIG = {
  cli_only: { icon: Monitor, labelKey: 'settings.agents.mode.cliOnly', /* ... */ },
  api_only: { icon: Cloud, labelKey: 'settings.agents.mode.apiOnly', /* ... */ },
  both: { icon: RefreshCw, labelKey: 'settings.agents.mode.both', /* ... */ },
  none: { icon: XCircle, labelKey: 'settings.agents.mode.none', /* ... */ },
} as const;

Because the object's keys are exactly the four AvailabilityMode values, MODE_CONFIG[mode] is guaranteed to exist for any valid mode — no undefined check needed. This is the standard way iKanban maps a union type to per-variant configuration (icon, label, styling) without a chain of if/else.

4. as const narrows literal types

Without as const, TypeScript would widen variant: 'secondary' to the general type string. With it, TypeScript keeps the exact literal 'secondary', which matters because the <Badge variant={...}> prop only accepts a specific set of literal strings, not any string.

Putting it together

const config = MODE_CONFIG[mode];
const Icon = config.icon;

return (
  <Badge variant={config.variant}>
    <Icon className="h-3 w-3" />
    {t(config.labelKey)}
  </Badge>
);

Icon here is a component reference stored in a variable, then rendered with <Icon />. This is how iKanban swaps icons dynamically based on data — you'll see the same trick in the sidebar icon map in Step 4.

Checkpoint

Open vibe-frontend/src/components/AgentModeBadge.tsx yourself and find:

  1. Where the optional showTooltip prop changes what gets rendered (hint: an early return).
  2. How mode === 'both' conditionally appends extra Tailwind classes to the badge.

Continue to Step 3 — Vite, TypeScript Config & Project Structure, where we cover how these .tsx files get built and how @/ imports resolve.