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
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})).
┌─────────────────────────────────────────────────────────────┐ │ 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.
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 code | Display label |
|---|---|
| isa / pension / eis | ISA / Pension / EIS |
| vct / aim / gia | VCT / AIM / GIA |
| property / cash | Property / 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".
wrapper_type. Each group shows a header (WRAPPER_LABEL · N assets) with the group subtotal on the right.WRAPPER · ASSET_CLASS) · value (right, bold) · actions: Compare (muted), Edit (green), Delete (red).background: var(--unlock-surface); border: 1px solid var(--unlock-border); border-radius: 8px.A recharts donut + a legend list, grouped by asset_class and sorted descending by value.
<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.
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%".
PIE_COLORS = ['#01BC77','#3b82f6','#f59e0b','#ef4444','#8b5cf6',
'#ec4899','#14b8a6','#f97316','#6366f1','#84cc16']
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.
"David & Sharon" (legacy_builder), post-business-sale. Total £2,950,000.
| Label | Wrapper | Asset class | Value |
|---|---|---|---|
| Acme plc — business-sale proceeds (single stock) | GIA | UK Equity | £900,000 |
| Family Home | GIA | Property Direct | £850,000 |
| SIPP — Income & Growth | Pension | UK Equity | £600,000 |
| Cash Reserve | Cash | Cash | £250,000 |
| Stocks & Shares ISA | ISA | Global Equity | £200,000 |
| EIS Portfolio | EIS | Private 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.
recharts + lucide-react.:root; Inter font; radius 8px.