# Flight Checker Checklist Integration — Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Remplacer le scaffolding placeholder de `/flight-checker` par la vraie checklist The Website Specification (10 catégories, 128 items), affichée dans une `DataTable` TanStack cochable avec recherche globale et progression.

**Architecture:** Data statique typée committée (`src/lib/spec/`), lue par un server component (`page.tsx`) et passée à un client component parent (`checklist.tsx`). Approche A : une seule `DataTable`, l'onglet pilote un filtre `category`, la recherche un `globalFilter`. État coché en localStorage via un hook à interface stable (migration SurrealDB future). Régénération via script Bun qui parse des dumps markdown committés.

**Tech Stack:** Next 16 (App Router, RSC), React 19, TanStack Table 8, shadcn v4 (radix-ui), Tailwind 4, Bun (runtime + `bun test`), Biome (lint/format).

**Spec:** `docs/superpowers/specs/2026-06-03-flight-checker-checklist-design.md`

---

## File Structure

| Fichier | Action | Responsabilité |
|---|---|---|
| `src/lib/spec/types.ts` | Create | Types `SpecStatus`, `SpecCategoryId`, `SpecItem`, `SpecCategory` + métadonnées catégories. |
| `src/lib/spec/checklist-data.ts` | Create | `export const CHECKLIST: SpecCategory[]` — les 128 items. |
| `src/lib/spec/checklist-data.test.ts` | Create | Tests d'intégrité data (bun test). |
| `scripts/spec-source/*.md` | Create | Dumps markdown bruts par catégorie (sortie MCP `get_checklist`), source de régénération. |
| `scripts/generate-checklist.ts` | Create | Parse les dumps markdown → réécrit `checklist-data.ts`. |
| `src/hooks/use-checklist-state.ts` | Create | Hook localStorage : `isChecked`, `toggle`, `reset`, `progress`. |
| `src/components/ui/checkbox.tsx` | Create (shadcn) | Primitive checkbox. |
| `src/components/ui/badge.tsx` | Create (shadcn) | Primitive badge. |
| `src/components/flight-check/status-badge.tsx` | Create | `Badge` colorée par statut. |
| `src/components/flight-check/columns.tsx` | Rewrite | Colonnes `SpecItem` (checkbox, statut, titre, desc, lien). |
| `src/components/flight-check/data-table.tsx` | Modify | Généralise : globalFilter contrôlé, filtre category, faceted status, binding checked. |
| `src/components/flight-check/checklist.tsx` | Create | Client parent : search + Tabs + progression + DataTable. |
| `src/components/flight-check/checklist-tabs.tsx` | Delete | Placeholder remplacé. |
| `src/app/flight-checker/page.tsx` | Modify | Server : nettoie le scaffolding, rend `<Checklist>`. |

**Convention `SpecItem.id`** : globalement unique = `` `${category}/${slug}` `` (ex: `foundations/doctype`), le slug étant le dernier segment de l'URL. Sert de clé React **et** de clé localStorage.

