--- phase: 12 slug: offer-editor-tier-tag-prezzo-pubblico status: draft shadcn_initialized: true preset: default created: 2026-06-14 --- # Phase 12 — UI Design Contract ## Offer Editor — Tier A/B/C, Tag & Prezzo Pubblico > Visual and interaction contract for the Offer Editor: list page + detail editor with 3-tier checkbox matrix, multi-dimensional tags, and structured transformation promise. Generated by gsd-ui-researcher, verified by gsd-ui-checker. --- ## Design System | Property | Value | |----------|-------| | Tool | shadcn/ui | | Preset | default (existing, Phase 11 forward) | | Component library | radix | | Icon library | Lucide React | | Font | Geist Sans | | Design direction | ClickUp/Pipedrive minimal flat style (per DESIGN-SYSTEM.md) | **Notes:** Phase 12 builds on Phase 11's established database-view UX (inline edit, tag multi-select, EditableCell). All colors, spacing, typography inherited from Phase 11 and project globals. No new design system tokens introduced. --- ## Spacing Scale Multiples of 4px per project standard: | Token | Value | Usage | |-------|-------|-------| | xs | 4px | Icon gaps, inline padding | | sm | 8px | Compact element spacing, tag chip gaps | | md | 16px | Default cell/control spacing, table row padding | | lg | 24px | Section padding, card margin, tag dimension spacing | | xl | 32px | Layout gaps between sections | | 2xl | 48px | Major section breaks | | 3xl | 64px | Page-level spacing | Exceptions: None --- ## Typography Phase 12 reuses Phase 11 typography (no new sizes introduced). Two weights only (400 regular, 600 bold): | Role | Size | Weight | Line Height | |------|------|--------|-------------| | Body | 14px | 400 | 1.5 | | Label | 12px | 400 | 1.5 | | Heading (h3) | 16px | 600 | 1.2 | | Heading (h2) | 20px | 600 | 1.2 | --- ## Color Phase 12 uses Phase 11 color contract (invariant brand tokens): | Role | Value | Usage | |------|-------|-------| | Dominant (60%) | #ffffff | Page/card backgrounds, editor surfaces | | Secondary (30%) | #f9f9f9 | Muted backgrounds, table headers, category filter chips (inactive) | | Accent (10%) | #1A463C | Primary CTAs ("Salva Offerta"), focus ring, active/selected state, active filter chips | | Accent secondary | #DEF168 | Status indicators (archived badge), active offer state | | Destructive | #dc2626 | Archive/delete actions only | | Muted text | #71717a | Placeholders, metadata, disabled states, inactive labels | | Border | #e5e7eb | Cell/block dividers, table borders | **Accent reserved for:** - "Salva Offerta" primary CTA button - Focus ring on EditableCell, TagMultiSelect, and input fields - Active state indicator (category filter chip, active tags) - Selection highlight in checkbox matrix --- ## Components & Interaction Patterns All patterns inherited from Phase 11 (EditableCell, TagMultiSelect, database-view table) and extended for offer composition and tier management. ### 1. Offer List Page (`/admin/offers`) **Purpose:** Display all offers (active + optionally archived) in a filterable card grid. Admin can create new offers, filter by category, and toggle archive view. **Layout Structure:** ``` ┌──────────────────────────────────────────────────────────────┐ │ Offerte [+ Nuova Offerta] │ ├──────────────────────────────────────────────────────────────┤ │ Filter row: │ │ [Tutti] [Entry Offer] [Signature Offer] [Retainer Offer] │ │ Mostra offerte archiviate: [toggle off] │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Offerta 1 │ │ Offerta 2 │ │ Offerta 3 │ │ │ │ Breve desc │ │ Breve desc │ │ Breve desc │ │ │ │ [Signature] │ │ [Entry] │ │ [Retainer] │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ [card 4] [card 5] [card 6] │ └──────────────────────────────────────────────────────────────┘ ``` #### A. Page Header - **Title:** "Offerte" (h2, 20px/600, black) - **Primary CTA:** "+ Nuova Offerta" button (bg #1A463C, text white, right-aligned) #### B. Filter Row - **Category filter chips:** Buttons for "Tutti", "Entry Offer", "Signature Offer", "Retainer Offer" - Inactive: border #e5e7eb, text #71717a, bg transparent - Active: border #1A463C, text #1A463C, bg transparent (or solid #1A463C text white — planner decides) - Spacing between chips: 8px (sm) - **Archive toggle:** "Mostra offerte archiviate" text label + checkbox or toggle switch - Position: right side of filter row OR below filter chips on tablet/mobile - Label color: #71717a - Toggle: standard checkbox or `` component from shadcn #### C. Offer Card Grid - **Grid layout:** 3 columns on desktop (≥1024px), 2 on tablet (768–1023px), 1 on mobile (<768px) - **Card dimensions:** ~280px width, responsive height (min 140px) - **Card structure:** ``` ┌───────────────────────────┐ │ Offerta Name (editable?) │ ← h3/16px/600 │ │ │ Breve descrizione │ ← body/14px/400, 2 lines max, ellipsis │ │ │ [Categoria Chip] │ ← single category badge │ │ │ [archived badge] [edit] │ ← conditional └───────────────────────────┘ ``` - **Card styling:** - Background: white (#ffffff) - Border: #e5e7eb, 1px - Padding: 16px (md) - Border-radius: 8px - Hover: `cursor-pointer`, subtle shadow (box-shadow: 0 4px 12px rgba(0,0,0,0.08)) - **Offer name:** Clickable, links to detail editor - **Short description:** Body text, truncated at 2 lines - **Category badge:** Single chip (Entry/Signature/Retainer), color-coded (TBD by planner; suggest brand palette shades) - **Archived badge:** Red text "Archiviata" (only if offer is archived) - **Edit icon:** Optional Lucide pencil icon (top-right corner), links to detail editor #### D. Empty State - When no offers exist: centered section with - Icon: Lucide briefcase or package (48px, #71717a) - Heading: "Nessuna offerta" (h3) - Copy: "Inizia creando la tua prima offerta" (body, #71717a) - CTA: "+ Nuova Offerta" button --- ### 2. Offer Editor (Detail Page) (`/admin/offers/[id]/edit` or modal) **Purpose:** Create/edit a complete offer with 3 tiers (A/B/C), service composition via checkbox matrix, tag system, and transformation promise. **Page Structure:** ``` ┌───────────────────────────────────────────────────────────────┐ │ ← Indietro | Modifica Offerta │ ├───────────────────────────────────────────────────────────────┤ │ │ │ [Offerta Name — editable inline] [Category] [Ticket] │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Tags (4 dimensions): │ │ │ │ Categoria: [Signature Offer] │ │ │ │ Ticket: [Low Ticket / Mid Ticket / High Ticket] │ │ │ │ Tipo: [Audit] [Done For You] [+ Crea Tipo] │ │ │ │ Obiettivo: [Primo Acquisto] [Up Scala] [+ Crea Obj] │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ [Divider] │ │ │ │ SERVIZI INCLUSI │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Servizio Prezzo │ A │ B │ C │ │ │ ├─────────────────────────────────────────────────────────┤ │ │ │ Service 1 €100 │ ☑ │ ☐ │ ☐ │ │ │ │ │ Service 2 €250 │ ☐ │ ☑ │ ☑ │ │ │ │ │ ... ... │...│...│... │ │ │ │ ├─────────────────────────────────────────────────────────┤ │ │ │ Totale Servizi — │€350│€500│€250│ │ │ │ ├─────────────────────────────────────────────────────────┤ │ │ │ Prezzo Pubblico — │€499│€699│€349│ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ [Divider] │ │ │ │ PROMESSA DI TRASFORMAZIONE │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Aiuto: [Client Ideal — editable inline] │ │ │ │ A ottenere: [Result — editable inline] │ │ │ │ In: [Estimated Time — editable inline] │ │ │ │ Senza: [Pain Point — editable inline] │ │ │ │ Grazie a: [Proprietary Method — editable inline] │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ [Salva Offerta] [Annulla] [Archivia] │ └───────────────────────────────────────────────────────────────┘ ``` #### A. Header & Navigation - **Back button:** "← Indietro" (left-aligned, links to `/admin/offers`) - **Page title:** "Modifica Offerta" (h2, 20px/600) OR "Crea Offerta" if new #### B. Offer Name & Quick Category/Ticket Display - **Name field:** EditableCell (text), required, displayed as h3 - **Category chip:** Single-select tag (Entry/Signature/Retainer), visually prominent - **Ticket chip:** Single-select tag (Low/Mid/High Ticket), visually secondary #### C. Tags Block (4 Dimensions) **Structure:** - Section label: "Tag" (h3, 16px/600) - Four rows (one per dimension): 1. **Categoria** (single): "Entry Offer" / "Signature Offer" / "Retainer Offer" 2. **Ticket** (single): "Low Ticket" / "Mid Ticket" / "High Ticket" 3. **Tipo** (multi): "Audit", "Done For You", "+ Crea Tipo" (add new) 4. **Obiettivo** (multi): "Primo Acquisto", "Up Scala Valore", "Lead Generation", "+ Crea Obiettivo" **Styling per dimension:** - Dimension label: 12px/400, bold, #71717a - Tags/chips: Use inherited TagMultiSelect component (from Phase 11) - Single-select (Categoria, Ticket): radio-like, one active chip at a time - Multi-select (Tipo, Obiettivo): checkbox-like, multiple active chips - Color: deterministic hash-based colors (6-8 color palette, per Phase 11 pattern) - Add action: "+ Crea [Dimension Name]" link/button inline - Spacing between rows: 16px (md) #### D. Services Matrix (Tier A/B/C) **Section label:** "Servizi Inclusi" (h3) **Table structure:** - **Columns:** Service Name | Unit Price | Tier A (checkbox) | Tier B (checkbox) | Tier C (checkbox) - **Header row:** - Left: "Servizio" (12px/400, bold) - Cols: "Prezzo" (right-aligned) + "A", "B", "C" (centered, column headers) - Header background: #f9f9f9 - Header border-bottom: #e5e7eb - **Service rows:** - Service name (left, flex-grow) - Unit price (right-aligned, tabular nums, "€X,XX") - Checkbox per tier (centered) - Style: standard `` - Color: #1A463C when checked - Interaction: Click to toggle; live total updates immediately - Row height: ~40px - Border-bottom: #e5e7eb - Hover: subtle background #f9f9f9 on the entire row - **Services filtered by category:** Only show services whose `services.category` matches the offer's selected category (Categoria tag) - **Row order:** Configurable (drag-to-reorder via @dnd-kit optional; planner decides) **Totals rows** (sticky to bottom of table): 1. **Totale Servizi** (auto-calculated): - Row label: "Totale Servizi" (bold, 14px/600) - Per tier: SUM of unit_price for all services with checkbox ☑ in that tier - Format: "€X,XXX.XX" - Updates **live** as checkboxes are toggled (no page reload) - Color: bold text, #1a1a1a 2. **Prezzo Pubblico** (manual input): - Row label: "Prezzo Pubblico" (bold, 14px/600) - Per tier: EditableCell (number type) - Placeholder: "€0,00" or empty - Format: "€X,XXX.XX" - Interaction: Click to edit, blur/Enter to save, Esc to cancel - Independent of "Totale Servizi" — user can set any value - Validation: numeric, ≥ 0, max 2 decimals **Empty state (no services matching category):** - Placeholder text: "Nessun servizio disponibile per questa categoria" (#71717a) - Table still visible but with placeholder row --- #### E. Transformation Promise Block **Section label:** "Promessa di Trasformazione" (h3) **Structure:** 5 editable fields, each a key-value pair: ``` Aiuto: [Client Ideal — input text, 60 chars] A ottenere: [Result — input text, 60 chars] In: [Time — input text, 30 chars, e.g. "3 mesi"] Senza: [Pain — input text, 60 chars] Grazie a: [Method — input text, 60 chars] ``` **Implementation:** - Each field is an EditableCell (text type, optional) - Field label (dim): 12px/400, color #71717a - Input placeholder: "Aggiungi [field name]" (muted) - No asterisk (all optional) - Layout: horizontal label-value pairs, or stacked label-over-input depending on space - On desktop: consider 2-column or full-width labels - Tablet/mobile: stack vertically - Spacing between fields: 16px (md) - Background: optional subtle bg #f9f9f9, or transparent (planner decides) - Border: none, or bottom-border only on each field - Focus ring: #1A463C when in edit mode (EditableCell pattern) --- #### F. Action Buttons **Bottom of editor:** - **[Salva Offerta]** (primary CTA) - Button: bg #1A463C, text white, 14px/600, padding 16px 24px - Position: left-aligned - On click: Submit form, validate all required fields (name + category + at least 1 service selected in 1 tier), save to DB, redirect to `/admin/offers`, show toast "Offerta salvata" - Disabled state: if no tiers have services selected, button disabled with tooltip "Seleziona almeno un servizio" - **[Annulla]** (secondary CTA) - Button: border #e5e7eb, text #1a1a1a, no fill - On click: Discard unsaved changes, redirect to `/admin/offers` - Optional confirmation: if form is dirty, "Scartare le modifiche?" dialog - **[Archivia]** (destructive action, visible only on edit, not on create) - Button: text #dc2626, no fill - On click: Toggle offer `is_archived` flag - Confirmation required: "Sei sicuro? L'offerta non sarà visibile nella lista." [Annulla] [Archivia] - After confirm: Save, show toast "Offerta archiviata", redirect to `/admin/offers` **Spacing between buttons:** 8px (sm) --- ### 3. Tag Creation Inline ("Crea Tipo", "Crea Obiettivo") **Interaction:** When user clicks "+ Crea [Dimension]", an input field appears inline (or in a small popover). **Structure:** - Text input: placeholder "Nome [Dimension]..." - On Enter or click checkmark: Create tag (server action `addTagToOffer`), add to active tags list - On Esc or click X: Cancel, close input - On success: Input clears, new tag appears in the chip list with deterministic color **Validation:** - Required: tag name 1-30 chars - Trimmed whitespace - Case-insensitive duplicate check per dimension (warn: "Questo tag esiste già") --- ## Copywriting Contract | Element | Copy | Context | |---------|------|---------| | **Offer list heading** | "Offerte" | Page title | | **Create offer CTA** | "+ Nuova Offerta" | Primary action on list | | **Back link** | "← Indietro" | Breadcrumb/nav on detail | | **Archive toggle label** | "Mostra offerte archiviate" | Filter checkbox | | **Edit page heading** | "Modifica Offerta" | Detail page title (or "Crea Offerta" if new) | | **Tags section label** | "Tag" | Section heading | | **Category dimension** | "Categoria" | Single-select label | | **Ticket dimension** | "Ticket" | Single-select label | | **Type dimension** | "Tipo" | Multi-select label | | **Objective dimension** | "Obiettivo" | Multi-select label | | **Create type link** | "+ Crea Tipo" | Inline tag creation | | **Create objective link** | "+ Crea Obiettivo" | Inline tag creation | | **Services section label** | "Servizi Inclusi" | Table heading | | **Service column header** | "Servizio" | Table column | | **Price column header** | "Prezzo" | Table column | | **Tier headers** | "A", "B", "C" | Table columns (single letters) | | **Services total label** | "Totale Servizi" | Calculated row | | **Public price label** | "Prezzo Pubblico" | Manual input row | | **Transformation block label** | "Promessa di Trasformazione" | Section heading | | **Ideal client label** | "Aiuto" | Field label | | **Result label** | "A ottenere" | Field label | | **Duration label** | "In" | Field label (e.g., "3 mesi") | | **Pain label** | "Senza" | Field label | | **Method label** | "Grazie a" | Field label | | **Save CTA** | "Salva Offerta" | Primary action (verb + noun) | | **Cancel CTA** | "Annulla" | Secondary action | | **Archive CTA** | "Archivia" | Destructive action (visible on edit only) | | **Archive confirmation** | "Sei sicuro? L'offerta non sarà visibile nella lista." | Destructive confirmation copy + context | | **Empty services message** | "Nessun servizio disponibile per questa categoria" | Conditional placeholder | | **Empty offers message** | "Nessuna offerta" | List page empty state heading | | **Empty offers hint** | "Inizia creando la tua prima offerta" | List page empty state body | | **Save success toast** | "Offerta salvata" | Success feedback | | **Archive success toast** | "Offerta archiviata" | Success feedback | | **Save error toast** | "Errore nel salvataggio. Verifica i campi." | Error feedback | | **Tag name placeholder** | "Nome tag..." | Create tag input | | **Duplicate tag warning** | "Questo tag esiste già" | Validation error | | **Disabled save tooltip** | "Seleziona almeno un servizio" | Constraint message | --- ## Interaction & State Management ### Checkbox Matrix Behavior - **On checkbox toggle:** Live recalculation of "Totale Servizi" for that tier (no network call) - **Debounce:** None — updates instant as user clicks - **Persistence:** On "Salva Offerta" click, POST all tier compositions to backend - **Validation on save:** At least 1 tier must have ≥1 service selected (backend confirms) ### Tag Dimensions - **Categoria & Ticket:** Single-select (user can only have one value per offer) - UI: Radio-button-like chips or dropdown — planner decides - Switching to a different service category auto-filters the services matrix - **Tipo & Obiettivo:** Multi-select (user can have 0..N values per offer) - UI: Checkbox-like chips (inherited TagMultiSelect pattern) - Tag creation inline: "+ Crea Tipo" / "+ Crea Obiettivo" - Duplicate check: case-insensitive per dimension (warn and skip) - Color: deterministic based on tag name hash (6-8 color palette, per Phase 11) ### Archive Workflow - Archive is a toggle, not permanent deletion - Archived offers are hidden from list by default (togglable via "Mostra offerte archiviate") - On edit page, "Archivia" button only visible if offer is not already archived (or toggle text changes to "Ripristina") - Confirmation required for destructive state change ### Edit State Management - Form is dirty if any field changed since load - On cancel with unsaved changes: confirmation dialog "Scartare le modifiche?" - On back button navigation: same check (browser may warn too) - Auto-save optional (planner decides) — recommend explicit "Salva" for clarity --- ## Visual States ### Offer Card States | State | Visual | |-------|--------| | Default | White card, border #e5e7eb, normal shadow | | Hover | `cursor-pointer`, shadow deepens (0 4px 12px rgba(0,0,0,0.08)) | | Archived | Gray text, optional red "Archiviata" badge top-right | ### Checkbox States | State | Visual | |-------|--------| | Unchecked | Empty square, border #e5e7eb | | Checked | Filled square, bg #1A463C, white checkmark | | Hover | Cursor pointer, subtle border darken | | Disabled | Gray box, opacity-50, no cursor | ### Editable Field States (EditableCell) | State | Visual | |-------|--------| | Display mode | Normal text, `cursor-pointer`, hover bg #f0f0f0 | | Edit mode | Input with ring-1 ring-#1A463C on focus | | Error | Ring-1 ring-#dc2626, error message below | | Disabled | Gray text, opacity-50, no cursor | ### Tag States (TagMultiSelect) | State | Visual | |-------|--------| | Display | Colored chip (6-8 colors), text inside, X icon to remove | | Hover | Slight opacity change on X icon | | Add mode | Input field + "+ Aggiungi" button (or Enter to confirm) | | Create new | Temporary placeholder, then colored chip with new tag name | ### Category Filter Chip States (List Page) | State | Visual | |-------|--------| | Inactive | Border #e5e7eb, text #71717a, bg transparent, `cursor-pointer` | | Active | Border #1A463C, text #1A463C (or bg #1A463C text white — planner decides) | | Hover | Border darken slightly | --- ## Responsive Behavior **Desktop (≥1024px):** - Offer list: 3-column card grid - Filter chips: horizontal row - Services matrix: full-width table, horizontal scroll if needed - Editor sections: full-width, stacked vertically **Tablet (768px–1023px):** - Offer list: 2-column card grid - Filter chips: wrap horizontally or scroll if needed - Services matrix: horizontal scroll (sticky left column optional) - Tag fields: stack or 2-column layout (planner decides) **Mobile (<768px):** - Offer list: 1-column card stack - Filter chips: vertical stack OR horizontal scroll in a container - Services matrix: horizontal scroll with sticky "Servizio" + "Prezzo" columns - Tag fields: stack vertically - Buttons: full-width stack - Archive toggle: move below filter chips --- ## Registry Safety | Registry | Blocks Used | Safety Gate | |----------|-------------|-------------| | shadcn official | button, input, dialog, badge, table, form, checkbox | not required (official shadcn) | | @dnd-kit | core, sortable, utilities (optional for row reordering) | not required (npm package, not registry) | --- ## Checker Sign-Off - [ ] Dimension 1 Copywriting: All labels/CTAs use verb+noun, destructive actions clear ("Archivia" + confirmation), tag creation inline - [ ] Dimension 2 Visuals: Checkbox matrix clear, live totals update instant, archive badge visible, tag colors consistent - [ ] Dimension 3 Color: Accent (#1A463C) reserved for primary CTA ("Salva Offerta"), focus ring, active filter chip; destructive (#dc2626) for archive only - [ ] Dimension 4 Typography: Body 14px/400, labels 12px/400, headings 16–20px/600 — all per Phase 11 contract (2 weights only) - [ ] Dimension 5 Spacing: All gaps/padding multiples of 4px, card padding 16px (md), section gaps 24px (lg), row height ~40px - [ ] Dimension 6 Registry Safety: shadcn official + npm packages, no third-party registries **Approval:** pending --- ## Notes for Planner 1. **Service filtering by category:** Implement dynamic filter on the services matrix when Categoria tag changes. Query: fetch all services WHERE `services.category = offer.categoria_tag_value`. 2. **Live total calculation:** Client-side state, no server call. Recalculate sum of unit prices for all checked services in each tier on every checkbox toggle. 3. **Three tiers (A/B/C):** Map to `offer_micros` table, one micro per tier. Store which services are assigned via new `offer_tier_services` junction table (additive, not modifying legacy `offer_micro_services`). 4. **Tag dimensions:** Use Phase 11's polymorphic `tags` table with `entity_type = "offer_macros"`. Add optional `dimension` column to distinguish Categoria/Ticket/Tipo/Obiettivo (or use tag name prefixes, planner decides). Single-select for Categoria/Ticket likely stored on `offer_macros` columns directly (simpler). 5. **Tag creation on the fly:** User can create new tag values during editing. Server action: `addTagToOffer(offerId, dimension, tagName)`. Validation + idempotency (onConflictDoNothing). 6. **Archive flag:** Add boolean `is_archived` column to `offer_macros` (additive migration). Filter in list query: WHERE is_archived = false (or include if toggle is on). 7. **Edit page routing:** Consider `/admin/offers/[id]/edit` (new page) or modal overlay. Both work; full-page simpler for first iteration. 8. **Services matrix columns:** Each tier (A/B/C) is visually a column with checkboxes. Sticky header recommended. Sticky left column (service name + price) optional on mobile/tablet. 9. **Transformation promise fields:** All optional. Can be empty string. EditableCell (text) pattern from Phase 11. 10. **Disable "Salva" button validation:** - Rule: at least 1 tier must have ≥1 service checked - If all tiers unchecked: button disabled with tooltip - On submit: backend also validates (server-side check) 11. **Category filter on list:** Clicking a chip sets active filter. API: `GET /admin/offers?category=entry` (or all if "Tutti"). Auto-refresh list. 12. **Migration strategy:** Additive only. Do NOT modify existing `offer_macros`, `offer_micros`, `offer_micro_services` — extend with: - `offer_tier_services` (new junction: tier_id → service_id) - `offer_macros.is_archived` (boolean, default false) - Optionally: structured promise fields on `offer_macros` (cliente_ideale, risultato, tempo, pain, metodo) - Optionally: `services.category` → verify not already used (check Phase 11 usage first) 13. **Dry run before prod:** Test with existing offer data. Verify no data loss. Run audit script to confirm migration integrity. --- ## Requirements Traceability | Requirement | Phase 12 Coverage | |-------------|------------------| | OFFER-11 | Offer editor: checkbox matrix, 3 tiers (A/B/C), live totals ✓ | | OFFER-15 | 4-dimensional tags (Categoria/Ticket/Tipo/Obiettivo), creatable on-the-fly ✓ | | OFFER-16 | Manual "Prezzo Pubblico" per tier, independent of service total ✓ | | OFFER-17 | Structured transformation promise (5 fields) ✓ | | OFFER-18 | List page filterable by category, archive toggle ✓ | --- ## Deferred Items - **CSV/Notion import** (OFFER-12): Deferred to future phase per user decision (2026-06-14) - **Drag-to-reorder services** (optional @dnd-kit): Not required for MVP; can be added in post-phase enhancement --- *UI-SPEC regenerated: 2026-06-14* *Design system source: Phase 11 (inherited), no new tokens* *Scope: Offer Editor (list + detail) with 3-tier checkbox matrix, multi-dimensional tags, manual pricing, transformation promise*