Build spec & handoff · for Lovable rebuild

Unlock Asset Register — list & allocation, exactly as built.

The written spec for rebuilding the single Asset Register page in Lovable. The live page is the visual reference; this is the record of structure, data model, the allocation donut, and the design tokens. ← Back to the page

Contents

01

What to build

The Asset Register is the platform's single source of truth — every other module reads from it. One page, two views of the same holdings: a List (grouped by account wrapper) and an Allocation donut (grouped by asset class), switched by a segmented toggle. Dark theme by default.

Stack: React + TypeScript, recharts for the donut, lucide-react icons. Money formatted as GBP, whole pounds (Intl.NumberFormat('en-GB',{style:'currency',currency:'GBP',maximumFractionDigits:0})).

02

Page anatomy (top → bottom)

┌─────────────────────────────────────────────────────────────┐
│ Illustrative-data banner (amber, persistent)                │
├─────────────────────────────────────────────────────────────┤
│ H1 "Asset Register"            [↑ Import from CSV] [+ Add]   │
│ Portfolio total bar ............................ £2,950,000  │
│ ┌─ "Your first insight" (accent left-border, dismissible) ─┐│
│ │ Estimated IHT exposure: £4,313,024 + first tension       ││
│ └──────────────────────────────────────────────────────────┘│
│ [ List | Allocation ]   ← segmented toggle                  │
│                                                             │
│ LIST view:  groups by wrapper (ISA, Pension, EIS, Cash, GIA)│
│   each group: header + subtotal, then asset rows            │
│ ALLOCATION view: donut + legend, grouped by asset class     │
└─────────────────────────────────────────────────────────────┘

Content column is centred, max-width: 960px.

03

Data model

Each holding is an Asset. The fields that matter for this page:

type Asset = {
  asset_id: string;
  label: string;            // "SIPP — Income & Growth"
  wrapper_type: string;     // 'isa' | 'pension' | 'eis' | 'cash' | 'gia' | 'vct' | 'aim' | 'property'
  asset_class: string;      // 'UK_EQUITY' | 'GLOBAL_EQUITY' | 'PROPERTY_DIRECT' | 'CASH' | 'PRIVATE_EQUITY' | ...
  current_value: number;    // GBP
  // + tax fields used elsewhere (acquisition_cost, bpr_qualifying_date, in_drawdown, …)
}
Wrapper codeDisplay label
isa / pension / eisISA / Pension / EIS
vct / aim / giaVCT / AIM / GIA
property / cashProperty / Cash

Asset-class codes are prettified for display: split on _, title-case, keep acronyms uppercase (UK, US, EIS, SEIS, VCT, AIM). e.g. UK_EQUITY → "UK Equity", PROPERTY_DIRECT → "Property Direct".

04

List view

05

Allocation (pie) view — exact spec

A recharts donut + a legend list, grouped by asset_class and sorted descending by value.

Donut

<ResponsiveContainer width="100%" height="100%">   {/* in a 240×240 box */}
  <PieChart>
    <Pie data={data} dataKey="value" cx="50%" cy="50%"
         innerRadius={70} outerRadius={100} paddingAngle={2}>
      {data.map((_, i) => <Cell fill={PIE_COLORS[i % PIE_COLORS.length]} />)}
    </Pie>
    <Tooltip formatter={(v) => `${gbp(v)} (${(v/total*100).toFixed(1)}%)`}
       contentStyle={{background:'var(--unlock-surface)',
                      border:'1px solid var(--unlock-border)',
                      borderRadius:8, fontSize:13}} />
  </PieChart>
</ResponsiveContainer>

Centre overlay (absolutely positioned, pointer-events:none): total in 18px/700, "Total" caption in 11px muted.

Legend (right of donut, flex-wrap)

One row per class: 10×10 rounded colour swatch (matching cell) · class name (14px/600) · value (muted, right) · percent (14px/700, right). Rows divided by 1px solid var(--unlock-border). Below: "Largest class: X at NN.N%".

Chart palette

PIE_COLORS = ['#01BC77','#3b82f6','#f59e0b','#ef4444','#8b5cf6',
              '#ec4899','#14b8a6','#f97316','#6366f1','#84cc16']
06

Design tokens

Dark theme is the default. Use these exact values; the brand green is constant.

--unlock-bg: #1e1e1e

--unlock-surface: #2b2b2b (cards/rows)

--unlock-surface-raised: #333333 (icon tiles)

--unlock-border: #444444

--unlock-text: #ffffff

--unlock-muted: #b0b0b0

--unlock-accent: #01BC77 (toggle active, Edit, buttons)

--unlock-danger: #ef4444 (Delete)

Light theme (optional, via .demo-light): bg #ffffff, surface #f5f6f8, border #e3e6ea, text #212022, muted #5b6470. Font: Inter. Radius: 0.5rem.

07

Synthetic data (the reference household)

"David & Sharon" (legacy_builder), post-business-sale. Total £2,950,000.

LabelWrapperAsset classValue
Acme plc — business-sale proceeds (single stock)GIAUK Equity£900,000
Family HomeGIAProperty Direct£850,000
SIPP — Income & GrowthPensionUK Equity£600,000
Cash ReserveCashCash£250,000
Stocks & Shares ISAISAGlobal Equity£200,000
EIS PortfolioEISPrivate Equity£150,000

Allocation by asset class → UK Equity 50.8% (£1.5m), Property Direct 28.8%, Cash 8.5%, Global Equity 6.8%, Private Equity 5.1%. A single holding (Acme) is 30.5% of the portfolio — the concentration story.

08

Behaviours & states

09

Lovable build checklist

  1. React + TS + Tailwind; add recharts + lucide-react.
  2. Define dark tokens (§6) on :root; Inter font; radius 8px.
  3. Header (title + Import/Add buttons), total bar, dismissible insight banner.
  4. Segmented List/Allocation toggle (accent active state).
  5. List: group by wrapper, subtotals, asset rows with Compare/Edit/Delete.
  6. Allocation: recharts donut (§5 exact props) + legend + "Largest class" line.
  7. Seed the synthetic household (§7); GBP whole-pound formatting; keep the illustrative banner.