**Comptes attendus par catégorie** (verrouillés par le test d'intégrité) : foundations 14, seo 13, accessibility 20, security 12, well-known 9, agent-readiness 18, performance 19, privacy 6, resilience 5, i18n 12. **Total = 128.**

---

## Task 1: Types & métadonnées catégories

**Files:**
- Create: `src/lib/spec/types.ts`

- [ ] **Step 1: Écrire les types et la table des catégories**

```ts
// src/lib/spec/types.ts
export type SpecStatus = "required" | "recommended" | "optional" | "avoid";

export type SpecCategoryId =
  | "foundations"
  | "seo"
  | "accessibility"
  | "security"
  | "well-known"
  | "agent-readiness"
  | "performance"
  | "privacy"
  | "resilience"
  | "i18n";

export type SpecItem = {
  /** Globalement unique : `${category}/${slug}`, ex "foundations/doctype". */
  id: string;
  category: SpecCategoryId;
  title: string;
  status: SpecStatus;
  description: string;
  url: string;
};

export type SpecCategory = {
  id: SpecCategoryId;
  label: string;
  summary: string;
  items: SpecItem[];
};

/** Métadonnées ordonnées des 10 catégories (ordre = ordre d'affichage des onglets). */
export const CATEGORY_META: { id: SpecCategoryId; label: string; summary: string }[] = [
  { id: "foundations", label: "Foundations", summary: "HTML, head, document basics." },
  { id: "seo", label: "SEO", summary: "Search visibility." },
  { id: "accessibility", label: "Accessibility", summary: "WCAG-aligned rules." },
  { id: "security", label: "Security", summary: "Headers, transport, policies." },
  { id: "well-known", label: "Well-Known URIs", summary: "Standard /.well-known/ paths." },
  { id: "agent-readiness", label: "Agent Readiness", summary: "Discoverability by AI agents." },
  { id: "performance", label: "Performance", summary: "Core Web Vitals, caching, fonts." },
  { id: "privacy", label: "Privacy", summary: "Consent and visitor choice." },
  { id: "resilience", label: "Resilience", summary: "Graceful failure." },
  { id: "i18n", label: "Internationalisation", summary: "Language, locale, direction." },
];

export const CATEGORY_ORDER: SpecCategoryId[] = CATEGORY_META.map((c) => c.id);
```

- [ ] **Step 2: Typecheck**

Run: `bunx tsc --noEmit`
Expected: PASS (aucune erreur sur `types.ts`).

- [ ] **Step 3: Commit**

```bash
git add src/lib/spec/types.ts
git commit -m "feat: spec types and category metadata for flight-checker"
```

---

## Task 2: Dumps markdown source (régénération)

**Files:**
- Create: `scripts/spec-source/foundations.md` … `scripts/spec-source/i18n.md` (10 fichiers)

> Source de vérité reproductible pour la régénération. Contenu = sortie brute de l'outil MCP `specification-website` → `get_checklist({ category })`, un fichier par `SpecCategoryId`. **L'agent exécutant produit ces dumps en appelant le MCP `get_checklist` pour les 10 catégories** (`foundations`, `seo`, `accessibility`, `security`, `well-known`, `agent-readiness`, `performance`, `privacy`, `resilience`, `i18n`) et colle la sortie Markdown telle quelle dans chaque fichier.

- [ ] **Step 1: Générer les 10 dumps via MCP**

Pour chaque catégorie, appeler l'outil MCP `mcp__specification-website__get_checklist` avec `{ category: "<id>" }` et écrire la sortie dans `scripts/spec-source/<id>.md`. Le format de chaque item dans la sortie est :

```
- [ ] **The HTML doctype** _(required)_ — Every HTML document must start with <!doctype html>...
      https://specification.website/spec/foundations/doctype/
```

- [ ] **Step 2: Vérifier le nombre d'items par fichier**

Run: `grep -c '^- \[ \] \*\*' scripts/spec-source/foundations.md`
Expected: `14`

Vérifier aussi les autres : seo=13, accessibility=20, security=12, well-known=9, agent-readiness=18, performance=19, privacy=6, resilience=5, i18n=12.

- [ ] **Step 3: Commit**

```bash
git add scripts/spec-source/
git commit -m "chore: committed spec checklist markdown source dumps"
```

---

## Task 3: Script de génération

**Files:**
- Create: `scripts/generate-checklist.ts`

- [ ] **Step 1: Écrire le parser/générateur**

```ts
// scripts/generate-checklist.ts
// Usage: bun scripts/generate-checklist.ts
// Parse scripts/spec-source/<category>.md → réécrit src/lib/spec/checklist-data.ts
import { readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { CATEGORY_META, type SpecCategoryId, type SpecItem } from "../src/lib/spec/types";

const ROOT = join(import.meta.dir, "..");
const SRC_DIR = join(ROOT, "scripts", "spec-source");
const OUT = join(ROOT, "src", "lib", "spec", "checklist-data.ts");

const ITEM_RE = /^- \[ \] \*\*(.+?)\*\* _\((required|recommended|optional|avoid)\)_ — (.+)$/;

function slugFromUrl(url: string): string {
  return url.replace(/\/+$/, "").split("/").pop() ?? "";
}

function parseCategory(id: SpecCategoryId): SpecItem[] {
  const md = readFileSync(join(SRC_DIR, `${id}.md`), "utf8");
  const lines = md.split("\n");
  const items: SpecItem[] = [];
  for (let i = 0; i < lines.length; i++) {
    const m = lines[i].match(ITEM_RE);
    if (!m) continue;
    const [, title, status, description] = m;
    const url = (lines[i + 1] ?? "").trim();
    if (!url.startsWith("http")) throw new Error(`Missing URL after "${title}" in ${id}.md`);
    items.push({
      id: `${id}/${slugFromUrl(url)}`,
      category: id,
      title,
      status: status as SpecItem["status"],
      description,
      url,
    });
  }
  return items;
}

const categories = CATEGORY_META.map((meta) => ({
  ...meta,
  items: parseCategory(meta.id),
}));

const total = categories.reduce((n, c) => n + c.items.length, 0);
const banner = `// AUTO-GENERATED by scripts/generate-checklist.ts — do not edit by hand.\n// Source: scripts/spec-source/*.md (MCP specification-website get_checklist).\n`;
const body = `${banner}import type { SpecCategory } from "./types";\n\nexport const CHECKLIST: SpecCategory[] = ${JSON.stringify(
  categories,
  null,
  2,
)};\n`;

writeFileSync(OUT, body);
console.log(`Wrote ${OUT} — ${categories.length} categories, ${total} items.`);
```

- [ ] **Step 2: Lancer le générateur**

Run: `bun scripts/generate-checklist.ts`
Expected: `Wrote .../checklist-data.ts — 10 categories, 128 items.`

- [ ] **Step 3: Formater le fichier généré**

Run: `bun run format`
Expected: PASS (Biome reformate `checklist-data.ts`).

- [ ] **Step 4: Commit**

```bash
git add scripts/generate-checklist.ts src/lib/spec/checklist-data.ts
git commit -m "feat: generate static checklist data from spec source"
```

---

## Task 4: Test d'intégrité de la data

**Files:**
- Create: `src/lib/spec/checklist-data.test.ts`

- [ ] **Step 1: Écrire le test (qui doit échouer si la data est incomplète)**

```ts
// src/lib/spec/checklist-data.test.ts
import { expect, test } from "bun:test";
import { CHECKLIST } from "./checklist-data";
import { CATEGORY_ORDER } from "./types";

const EXPECTED: Record<string, number> = {
  foundations: 14,
  seo: 13,
  accessibility: 20,
  security: 12,
  "well-known": 9,
  "agent-readiness": 18,
  performance: 19,
  privacy: 6,
  resilience: 5,
  i18n: 12,
};

test("contains all 10 categories in order", () => {
  expect(CHECKLIST.map((c) => c.id)).toEqual(CATEGORY_ORDER);
});

test("each category has the expected item count, 128 total", () => {
  let total = 0;
  for (const cat of CHECKLIST) {
    expect(cat.items.length).toBe(EXPECTED[cat.id]);
    total += cat.items.length;
  }
  expect(total).toBe(128);
});

test("every item id is globally unique and well-formed", () => {
  const ids = new Set<string>();
  for (const cat of CHECKLIST) {
    for (const item of cat.items) {
      expect(item.id.startsWith(`${cat.id}/`)).toBe(true);
      expect(item.category).toBe(cat.id);
      expect(item.title.length).toBeGreaterThan(0);
      expect(item.url.startsWith("https://")).toBe(true);
      expect(ids.has(item.id)).toBe(false);
      ids.add(item.id);
    }
  }
  expect(ids.size).toBe(128);
});

test("every status is one of the four valid values", () => {
  const valid = new Set(["required", "recommended", "optional", "avoid"]);
  for (const cat of CHECKLIST) {
    for (const item of cat.items) {
      expect(valid.has(item.status)).toBe(true);
    }
  }
});
```

- [ ] **Step 2: Lancer le test**

Run: `bun test src/lib/spec/checklist-data.test.ts`
Expected: PASS (4 tests). Si échec sur les comptes, corriger les dumps de Task 2 et relancer Task 3.

- [ ] **Step 3: Commit**

```bash
git add src/lib/spec/checklist-data.test.ts
git commit -m "test: integrity checks for checklist data"
```

---

## Task 5: Hook d'état coché (localStorage)

**Files:**
- Create: `src/hooks/use-checklist-state.ts`

- [ ] **Step 1: Écrire le hook**

```ts
// src/hooks/use-checklist-state.ts
"use client";

import { useCallback, useEffect, useState } from "react";

const STORAGE_KEY = "flight-checker:checked";

type CheckedMap = Record<string, true>;

function read(): CheckedMap {
  if (typeof window === "undefined") return {};
  try {
    const raw = window.localStorage.getItem(STORAGE_KEY);
    return raw ? (JSON.parse(raw) as CheckedMap) : {};
  } catch {
    return {};
  }
}

export type ChecklistState = {
  /** true tant que le state localStorage n'est pas hydraté (rendu serveur). */
  hydrating: boolean;
  isChecked: (id: string) => boolean;
  toggle: (id: string) => void;
  reset: () => void;
  /** done/total ; sans `ids`, compte global via `total`. */
  progress: (ids: string[]) => { done: number; total: number };
};

export function useChecklistState(): ChecklistState {
  const [checked, setChecked] = useState<CheckedMap>({});
  const [hydrating, setHydrating] = useState(true);

  // Hydratation après montage uniquement → pas de mismatch SSR.
  useEffect(() => {
    setChecked(read());
    setHydrating(false);
  }, []);

  const persist = useCallback((next: CheckedMap) => {
    setChecked(next);
    try {
      window.localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
    } catch {
      // quota / mode privé : on garde l'état en mémoire.
    }
  }, []);

  const isChecked = useCallback((id: string) => checked[id] === true, [checked]);

  const toggle = useCallback(
    (id: string) => {
      const next = { ...checked };
      if (next[id]) delete next[id];
      else next[id] = true;
      persist(next);
    },
    [checked, persist],
  );

  const reset = useCallback(() => persist({}), [persist]);

  const progress = useCallback(
    (ids: string[]) => ({
      done: ids.reduce((n, id) => (checked[id] ? n + 1 : n), 0),
      total: ids.length,
    }),
    [checked],
  );

  return { hydrating, isChecked, toggle, reset, progress };
}
```

- [ ] **Step 2: Typecheck**

Run: `bunx tsc --noEmit`
Expected: PASS.

- [ ] **Step 3: Commit**

```bash
git add src/hooks/use-checklist-state.ts
git commit -m "feat: localStorage-backed checklist state hook"
```

---

## Task 6: Composants shadcn (checkbox, badge) + status-badge

**Files:**
- Create: `src/components/ui/checkbox.tsx` (shadcn CLI)
- Create: `src/components/ui/badge.tsx` (shadcn CLI)
- Create: `src/components/flight-check/status-badge.tsx`

- [ ] **Step 1: Installer les primitives shadcn**

Run: `bunx shadcn@latest add checkbox badge`
Expected: crée `src/components/ui/checkbox.tsx` et `src/components/ui/badge.tsx`.

- [ ] **Step 2: Vérifier la présence des fichiers**

Run: `ls src/components/ui/checkbox.tsx src/components/ui/badge.tsx`
Expected: les deux chemins existent.

- [ ] **Step 3: Écrire le StatusBadge**

```tsx
// src/components/flight-check/status-badge.tsx
import { Badge } from "@/components/ui/badge";
import type { SpecStatus } from "@/lib/spec/types";

const STYLES: Record<SpecStatus, string> = {
  required: "border-transparent bg-red-500/15 text-red-700 dark:text-red-300",
  recommended: "border-transparent bg-blue-500/15 text-blue-700 dark:text-blue-300",
  optional: "border-transparent bg-muted text-muted-foreground",
  avoid: "border-transparent bg-amber-500/15 text-amber-700 dark:text-amber-300",
};

const LABELS: Record<SpecStatus, string> = {
  required: "Required",
  recommended: "Recommended",
  optional: "Optional",
  avoid: "Avoid",
};

export function StatusBadge({ status }: { status: SpecStatus }) {
  return (
    <Badge variant="outline" className={STYLES[status]}>
      {LABELS[status]}
    </Badge>
  );
}
```

- [ ] **Step 4: Typecheck**

Run: `bunx tsc --noEmit`
Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add src/components/ui/checkbox.tsx src/components/ui/badge.tsx src/components/flight-check/status-badge.tsx
git commit -m "feat: add checkbox/badge primitives and status badge"
```

---

## Task 7: Colonnes SpecItem

**Files:**
- Rewrite: `src/components/flight-check/columns.tsx`

> La cellule checkbox lit/écrit l'état via `meta` de la table (fourni par `data-table.tsx` en Task 8) plutôt que la row-selection interne de TanStack, pour brancher directement le hook localStorage.

- [ ] **Step 1: Réécrire columns.tsx**

```tsx
// src/components/flight-check/columns.tsx
"use client";

import type { ColumnDef, RowData } from "@tanstack/react-table";
import { ArrowUpRight } from "lucide-react";
import { Checkbox } from "@/components/ui/checkbox";
import { StatusBadge } from "@/components/flight-check/status-badge";
import type { SpecItem } from "@/lib/spec/types";

declare module "@tanstack/react-table" {
  interface TableMeta<TData extends RowData> {
    isChecked: (id: string) => boolean;
    toggle: (id: string) => void;
  }
}

export const columns: ColumnDef<SpecItem>[] = [
  {
    id: "done",
    header: "",
    enableGlobalFilter: false,
    cell: ({ row, table }) => {
      const id = row.original.id;
      return (
        <Checkbox
          checked={table.options.meta?.isChecked(id) ?? false}
          onCheckedChange={() => table.options.meta?.toggle(id)}
          aria-label={`Mark "${row.original.title}" as done`}
        />
      );
    },
  },
  {
    accessorKey: "status",
    header: "Status",
    enableGlobalFilter: false,
    filterFn: (row, columnId, value: string[]) =>
      value.length === 0 || value.includes(row.getValue(columnId)),
    cell: ({ row }) => <StatusBadge status={row.original.status} />,
  },
  {
    accessorKey: "title",
    header: "Item",
    cell: ({ row }) => <span className="font-medium">{row.original.title}</span>,
  },
  {
    accessorKey: "description",
    header: "Description",
    cell: ({ row }) => (
      <span className="text-muted-foreground">{row.original.description}</span>
    ),
  },
  {
    id: "link",
    header: "",
    enableGlobalFilter: false,
    cell: ({ row }) => (
      <a
        href={row.original.url}
        target="_blank"
        rel="noopener noreferrer"
        className="text-muted-foreground hover:text-foreground inline-flex"
        aria-label={`Open spec for ${row.original.title}`}
      >
        <ArrowUpRight className="size-4" />
      </a>
    ),
  },
];
```

- [ ] **Step 2: Typecheck**

Run: `bunx tsc --noEmit`
Expected: PASS.

- [ ] **Step 3: Commit**

```bash
git add src/components/flight-check/columns.tsx
git commit -m "feat: SpecItem table columns with checkbox, status, link"
```

---

## Task 8: DataTable généralisée

**Files:**
- Modify: `src/components/flight-check/data-table.tsx`

> Ajoute : `globalFilter` et `columnFilters` contrôlés depuis le parent, et `meta` (isChecked/toggle). La catégorie active et la recherche sont gérées par `checklist.tsx` (Task 9) et passées en props.

- [ ] **Step 1: Réécrire data-table.tsx**

```tsx
// src/components/flight-check/data-table.tsx
"use client";

import {
  type ColumnDef,
  type ColumnFiltersState,
  flexRender,
  getCoreRowModel,
  getFilteredRowModel,
  useReactTable,
} from "@tanstack/react-table";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";

interface DataTableProps<TData, TValue> {
  columns: ColumnDef<TData, TValue>[];
  data: TData[];
  globalFilter: string;
  columnFilters: ColumnFiltersState;
  meta: { isChecked: (id: string) => boolean; toggle: (id: string) => void };
}

export function DataTable<TData, TValue>({
  columns,
  data,
  globalFilter,
  columnFilters,
  meta,
}: DataTableProps<TData, TValue>) {
  const table = useReactTable({
    data,
    columns,
    state: { globalFilter, columnFilters },
    getCoreRowModel: getCoreRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    meta,
  });

  return (
    <div className="overflow-hidden rounded-md border">
      <Table>
        <TableHeader>
          {table.getHeaderGroups().map((headerGroup) => (
            <TableRow key={headerGroup.id}>
              {headerGroup.headers.map((header) => (
                <TableHead key={header.id}>
                  {header.isPlaceholder
                    ? null
                    : flexRender(header.column.columnDef.header, header.getContext())}
                </TableHead>
              ))}
            </TableRow>
          ))}
        </TableHeader>
        <TableBody>
          {table.getRowModel().rows.length ? (
            table.getRowModel().rows.map((row) => (
              <TableRow key={row.id}>
                {row.getVisibleCells().map((cell) => (
                  <TableCell key={cell.id}>
                    {flexRender(cell.column.columnDef.cell, cell.getContext())}
                  </TableCell>
                ))}
              </TableRow>
            ))
          ) : (
            <TableRow>
              <TableCell colSpan={columns.length} className="h-24 text-center">
                No results.
              </TableCell>
            </TableRow>
          )}
        </TableBody>
      </Table>
    </div>
  );
}
```

- [ ] **Step 2: Typecheck**

Run: `bunx tsc --noEmit`
Expected: PASS.

- [ ] **Step 3: Commit**

```bash
git add src/components/flight-check/data-table.tsx
git commit -m "refactor: generalize DataTable with controlled filters and meta"
```

---

## Task 9: Composant parent Checklist

**Files:**
- Create: `src/components/flight-check/checklist.tsx`
- Delete: `src/components/flight-check/checklist-tabs.tsx`

> Détient l'onglet actif, la recherche, le statut filtré et le hook d'état. Calcule par catégorie : nombre de matchs (recherche active) et progression `done/total`.

- [ ] **Step 1: Écrire checklist.tsx**

```tsx
// src/components/flight-check/checklist.tsx
"use client";

import { useMemo, useState } from "react";
import { columns } from "@/components/flight-check/columns";
import { DataTable } from "@/components/flight-check/data-table";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useChecklistState } from "@/hooks/use-checklist-state";
import type { SpecCategory } from "@/lib/spec/types";

function matchesQuery(text: string, q: string) {
  return text.toLowerCase().includes(q);
}

export function Checklist({ categories }: { categories: SpecCategory[] }) {
  const [active, setActive] = useState(categories[0]?.id ?? "");
  const [query, setQuery] = useState("");
  const { isChecked, toggle, reset, progress } = useChecklistState();

  const q = query.trim().toLowerCase();

  // Nombre de matchs par catégorie pour les badges d'onglet.
  const matchCounts = useMemo(() => {
    const map: Record<string, number> = {};
    for (const cat of categories) {
      map[cat.id] = q
        ? cat.items.filter(
            (it) => matchesQuery(it.title, q) || matchesQuery(it.description, q),
          ).length
        : cat.items.length;
    }
    return map;
  }, [categories, q]);

  const allIds = useMemo(
    () => categories.flatMap((c) => c.items.map((i) => i.id)),
    [categories],
  );
  const overall = progress(allIds);

  return (
    <div className="flex flex-col gap-4 p-4">
      <div className="flex flex-wrap items-center gap-3">
        <Input
          placeholder="Search the spec…"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          className="max-w-xs"
        />
        <span className="text-muted-foreground text-sm">
          {overall.done}/{overall.total} done
        </span>
        <Button variant="ghost" size="sm" onClick={reset} className="ml-auto">
          Reset
        </Button>
      </div>

      <Tabs value={active} onValueChange={setActive} className="w-full">
        <TabsList variant="line" className="flex-wrap">
          {categories.map((cat) => {
            const p = progress(cat.items.map((i) => i.id));
            return (
              <TabsTrigger key={cat.id} value={cat.id}>
                {cat.label}
                <span className="text-muted-foreground ml-1.5 text-xs">
                  {q ? matchCounts[cat.id] : `${p.done}/${p.total}`}
                </span>
              </TabsTrigger>
            );
          })}
        </TabsList>

        {categories.map((cat) => (
          <TabsContent key={cat.id} value={cat.id}>
            <p className="text-muted-foreground mb-3 text-sm">{cat.summary}</p>
            <DataTable
              columns={columns}
              data={cat.items}
              globalFilter={q}
              columnFilters={[]}
              meta={{ isChecked, toggle }}
            />
          </TabsContent>
        ))}
      </Tabs>
    </div>
  );
}
```

- [ ] **Step 2: Supprimer le placeholder**

Run: `git rm src/components/flight-check/checklist-tabs.tsx`
Expected: fichier supprimé.

- [ ] **Step 3: Typecheck**

Run: `bunx tsc --noEmit`
Expected: PASS.

- [ ] **Step 4: Commit**

```bash
git add src/components/flight-check/checklist.tsx
git commit -m "feat: Checklist client component with global search and progress"
```

---

## Task 10: Câblage de la page + nettoyage

**Files:**
- Modify: `src/app/flight-checker/page.tsx`

- [ ] **Step 1: Remplacer le contenu de la page (server component)**

Conserver le shell sidebar/header existant, corriger le breadcrumb, supprimer les 3 cartes vides, rendre `<Checklist>` avec la data statique.

```tsx
// src/app/flight-checker/page.tsx
import { AppSidebar } from "@/components/app-sidebar";
import { Checklist } from "@/components/flight-check/checklist";
import { ThemeToggle } from "@/components/theme-toggle";
import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbList,
  BreadcrumbPage,
} from "@/components/ui/breadcrumb";
import { Separator } from "@/components/ui/separator";
import {
  SidebarInset,
  SidebarProvider,
  SidebarTrigger,
} from "@/components/ui/sidebar";
import { CHECKLIST } from "@/lib/spec/checklist-data";

export default function Page() {
  return (
    <SidebarProvider>
      <AppSidebar />
      <SidebarInset>
        <header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
          <div className="flex items-center gap-2 px-4">
            <SidebarTrigger className="-ml-1" />
            <Separator
              orientation="vertical"
              className="mr-2 data-vertical:h-4 data-vertical:self-auto"
            />
            <Breadcrumb>
              <BreadcrumbList>
                <BreadcrumbItem>
                  <BreadcrumbPage>Flight Checker</BreadcrumbPage>
                </BreadcrumbItem>
              </BreadcrumbList>
            </Breadcrumb>
          </div>
          <div className="ml-auto px-4">
            <ThemeToggle />
          </div>
        </header>
        <Checklist categories={CHECKLIST} />
      </SidebarInset>
    </SidebarProvider>
  );
}
```

- [ ] **Step 2: Typecheck**

Run: `bunx tsc --noEmit`
Expected: PASS.

- [ ] **Step 3: Commit**

```bash
git add src/app/flight-checker/page.tsx
git commit -m "feat: wire real checklist data into flight-checker page"
```

---

## Task 11: Vérification finale

- [ ] **Step 1: Tests data**

Run: `bun test`
Expected: PASS (tous les tests d'intégrité verts).

- [ ] **Step 2: Lint**

Run: `bun run lint`
Expected: PASS (Biome, 0 erreur). Corriger si besoin via `bun run format`.

- [ ] **Step 3: Build**

Run: `bun run build`
Expected: PASS — `/flight-checker` compile (server component, pas de `"use client"` sur `page.tsx`).

- [ ] **Step 4: Vérification manuelle (dev server)**

Run: `bun run dev` puis ouvrir `http://localhost:3000/flight-checker`. Vérifier :
- Les 10 onglets s'affichent avec compteurs `0/N`.
- Cocher des cases met à jour la progression globale et celle de l'onglet.
- Recharger la page conserve les cases cochées (localStorage).
- La recherche filtre les lignes et les compteurs d'onglet passent en nombre de matchs.
- Aucun warning d'hydratation dans la console.

- [ ] **Step 5: Commit final éventuel**

```bash
git add -A
git commit -m "chore: lint/format pass for flight-checker checklist"
```

---

## Self-Review (rempli par l'auteur du plan)

- **Spec coverage :** modèle data (T1–T3), régénération hybride (T2–T3), test intégrité (T4), hook localStorage abstrait (T5), shadcn checkbox/badge (T6), TanStack DataTable + colonnes (T6–T8), search globale + progression + badges onglet (T9), server/client split & nettoyage scaffolding (T10), vérif build/lint/manuelle (T11). ✅
- **Filtre statut faceted :** la plomberie est présente (`columnFilters` contrôlé + `filterFn` sur `status`), branchée à vide (`columnFilters={[]}`) au MVP — l'UI de sélection du statut pourra être ajoutée sans refonte.
- **Placeholders :** aucun ; le seul contenu généré à l'exécution (128 items) est produit via MCP en T2, verrouillé par le test T4.
- **Cohérence des types :** `SpecItem`/`SpecCategory`/`SpecCategoryId` définis en T1, réutilisés tels quels partout ; `meta.isChecked/toggle` déclaré en T7, fourni en T8, alimenté en T9.
