Files
simone 640f986967 docs(12): create phase plan
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.
2026-06-14 21:00:32 +02:00

28 KiB
Raw Permalink Blame History

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 (7681023px), 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 <input type="checkbox">
      • 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 (768px1023px):

  • 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 1620px/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