Re-scoped Offer Editor (Tier A/B/C, Tag & Prezzo Pubblico) per 2026-06-14 user decision: 5-plan wave structure (schema migration -> prod apply checkpoint -> query/action layer -> list + detail editor UI), OFFER-12 CSV/Notion import deferred.
28 KiB
phase, slug, status, shadcn_initialized, preset, created
| phase | slug | status | shadcn_initialized | preset | created |
|---|---|---|---|---|---|
| 12 | offer-editor-tier-tag-prezzo-pubblico | draft | true | default | 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
<Switch>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):
- Categoria (single): "Entry Offer" / "Signature Offer" / "Retainer Offer"
- Ticket (single): "Low Ticket" / "Mid Ticket" / "High Ticket"
- Tipo (multi): "Audit", "Done For You", "+ Crea Tipo" (add new)
- 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
<input type="checkbox"> - Color: #1A463C when checked
- Interaction: Click to toggle; live total updates immediately
- Style: standard
- Row height: ~40px
- Border-bottom: #e5e7eb
- Hover: subtle background #f9f9f9 on the entire row
- Services filtered by category: Only show services whose
services.categorymatches 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):
-
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
-
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_archivedflag - 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
-
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. -
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.
-
Three tiers (A/B/C): Map to
offer_microstable, one micro per tier. Store which services are assigned via newoffer_tier_servicesjunction table (additive, not modifying legacyoffer_micro_services). -
Tag dimensions: Use Phase 11's polymorphic
tagstable withentity_type = "offer_macros". Add optionaldimensioncolumn to distinguish Categoria/Ticket/Tipo/Obiettivo (or use tag name prefixes, planner decides). Single-select for Categoria/Ticket likely stored onoffer_macroscolumns directly (simpler). -
Tag creation on the fly: User can create new tag values during editing. Server action:
addTagToOffer(offerId, dimension, tagName). Validation + idempotency (onConflictDoNothing). -
Archive flag: Add boolean
is_archivedcolumn tooffer_macros(additive migration). Filter in list query: WHERE is_archived = false (or include if toggle is on). -
Edit page routing: Consider
/admin/offers/[id]/edit(new page) or modal overlay. Both work; full-page simpler for first iteration. -
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.
-
Transformation promise fields: All optional. Can be empty string. EditableCell (text) pattern from Phase 11.
-
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)
-
Category filter on list: Clicking a chip sets active filter. API:
GET /admin/offers?category=entry(or all if "Tutti"). Auto-refresh list. -
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)
-
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