`/`` with Tailwind classes, inline-editable via `EditableCell`/`OptionSelect`/`OptionMultiSelect`, no modals for field edits | Phase 11 (2026-06-13) | Phase 14 ports this same shift to leads — `LeadTable.tsx` should drop the shadcn `Table` import entirely in favor of the raw-table pattern, for visual + interaction consistency with `/admin/catalog` |
+| Lead creation/edit via `CreateLeadModal`/`EditLeadModal` dialogs (`LeadForm.tsx`) | Catalog has both: quick-add inline row (`QuickAddRow` in `ServiceTable.tsx`) for new items + inline edit for existing | Phase 11 | **Not explicitly required by CRM-08..12** — `CreateLeadModal` can stay as the "new lead" entry point (CRM-08 only requires inline edit of *existing* lead fields, not a new quick-add row). Adding a `QuickAddRow`-equivalent for leads is a "nice to have" / Claude's-discretion item, not a stated requirement — flagged in Open Questions |
+| Lead `status` shown via hardcoded `STAGE_COLOR` record + shadcn `` | Catalog `category`/`fase`/`tags` shown via `OptionSelect`/`OptionMultiSelect` + `getOptionColor` | Phase 11 | See Pitfall 6 — recommend hybrid: keep semantic stage colors, adopt `OptionSelect`'s interaction model |
+
+**Deprecated/outdated:** None — no library deprecations relevant to this phase. The "old approach" column above refers to pre-Phase-11 UI patterns within this codebase, not external library deprecations.
+
+## Assumptions Log
+
+| # | Claim | Section | Risk if Wrong |
+|---|-------|---------|---------------|
+| A1 | The 6 fields needing inline edit per CRM-08 ("status, next_action, ecc.") should include `name`, `email`, `phone`, `company` in addition to `status`/`next_action` — i.e., "tutti i campi principali" maps to the full set of scalar columns shown in the current `LeadTable.tsx` header row (Nome, Email, Azienda, Stato, Ultimo Contatto, Prossima Azione) | Architecture Patterns Pattern 1/2, Recommended Project Structure | If the user only wants `status`+`next_action` inline-editable and the rest left read-only/modal-edited, the planner would over-scope `updateLeadField`'s `EDITABLE_FIELDS` allowlist — low risk since extra editable fields are additive and harmless, but worth confirming during planning/discuss-phase |
+| A2 | `Ultimo Contatto` (`last_contact_date`) stays read-only/computed (auto-set by `createActivity`, per `lead-service.ts` lines 102-106) and is NOT inline-editable | Recommended Project Structure | Low risk — `last_contact_date` is a derived/audit field; making it editable would conflict with the auto-update-on-activity logic. If wrong, planner adds one more `EditableCell` for a date field (more complex type="date" handling not yet in `EditableCell`'s `type` union) |
+| A3 | For `status` (CRM-08), the existing `STAGE_COLOR` semantic color map should be preserved rather than switched to `getOptionColor()`'s hash-based palette (Pitfall 6) | Pitfalls Pitfall 6, Anti-Patterns | Low/cosmetic risk — if wrong, `won`/`lost` lose their green/red semantic colors in favor of hash-derived pastels; purely visual, easy to adjust in a follow-up |
+| A4 | No new lead "quick-add row" (mirroring catalog's `QuickAddRow`) is required — `CreateLeadModal` remains the lead-creation entry point | State of the Art row 2, Open Questions | Low risk — CRM-08..12 requirements don't mention lead creation UX; if the user actually wants Attio-style quick-add-row creation too, it's an additive enhancement, not a blocker |
+| A5 | New lead-scoped server actions (`updateLeadField`, `addLeadTag`, etc.) should include `requireAdmin()` checks per the catalog-actions convention, even though the pre-existing `leads/actions.ts` functions lack this check | Pitfalls Pitfall 1 | Medium — if the planner skips this, new mutating endpoints would be unauthenticated (matching the pre-existing gap, but inconsistent with Phase 11's stricter convention); retrofitting the *existing* actions is explicitly out of scope unless the user requests it |
+
+**Validation needed:** A1, A3, A4 are UX/scope judgment calls best confirmed during `/gsd-plan-phase` or a quick `/gsd-discuss-phase` pass, since the user has strong opinions about the CRM's look-and-feel ("tutti i miei amici fanno robe fighe e complesse"). A5 is a security-adjacent decision (Pitfall 1 / Security Domain V4) that should be called out explicitly to the user given CLAUDE.md's emphasis on confirming destructive/security-relevant changes — though adding an auth check is additive/protective, not destructive, so it likely doesn't need explicit pre-approval, just documentation in the plan.
+
+## Open Questions
+
+1. **Should `status` use `OptionSelect` (Notion-style, hash colors, user-extensible) or a closed-enum dropdown preserving `STAGE_COLOR`'s semantic colors?**
+ - What we know: `LEAD_STAGES` is a fixed 6-value enum (`contacted | qualified | proposal_sent | negotiating | won | lost`) with meaningful semantic colors already defined in `STAGE_COLOR` (green=won, red=lost, etc.). `OptionSelect` is designed for open-ended, user-extensible pools (category/fase/tags) with hash-based colors.
+ - What's unclear: Whether "Attio/Pipedrive style" for the planner means visual consistency with the tags column (same `OptionSelect` widget, hash colors) or semantic pipeline-stage coloring (Pipedrive's actual stage pills ARE color-coded by stage meaning, often user-configurable per stage — closer to `STAGE_COLOR`).
+ - Recommendation: Use `OptionSelect` for interaction consistency (click-to-open dropdown, same as tags) but pass a fixed `options={LEAD_STAGES}` list (no create-on-the-fly) and either (a) extend `OptionSelect` with an optional color-override map, or (b) render the closed/non-extensible dropdown with `STAGE_COLOR`-based badges directly (simpler, less reuse). Either is a small, low-risk implementation choice — flag for the planner to pick one explicitly rather than leaving ambiguous.
+
+2. **Does CRM-08's "campi principali" include adding a quick-add row for new leads (Attio/Pipedrive "+ Add record" inline row)?**
+ - What we know: CRM-08 requirement text says "La tabella lead supporta inline editing dei campi principali ... senza apertura modale" — this is about *editing*, not *creation*. `CreateLeadModal` already exists for creation.
+ - What's unclear: Whether the Pipedrive/Attio reference implies replacing modal-based creation with an inline quick-add row too (Phase 11 did both for catalog).
+ - Recommendation: Treat as out-of-scope/additive (A4) unless `/gsd-discuss-phase` or the planner surfaces it — CRM-08..12 requirements don't list it, and `CreateLeadModal` is functional. If the planner wants full UX parity with Phase 11's catalog, a `QuickAddRow`-equivalent for leads (name + email + Enter) is a natural, low-effort addition using the same pattern.
+
+3. **Should `LeadDetail.tsx` (the `/admin/leads/[id]` detail page) also show the new lead tags?**
+ - What we know: CRM-09 says "L'utente assegna tag multi-select ai lead" — doesn't specify whether tags are visible/editable only in the table or also on the detail page. The phase description's "compact detail panels" UI hint suggests Attio-style detail views also show tags/properties.
+ - What's unclear: Whether `LeadDetail.tsx` needs an `OptionMultiSelect` for tags too, or if the table is the sole tag-management surface.
+ - Recommendation: Low-cost addition — if `getLeadsWithTags`/`addLeadTag`/etc. are built for the table, surfacing the same `OptionMultiSelect` in `LeadDetail.tsx`'s "Profilo" card costs little extra and improves consistency. Mark as Claude's discretion / nice-to-have within CRM-09's scope.
+
+4. **Pre-existing `requireAdmin()` gap in `src/app/admin/leads/actions.ts` — retrofit existing actions too, or only guard new ones?**
+ - What we know: `createLead`, `updateLead`, `deleteLead`, `logActivity`, `assignQuoteToLead` (all pre-existing) have no `requireAdmin()` check, unlike `src/app/admin/catalog/actions.ts`'s Phase-11-era actions.
+ - What's unclear: Whether this is in scope for Phase 14 at all — none of CRM-08..12 mention auth.
+ - Recommendation: Out of scope for CRM-08..12 directly. New actions (CRM-08/09) should include `requireAdmin()` per A5. Retrofitting the 5 pre-existing actions is a separate, small security-hardening task that could be mentioned to the user as a "spotted while researching" item, but shouldn't block or expand this phase's plan unless the user asks.
+
+## Environment Availability
+
+Skipped — this phase is purely code/UI changes against the existing Next.js app and already-provisioned Postgres database (no new external tools, services, or runtimes required). All necessary npm packages are already installed (verified above).
+
+## Security Domain
+
+### Applicable ASVS Categories
+
+| ASVS Category | Applies | Standard Control |
+|---------------|---------|-------------------|
+| V2 Authentication | Indirect | `/admin/*` routes protected by Auth.js v4 session (per CLAUDE.md architecture constraint #4) — new server actions should call `requireAdmin()` (see Pitfall 1 / A5) |
+| V3 Session Management | No | No session-handling changes in this phase |
+| V4 Access Control | Yes | All new mutating server actions (`updateLeadField`, `addLeadTag`, `removeLeadTag`, `renameLeadTag`) must be reachable only from authenticated admin context — follow `requireAdmin()` pattern from `src/app/admin/catalog/actions.ts` |
+| V5 Input Validation | Yes | `updateLeadField`'s per-field validation (Pattern 2) — especially `status` must be constrained to `LEAD_STAGES` enum values regardless of client-side `OptionSelect` configuration (defense in depth, Pitfall 2). Tag names should be trimmed/non-empty (mirrors `addServiceOption`) |
+| V6 Cryptography | No | No crypto/secrets involved in this phase |
+
+### Known Threat Patterns for this stack
+
+| Pattern | STRIDE | Standard Mitigation |
+|---------|--------|----------------------|
+| Unauthenticated server action invocation (Next.js Server Actions are callable directly if not guarded) | Elevation of Privilege | `requireAdmin()` session check at the top of every new mutating action (Pattern 2/3 examples include this) |
+| Arbitrary `status` value injection via `OptionSelect`'s create-on-the-fly UX if misapplied to the closed `status` enum | Tampering | Server-side allowlist check against `LEAD_STAGES` in `updateLeadField` (Pattern 2), independent of client UI configuration (Pitfall 2) |
+| SQL injection via raw string interpolation | Tampering | N/A — Drizzle ORM parameterized queries used throughout; no raw SQL introduced by this phase |
+
+## Sources
+
+### Primary (HIGH confidence)
+- `/Users/simonecavalli/Vault/IAMCAVALLI/src/components/admin/catalog/ServiceTable.tsx` — Phase 11 database-view table pattern (shipped, code-complete)
+- `/Users/simonecavalli/Vault/IAMCAVALLI/src/components/ui/editable-cell.tsx` — inline-edit primitive
+- `/Users/simonecavalli/Vault/IAMCAVALLI/src/components/ui/option-select.tsx` — single-select primitive
+- `/Users/simonecavalli/Vault/IAMCAVALLI/src/components/ui/option-multi-select.tsx` — multi-select primitive
+- `/Users/simonecavalli/Vault/IAMCAVALLI/src/components/ui/option-colors.ts` — color derivation
+- `/Users/simonecavalli/Vault/IAMCAVALLI/src/app/admin/catalog/actions.ts` — server action patterns (updateServiceField, addServiceOption/removeServiceOption/renameServiceOption, requireAdmin)
+- `/Users/simonecavalli/Vault/IAMCAVALLI/src/app/admin/catalog/CatalogSearch.tsx` — client-side instant filter pattern
+- `/Users/simonecavalli/Vault/IAMCAVALLI/src/lib/admin-queries.ts` (lines 355-430) — getAllServices/getCatalogFieldOptions tags-join pattern
+- `/Users/simonecavalli/Vault/IAMCAVALLI/src/db/schema.ts` — leads, activities, reminders, tags, quotes table definitions
+- `/Users/simonecavalli/Vault/IAMCAVALLI/src/db/migrations/0006_add_tags_table.sql` — confirms `tags` table already deployed, polymorphic, no migration needed for CRM-09
+- `/Users/simonecavalli/Vault/IAMCAVALLI/src/components/admin/leads/LeadTable.tsx`, `LeadForm.tsx`, `SendQuoteModal.tsx`, `LeadDetail.tsx`, `LogActivityModal.tsx` — current CRM code under fix/redesign
+- `/Users/simonecavalli/Vault/IAMCAVALLI/src/components/admin/dashboard/FollowUpWidget.tsx` — CRM-10 target
+- `/Users/simonecavalli/Vault/IAMCAVALLI/src/lib/lead-validators.ts`, `lead-service.ts` — Zod schemas and query/mutation helpers
+- npm registry — `npm view next/react-hook-form/zod/@hookform/resolvers version` [VERIFIED: npm registry, 2026-06-13]
+
+### Secondary (MEDIUM confidence)
+- None used — all findings verified directly against the codebase (HIGH confidence, codebase is ground truth for an internal refactor/redesign phase)
+
+### Tertiary (LOW confidence)
+- None — no external/ecosystem research was needed since this phase reuses 100% internal, already-shipped primitives
+
+## Metadata
+
+**Confidence breakdown:**
+- Standard Stack: HIGH — zero new dependencies; all versions verified against npm registry and the project's own `package.json`
+- Architecture: HIGH — directly ported from Phase 11's shipped, verified, lint-passing code (`ServiceTable.tsx`, catalog actions, admin-queries.ts)
+- Pitfalls: HIGH — all six pitfalls derived from direct code reading of the actual files this phase will modify, not speculation
+
+**Research date:** 2026-06-13
+**Valid until:** 30 days (stable internal codebase pattern; revisit if Phase 11 patterns change before Phase 14 executes, or if Phase 12/13 modify shared primitives)
diff --git a/.planning/phases/14-crm-attio-style-fix/14-UI-SPEC.md b/.planning/phases/14-crm-attio-style-fix/14-UI-SPEC.md
new file mode 100644
index 0000000..df5b6f6
--- /dev/null
+++ b/.planning/phases/14-crm-attio-style-fix/14-UI-SPEC.md
@@ -0,0 +1,442 @@
+---
+phase: 14
+slug: crm-attio-style-fix
+status: approved
+shadcn_initialized: true
+preset: "default (neutral base color)"
+created: "2026-06-13"
+---
+
+# Phase 14 — UI Design Contract
+
+> Visual and interaction contract for CRM table redesign (Attio-style) and bug fixes. Generated by gsd-ui-researcher, verified by gsd-ui-checker.
+
+---
+
+## Design System
+
+| Property | Value |
+|----------|-------|
+| Tool | shadcn/ui |
+| Preset | default (neutral, baseColor: neutral) |
+| Component library | radix-ui (via shadcn) |
+| Icon library | lucide-react |
+| Font | Geist Sans (system fallback) |
+
+**Source:** `components.json` and `src/app/globals.css` (Phase 11, confirmed 2026-06-13)
+
+---
+
+## Spacing Scale
+
+Declared values (multiples of 4):
+
+| Token | Value | Usage |
+|-------|-------|-------|
+| xs | 4px | Icon gaps, inline padding |
+| sm | 8px | Compact element spacing |
+| md | 16px | Default element spacing (py-2 px-3 ≈ 8px/12px) |
+| lg | 24px | Section padding |
+| xl | 32px | Layout gaps |
+| 2xl | 48px | Major section breaks |
+| 3xl | 64px | Page-level spacing |
+
+**Exceptions:** None — standard 4px-multiple scale
+
+**Implementation:** Tailwind utility classes (py-2 px-3 for table cells, gap-1.5 for dropdown spacing, etc.). Reuse `EditableCell`, `OptionSelect`, `OptionMultiSelect` internal spacing without modification.
+
+---
+
+## Typography
+
+| Role | Size | Weight | Line Height |
+|------|------|--------|-------------|
+| Body | 16px (default) | 400 (regular) | 1.6 |
+| Label / Small | 14px | 400 (regular) | 1.5 |
+| XSmall / Badge | 12px | 500 (medium, via `font-medium`) | 1.4 |
+| Heading (page) | 20px | 600 (semibold, via `font-semibold`) | 1.2 |
+
+**Source:** Tailwind defaults + Phase 11 component internals (EditableCell: `text-sm`, OptionSelect/OptionMultiSelect: `text-xs badge, text-sm button`)
+
+**Details:**
+- Body: 16px, weight 400, line-height 1.6 (per `globals.css` body)
+- Table cell labels (lead name, email, status): `text-sm` = 14px, weight 400
+- Badge labels (status stage, tags): `text-xs` = 12px, weight 500 (`font-medium`)
+- Placeholder text: `text-[#71717a]` (muted-foreground)
+- Error messages: `text-xs text-red-600`
+
+---
+
+## Color
+
+| Role | Value | Usage |
+|------|-------|-------|
+| Dominant (60%) | #ffffff (white) | Page background, card backgrounds |
+| Secondary (30%) | #f4f4f5 (neutral-100) | Hover states, subtle section backgrounds, table row hover |
+| Accent (10%) | #1A463C (primary, dark green) | Button text, icon colors, ring/focus, checkbox accent |
+| Destructive | #ef4444 (red) | Error messages, delete/dangerous action badges |
+
+**Semantic colors (for lead status badges):**
+- Contacted: blue (bg-blue-100 text-blue-800)
+- Qualified: purple (bg-purple-100 text-purple-800)
+- Proposal Sent: amber (bg-amber-100 text-amber-800)
+- Negotiating: orange (bg-orange-100 text-orange-800)
+- Won: green (bg-green-100 text-green-800)
+- Lost: red (bg-red-100 text-red-800)
+
+**Accent reserved for:**
+- Form focus rings (input/textarea `ring-primary`)
+- Checkbox `accent-[#1A463C]`
+- Icon colors in OptionSelect/OptionMultiSelect (checkmarks, pencil, plus, X)
+- Primary button text
+- Selected state indicators (table row actions)
+
+**Notes:**
+- Table row hover: `hover:bg-[#f9f9f9]` (Phase 11 ServiceTable pattern)
+- Border color: `border-[#e5e7eb]`
+- Input ring (unfocused): `ring-1 ring-primary` (#1A463C, 1px border)
+- Error ring: `ring-2 ring-red-500` (2px on error state)
+- OptionSelect/OptionMultiSelect dropdown: white background, 1.5px shadow, 1px border
+
+---
+
+## Copywriting Contract
+
+| Element | Copy | Context |
+|---------|------|---------|
+| Primary CTA | "Salva" (implicit, on Enter key) | Inline cell edit commit on Enter or blur |
+| CTA — Add Lead | "Nuovo Lead" | CreateLeadModal button (unchanged from Phase 10) |
+| CTA — Tag Create | "Crea «{tag_name}»" | OptionMultiSelect dropdown, create-on-type UX |
+| CTA — Status Create | N/A (fixed enum) | Status uses OptionSelect but with fixed LEAD_STAGES, no create-on-the-fly |
+| Empty state heading | "Nessun lead trovato" | LeadTable when leads.length === 0 |
+| Empty state body | (no secondary message) | Single-line empty state, matching ServiceTable pattern |
+| Required field error | "Campo richiesto" | EditableCell when required field is empty on blur |
+| Invalid status | "Stato non valido" | updateLeadField server action validation |
+| Activity log | "Attività registrata" (feedback on LogActivityModal submit) | Unchanged from Phase 10 |
+| FollowUpWidget — (CRM-10, i18n) | "Richiedi Follow-up" (heading), "lead/lead" (plural), "da contattare" (to contact), "Contatta" (button), "Vedi Tutto" (view all), "Tutti i lead sono aggiornati!" (no follow-ups) | Dashboard widget, fully Italian |
+
+**Destructive actions:**
+- None explicit in Phase 14 scope (lead deletion already exists, not modified by CRM-08..12)
+- If a "delete lead" row action is added later, copy: "Elimina lead: Sei sicuro? Questa azione non può essere annullata."
+
+---
+
+## Component Inventory
+
+### Reused from Phase 11 (no new imports needed)
+
+| Component | Path | Purpose in Phase 14 |
+|-----------|------|-------------------|
+| `EditableCell` | `src/components/ui/editable-cell.tsx` | Inline edit for lead name, email, phone, company, next_action |
+| `OptionSelect` | `src/components/ui/option-select.tsx` | Lead status dropdown (fixed enum: LEAD_STAGES, 6 values, no create-on-the-fly) |
+| `OptionMultiSelect` | `src/components/ui/option-multi-select.tsx` | Lead tags multi-select (CRM-09: create-on-the-fly enabled) |
+| `getOptionColor` | `src/components/ui/option-colors.ts` | Badge color generation for tags (reused directly by OptionMultiSelect) |
+| `Badge` | `src/components/ui/badge` | Display lead status & tags badges |
+| `Input` | `src/components/ui/input` | Inline edit inputs, search box in new LeadsSearch component |
+| `Button` | `src/components/ui/button` | Detail/action buttons (unchanged from Phase 10) |
+
+### To Create / Modify
+
+| Component | Path | Change | Scope |
+|-----------|------|--------|-------|
+| `LeadTable` | `src/components/admin/leads/LeadTable.tsx` | REWRITE — replace shadcn `` wrapper with raw ``, add EditableCell + OptionSelect/OptionMultiSelect per row | CRM-08, CRM-09 |
+| `LeadsSearch` | `src/app/admin/leads/LeadsSearch.tsx` | NEW — client-side search component (useMemo filter on name/email/company/status/tags), mirrors CatalogSearch.tsx | CRM-08 UX (instant filter) |
+| `FollowUpWidget` | `src/components/admin/dashboard/FollowUpWidget.tsx` | FIX (CRM-10) — translate 6 hardcoded English strings to Italian | CRM-10 |
+| `LeadForm` | `src/components/admin/leads/LeadForm.tsx` | FIX (CRM-11) — remove `useForm`, use Zod-inferred CreateLeadInput/UpdateLeadInput types, remove `as any` casts | CRM-11 |
+| `SendQuoteModal` | `src/components/admin/leads/SendQuoteModal.tsx` | FIX (CRM-12) — remove unreachable `if (tab === "new")` branch from onSubmit | CRM-12 |
+
+---
+
+## Table Layout — LeadTable (CRM-08, CRM-09)
+
+### Visual Pattern (from Phase 11 ServiceTable)
+
+```
+┌─ Lead Table ─────────────────────────────────────────────────────────────────┐
+│ Nome │ Email │ Stato │ Prossima Azione │ Tag │
+├───────────────────┼─────────────────┼───────────┼───────────────────┼────────┤
+│ [EditableCell] │ [EditableCell] │ [Status] │ [EditableCell] │ [Tags] │
+│ hover: bg-#f9f9f9 │ │ │ │ │
+│ border-b #e5e7eb │ │ │ │ │
+├───────────────────┼─────────────────┼───────────┼───────────────────┼────────┤
+│ ... │ ... │ ... │ ... │ ... │
+└───────────────────┴─────────────────┴───────────┴───────────────────┴────────┘
+```
+
+### Column Structure
+
+| Column | Type | Editable | Required | Cell Component | Min-Width | Notes |
+|--------|------|----------|----------|----------------|-----------|-------|
+| Nome | text | Yes | Yes | EditableCell(type="text") | 160px | Lead name, bold, linked to detail page |
+| Email | text | Yes | No | EditableCell(type="text") | 160px | Nullable, show "—" if empty |
+| Telefono | text | Yes | No | EditableCell(type="text") | 140px | Nullable, show "—" if empty |
+| Azienda | text | Yes | No | EditableCell(type="text") | 140px | Nullable, show "—" if empty |
+| Stato | enum | Yes | Yes | OptionSelect(options=LEAD_STAGES, no create) | 120px | 6 fixed values: contacted, qualified, proposal_sent, negotiating, won, lost |
+| Prossima Azione | text | Yes | No | EditableCell(type="text") | 180px | Nullable, show "—" if empty |
+| Tag | multi-select | Yes | No | OptionMultiSelect(create-on-the-fly) | 180px | CRM-09: lead tags, polymorphic table (entity_type="leads") |
+| Azioni | link | No | N/A | Link → `/admin/leads/[id]` | 80px | "Dettagli" button, unchanged from Phase 10 |
+
+### Row States
+
+- **Normal:** white background, border-b #e5e7eb, text #1a1a1a
+- **Hover:** bg-#f9f9f9, smooth 150ms transition
+- **Cell edit (active):** EditableCell in input mode — ring-1 ring-primary, h-8 for text inputs
+- **Cell error:** ring-2 ring-red-500, error message text-xs text-red-600 below cell
+- **Row with error:** shows error row spanning all columns, text-xs text-red-600
+
+### Search/Filter (CRM-08 UX — "Attio-style instant")
+
+- New component: `LeadsSearch` (client-side wrapper)
+- Input: search box at top of page (before table)
+- Behavior: `useMemo` filter on keystroke, no server round-trip
+- Filter fields: name, email, company, status label, tag names (all case-insensitive, includes-search)
+- Display: filtered leads in table below; if no results, empty state "Nessun lead trovato"
+- Placeholder: "Cerca lead..." (consistent with CatalogSearch style)
+
+---
+
+## Bug Fixes
+
+### CRM-10: FollowUpWidget Internationalization
+
+**Current state:** 6 hardcoded English strings in JSX.
+
+**Strings to translate:**
+1. "Require Follow-up" → "Richiedi Follow-up"
+2. "lead" / "leads" (noun, "X lead to contact") → "lead" / "lead" (Italian stays same, but handle plural: "X lead da contattare" / "X lead da contattare")
+3. "to contact" → "da contattare"
+4. "Contact" (button label) → "Contatta"
+5. "View All" → "Vedi Tutto"
+6. "All leads are up to date!" → "Tutti i lead sono aggiornati!"
+
+**Style:** Tone matches existing `LeadDetail.tsx`/`LeadTable.tsx` (semi-formal, second-person friendly).
+
+**Location:** `src/components/admin/dashboard/FollowUpWidget.tsx` JSX only (no data-layer strings confirmed).
+
+### CRM-11: LeadForm Type Safety (react-hook-form)
+
+**Current pattern:** `useForm()`, `field.value as any`, `data: any` in onSubmit.
+
+**Fix pattern:**
+- Import `CreateLeadInput` / `UpdateLeadInput` from `src/lib/lead-validators.ts` (Zod-inferred types)
+- Change `useForm()` → `useForm()` (or UpdateLeadInput for edit modal)
+- Replace `as any` casts with type-safe narrowing at schema boundary (e.g., `lead.status as CreateLeadInput["status"]`)
+- Remove blanket `data: any` in `onSubmit` callback signature
+- Nullable fields: use `field.value || ""` pattern (already established elsewhere in the app)
+
+**No schema changes required** — existing `createLeadSchema`/`updateLeadSchema` are correct; this is purely a type-safety fix in the component.
+
+### CRM-12: SendQuoteModal Unreachable Code
+
+**Current problem:** `if (tab === "new")` branch inside `onSubmit` (form submit handler) is unreachable because the "new" tab has its own standalone button with `onClick` handler that does a redirect, never submitting the form.
+
+**Fix:**
+- Remove the unreachable `if (tab === "new")` branch from `onSubmit` handler
+- Optionally simplify `onSubmit` to only handle the "existing quote" path (tab === "existing")
+- Confirm `generate_new` field in schema/defaultValues is also dead code (currently always false, unused in action)
+
+**Scope:** Code cleanup only; no UX change to "new" tab button (still does redirect as-is).
+
+---
+
+## Server Actions (Backend Contract)
+
+### New Actions Required (CRM-08, CRM-09)
+
+**File:** `src/app/admin/leads/actions.ts` (extend existing)
+
+```typescript
+// Pattern 1: Field update with per-field validation
+export async function updateLeadField(
+ leadId: string,
+ fieldName: EditableField,
+ value: string
+): Promise {
+ // Validates fieldName against EDITABLE_FIELDS allowlist
+ // Applies field-specific validation (name required, status in LEAD_STAGES, etc.)
+ // Calls db.update() via Drizzle ORM
+ // Calls revalidatePath("/admin/leads") and revalidatePath(`/admin/leads/${leadId}`)
+}
+
+// Pattern 2: Tag CRUD (polymorphic tags table)
+export async function addLeadTag(leadId: string, value: string): Promise
+export async function removeLeadTag(leadId: string, value: string): Promise
+export async function renameLeadTag(oldValue: string, newValue: string): Promise
+// All three call requireAdmin() at the top
+// Use tags table with entity_type="leads", entity_id=leadId
+// Call revalidatePath("/admin/leads") after each mutation
+```
+
+**Auth guard:** All new actions must include `await requireAdmin()` check (per Phase 11 convention in `catalog/actions.ts`).
+
+### Query Functions (CRM-08 data fetch)
+
+**File:** `src/lib/admin-queries.ts` (extend) or new `src/lib/lead-service.ts`
+
+```typescript
+export type LeadWithTags = Lead & { tags: string[] };
+export type LeadFieldOptions = { status: string[]; tags: string[] };
+
+export async function getLeadsWithTags(): Promise
+ // Left-join leads + tags table (entity_type="leads")
+ // Map-reduce to attach tags array per lead
+ // Return sorted by updated_at DESC, tags ASC
+
+export async function getLeadFieldOptions(): Promise
+ // Return status: LEAD_STAGES (fixed enum)
+ // Return tags: distinct tag names for entity_type="leads", sorted by locale "it"
+```
+
+---
+
+## Interaction Flows
+
+### Flow 1: Inline Edit Lead Field (CRM-08)
+
+```
+User clicks cell
+ ↓
+EditableCell → input mode (focus + select text)
+ ↓
+User types + presses Enter (or loses focus)
+ ↓
+EditableCell.commit() → calls onSave callback
+ ↓
+Callback runs server action updateLeadField(leadId, fieldName, value)
+ ↓
+Server action validates, updates DB, revalidates paths
+ ↓
+Router.refresh() re-renders page with new data
+ ↓
+EditableCell exits edit mode, shows new value
+```
+
+**Error handling:** If server action throws, error message displays below cell (text-xs text-red-600). User can retry by clicking cell again.
+
+### Flow 2: Multi-Select Lead Tags (CRM-09)
+
+```
+User clicks tag cell
+ ↓
+OptionMultiSelect → dropdown opens, shows pill list + search input
+ ↓
+User types tag name (or selects existing)
+ ↓
+If new: shows "Crea «{name}»" button
+ ↓
+User presses Enter or clicks button
+ ↓
+Calls onAdd callback → server action addLeadTag(leadId, tagName)
+ ↓
+Server action inserts into tags table (entity_type="leads")
+ ↓
+Router.refresh() + revalidate paths
+ ↓
+OptionMultiSelect pill list updates with new tag (+ remove button)
+```
+
+**Remove tag:** User clicks × button on pill → calls onRemove callback → removeLeadTag action.
+
+**Rename tag:** User clicks pencil icon on option → inline input → commitRename → renameLeadTag action (propagates to all leads with this tag).
+
+### Flow 3: Client-Side Search (CRM-08 UX)
+
+```
+User types in search box (LeadsSearch component)
+ ↓
+useState(query) triggers useMemo filter
+ ↓
+Filter includes: name, email, company, status, tags (all case-insensitive)
+ ↓
+Filtered leads array updates
+ ↓
+LeadTable re-renders with filtered rows
+ ↓
+No server call, instant response
+```
+
+**Clear search:** User clears input → all leads shown again.
+
+**Empty results:** LeadTable displays "Nessun lead trovato" empty state.
+
+---
+
+## Registry Safety
+
+| Registry | Blocks Used | Status |
+|----------|-------------|--------|
+| shadcn official | Badge, Input, Button, Table (deprecated in new LeadTable), Textarea, Table import removal | no vetting required |
+| third-party | none | N/A |
+
+**Notes:**
+- Phase 14 removes the shadcn `Table` wrapper from LeadTable (switches to raw `` for consistency with Phase 11 ServiceTable pattern)
+- No new third-party registries introduced
+- All components reused from Phase 11 or existing shadcn official library
+
+---
+
+## Checker Sign-Off
+
+- [x] Dimension 1 Copywriting: PASS
+- [x] Dimension 2 Visuals: FLAG — no explicit focal point declared for the LeadsSearch + LeadTable screen (non-blocking; recommend "Primary focal point: search input bar at top; secondary: lead name column (bold, linked to detail)")
+- [x] Dimension 3 Color: PASS
+- [x] Dimension 4 Typography: PASS
+- [x] Dimension 5 Spacing: PASS
+- [x] Dimension 6 Registry Safety: PASS
+
+**Approval:** APPROVED (2026-06-14) — 5/6 PASS, 1 non-blocking FLAG (Visuals/focal point)
+
+---
+
+## Design Decisions & Rationale
+
+### Decision 1: Status Field — OptionSelect with Fixed LEAD_STAGES (not extensible)
+
+**What:** Lead `status` uses `OptionSelect` component but with `options={LEAD_STAGES}` (6 fixed values: contacted, qualified, proposal_sent, negotiating, won, lost) and **no create-on-the-fly** UX.
+
+**Why:** `status` is a closed enum, not an open-ended Notion-style property like tags. Allowing users to create arbitrary status values would break pipeline logic, STAGE_COLOR lookups, and LEAD_STAGES-based filters elsewhere (LeadDetail.tsx, FollowUpWidget, queries). OptionSelect is reused for interaction consistency with the tags column (click-to-open dropdown, keyboard nav, rename support), but the input validation (Pattern 2) enforces that only LEAD_STAGES values are accepted server-side, regardless of client-side UI.
+
+**Semantic colors preserved:** Badge colors for status still use STAGE_COLOR map (blue/purple/amber/orange/green/red), not `getOptionColor()`'s hash-based pastels, to maintain pipeline-stage meaning.
+
+### Decision 2: LeadTable Layout — Raw `` not shadcn `` Wrapper
+
+**What:** Phase 14 LeadTable uses raw HTML `//` elements with Tailwind classes, exactly like Phase 11 ServiceTable.
+
+**Why:**
+1. **Visual consistency:** Phase 11 established the "database-view" pattern (ServiceTable) as the standard for inline-editable Attio-style tables; replicating it for leads ensures visual/interaction coherence.
+2. **Component reuse:** EditableCell/OptionSelect/OptionMultiSelect are already styled for raw-table cells (py-2 px-3, min-w-*, inline spacing); wrapping them in shadcn `` wrapper components adds unnecessary nesting.
+3. **Simplicity:** Raw table is easier to customize row-by-row (per-row error messages, inline edit state transitions, per-row hover) than the shadcn wrapper's structured slots.
+
+### Decision 3: No Lead Quick-Add Row (CRM-08 scope)
+
+**What:** Phase 14 does NOT include a `QuickAddRow` equivalent for leads (Phase 11 added it for services).
+
+**Why:** CRM-08 requirement says "inline editing dei campi principali" (existing leads) + CRM-09 says "assegna tag" (existing leads). Neither mentions lead *creation* UX. The existing `CreateLeadModal` is the lead-creation entry point and remains unchanged. Adding a quick-add row would be a nice-to-have (Attio/Pipedrive parity), but it is explicitly **out of scope** for Phase 14 per CRM-08..12.
+
+**If user asks for it:** Flag as a follow-up enhancement, separate from Phase 14 planning.
+
+### Decision 4: LeadDetail.tsx Tags Display (Claude's Discretion)
+
+**What:** Whether `LeadDetail.tsx` (the `/admin/leads/[id]` detail page) should surface the new lead tags in a read-only or editable section.
+
+**Current assumption:** Tags are managed and visible in the table only (CRM-09 doesn't explicitly require detail-page display).
+
+**Recommendation:** Low-effort addition — if planner agrees, add an `OptionMultiSelect` for tags in LeadDetail's "Profilo" card (same data layer as table). If planner says no, leave LeadDetail as-is.
+
+### Decision 5: FollowUpWidget Plural Handling (CRM-10)
+
+**What:** The English phrase "X lead to contact" vs. "X leads to contact" — Italian equivalent: "X lead da contattare" (both singular and plural use "lead").
+
+**Implementation:** Use template string: `{count} lead da contattare` (always "lead", never "leads" in Italian, since "lead" is a loanword with Italian plural also "lead").
+
+---
+
+## Open Questions Resolved
+
+| Question | Resolution | Rationale |
+|----------|-----------|-----------|
+| Should status use OptionSelect or closed-enum dropdown? | OptionSelect + fixed LEAD_STAGES, no create-on-the-fly | Consistency with table interaction pattern; server-side validation ensures only valid statuses enter DB |
+| Include quick-add row for new leads? | No — out of scope for CRM-08..12 | CRM requirements focus on editing existing leads; CreateLeadModal unchanged |
+| Surface tags in LeadDetail.tsx? | Claude's discretion (recommend yes, low-effort) | Add OptionMultiSelect in detail card if planner approves; keep table as primary interface if not |
+| Retrofit pre-existing lead actions with requireAdmin()? | No — out of scope for Phase 14 | New actions (CRM-08/09) include requireAdmin(); pre-existing gap noted for user awareness, not in this phase's plan |
+
| |