Skip to content

Commit 2defb11

Browse files
V48 (specification-implementation): Polish packs experience modular units
Split Packs master/detail multi-concern surfaces into named units (filter bar, table, detail states/accounting/governance/proof roots, status pill), keep pure formatters in models/, thin PacksPageClient with route-params hook, and document the layout in packs README, FAMILIARIZATION §7.4, and SOURCE_LAYOUT.
1 parent bb86f08 commit 2defb11

15 files changed

Lines changed: 794 additions & 513 deletions

File tree

FAMILIARIZATION.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,23 @@ in later V48 gates. Workbench panels under `Reads*` + `models/deposit-read-*`
393393
### 7.4 Packs (`/packs`)
394394

395395
Network-scope PackActivity master-detail (ledgerized history).
396-
**Not** personal pipeline activity (that is `/deposits`).
397-
Models: `pack-activity-model` under bitcode activity; UI units portfolio/master/detail.
396+
**Not** personal pipeline activity (that is `/deposits`).
397+
398+
Home: `uapi/components/packs/`.
399+
400+
| Concern | Location |
401+
| --- | --- |
402+
| Page client (orchestration only) | `PacksPageClient/` + `hooks/use-packs-activity`, `use-packs-route-params` |
403+
| Pure formatters / option catalogs | `models/packs-format.ts`, `models/packs-activity-types.ts` |
404+
| Portfolio positions + market signals | `PacksPortfolioOverview/` |
405+
| Master shell | `PacksActivityMaster/` composes filter bar + table + type totals |
406+
| Filter bar / data grid | `PacksActivityFilterBar/`, `PacksActivityTable/` |
407+
| Detail shell + sections | `PacksActivityDetail/` + `PacksActivityDetailStates/`, `…Accounting/`, `…Governance/`, `…ProofRoots/` |
408+
| Shared chrome | `PacksDetailSection/`, `PacksStatusPill/` |
409+
| Cross-route activity projection | `bitcode/activity/PackActivityModel/pack-activity-model` (leave under Bitcode; not packs-only) |
410+
411+
App shell: `uapi/app/packs/` re-exports the page client. Layout contract:
412+
`internal-docs/BITCODE_SOURCE_LAYOUT.md` § Packs experience.
398413

399414
### 7.5 Docs (`/docs`)
400415

internal-docs/BITCODE_SOURCE_LAYOUT.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,19 @@ uapi/components/deposits/
134134

135135
```
136136
uapi/components/packs/
137-
models/
138-
PacksPageClient/ + hooks/use-packs-activity.ts
137+
models/ # pure: packs-format.ts, activity types
138+
PacksPageClient/ + hooks/ # use-packs-activity, use-packs-route-params
139139
PacksPortfolioOverview/
140-
PacksActivityMaster/
141-
PacksActivityDetail/
140+
PacksActivityMaster/ # shell: filter bar + table + totals
141+
PacksActivityFilterBar/
142+
PacksActivityTable/
143+
PacksActivityDetail/ # shell: overview + measurements + sections
144+
PacksActivityDetailStates/
145+
PacksActivityDetailAccounting/
146+
PacksActivityDetailGovernance/
147+
PacksActivityDetailProofRoots/
142148
PacksDetailSection/
149+
PacksStatusPill/ # React status chip (not models/)
143150
```
144151

145152
---

uapi/components/packs/PacksActivityDetail/PacksActivityDetail.tsx

Lines changed: 19 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
/**
2-
* Packs detail aside: source-safe overview, measurements, states, accounting,
3-
* governance, and proof roots for the selected PackActivity row.
2+
* Packs detail aside shell: overview, measurements, then section units
3+
* (states, accounting, governance, proof roots) for the selected row.
44
*/
55
"use client";
66

77
import React from "react";
88
import { ShieldCheck } from "lucide-react";
9-
import {
10-
ProductRouteProofDetail,
11-
ProductRouteStatePanel,
12-
} from "@/components/bitcode/routes/ProductRouteShell/ProductRouteShell";
9+
import { ProductRouteStatePanel } from "@/components/bitcode/routes/ProductRouteShell/ProductRouteShell";
1310
import type { PackActivityDetailProjection } from "@/components/bitcode/activity/PackActivityModel/pack-activity-model";
1411
import {
15-
formatSats,
1612
formatTimestamp,
1713
formatType,
18-
statusPill,
1914
} from "@/components/packs/models/packs-format";
20-
import { PacksDetailSection as DetailSection } from "@/components/packs/PacksDetailSection/PacksDetailSection";
15+
import { PacksDetailSection } from "@/components/packs/PacksDetailSection/PacksDetailSection";
16+
import { PacksActivityDetailStates } from "@/components/packs/PacksActivityDetailStates/PacksActivityDetailStates";
17+
import { PacksActivityDetailAccounting } from "@/components/packs/PacksActivityDetailAccounting/PacksActivityDetailAccounting";
18+
import { PacksActivityDetailGovernance } from "@/components/packs/PacksActivityDetailGovernance/PacksActivityDetailGovernance";
19+
import { PacksActivityDetailProofRoots } from "@/components/packs/PacksActivityDetailProofRoots/PacksActivityDetailProofRoots";
2120

2221
export type PacksActivityDetailProps = {
2322
detail: PackActivityDetailProjection | null;
@@ -54,7 +53,7 @@ export function PacksActivityDetail({ detail }: PacksActivityDetailProps) {
5453
</p>
5554
</div>
5655

57-
<DetailSection title="Overview">
56+
<PacksDetailSection title="Overview">
5857
<dl className="grid gap-3 text-sm tablet:grid-cols-2">
5958
<div>
6059
<dt className="text-neutral-500">Type</dt>
@@ -81,9 +80,9 @@ export function PacksActivityDetail({ detail }: PacksActivityDetailProps) {
8180
</dd>
8281
</div>
8382
</dl>
84-
</DetailSection>
83+
</PacksDetailSection>
8584

86-
<DetailSection title="Measurements">
85+
<PacksDetailSection title="Measurements">
8786
<div className="grid gap-2">
8887
{detail.measurements.length ? (
8988
detail.measurements.map((measurement) => (
@@ -103,184 +102,19 @@ export function PacksActivityDetail({ detail }: PacksActivityDetailProps) {
103102
</p>
104103
)}
105104
</div>
106-
</DetailSection>
105+
</PacksDetailSection>
107106

108-
<DetailSection title="State readback">
109-
<div className="grid gap-2 text-sm">
110-
<div className="flex items-center justify-between gap-3">
111-
{statusPill(detail.states.settlement, "settlement not recorded")}
112-
</div>
113-
<div className="flex items-center justify-between gap-3">
114-
{statusPill(detail.states.rights, "BTD rights not recorded")}
115-
</div>
116-
<div className="flex items-center justify-between gap-3">
117-
{statusPill(
118-
detail.states.compensation,
119-
"compensation not recorded",
120-
)}
121-
</div>
122-
<div className="flex items-center justify-between gap-3">
123-
{statusPill(detail.states.delivery, "delivery not recorded")}
124-
</div>
125-
<div className="flex items-center justify-between gap-3">
126-
{statusPill(detail.states.repair, "repair not recorded")}
127-
</div>
128-
</div>
129-
</DetailSection>
107+
<PacksActivityDetailStates detail={detail} />
130108

131-
{detail.commodityState?.repairRequired ||
132-
detail.commodityState?.blockers?.length ? (
133-
<DetailSection title="Repair surface">
134-
<div className="grid gap-2 text-sm">
135-
<div className="flex items-center justify-between gap-3">
136-
{statusPill(
137-
detail.states.repair || "repair-required",
138-
"repair posture pending",
139-
)}
140-
</div>
141-
<ul className="grid gap-1 text-xs text-neutral-400">
142-
{(detail.commodityState?.blockers || []).map((blocker) => (
143-
<li key={blocker} className="break-words">
144-
{blocker}
145-
</li>
146-
))}
147-
</ul>
148-
<p className="text-xs text-neutral-500">
149-
State advances only through proof-backed readback; repair fails
150-
closed until the missing or contradictory evidence above is
151-
reconciled.
152-
</p>
153-
</div>
154-
</DetailSection>
109+
{detail.accounting ? (
110+
<PacksActivityDetailAccounting accounting={detail.accounting} />
155111
) : null}
156112

157-
{detail.accounting && (
158-
<DetailSection title="Accounting">
159-
<dl className="grid gap-3 text-sm tablet:grid-cols-2">
160-
<div>
161-
<dt className="text-neutral-500">BTD/BTC state</dt>
162-
<dd className="mt-1 text-neutral-100">
163-
{detail.accounting.state || "not recorded"}
164-
</dd>
165-
</div>
166-
<div>
167-
<dt className="text-neutral-500">BTD range</dt>
168-
<dd className="mt-1 text-neutral-100">
169-
{detail.accounting.btdRangeState || "not recorded"}
170-
</dd>
171-
</div>
172-
<div>
173-
<dt className="text-neutral-500">BTC settlement</dt>
174-
<dd className="mt-1 text-neutral-100">
175-
{detail.accounting.btcSettlementState || "not recorded"}
176-
</dd>
177-
</div>
178-
<div>
179-
<dt className="text-neutral-500">Treasury route</dt>
180-
<dd className="mt-1 text-neutral-100">
181-
{detail.accounting.treasuryRouteState || "not recorded"}
182-
</dd>
183-
</div>
184-
<div>
185-
<dt className="text-neutral-500">Contributors</dt>
186-
<dd className="mt-1 font-mono text-neutral-100">
187-
{detail.accounting.contributorCount}
188-
</dd>
189-
</div>
190-
<div>
191-
<dt className="text-neutral-500">Allocated</dt>
192-
<dd className="mt-1 font-mono text-neutral-100">
193-
{formatSats(detail.accounting.allocatedContributorSats)}
194-
</dd>
195-
</div>
196-
{detail.accounting.statementRoot && (
197-
<div className="tablet:col-span-2">
198-
<dt className="text-neutral-500">Accounting root</dt>
199-
<dd className="mt-1 break-all font-mono text-xs text-emerald-100">
200-
{detail.accounting.statementRoot}
201-
</dd>
202-
</div>
203-
)}
204-
</dl>
205-
</DetailSection>
206-
)}
207-
208-
{detail.governance && (
209-
<DetailSection title="Governance">
210-
<dl className="grid gap-3 text-sm tablet:grid-cols-2">
211-
<div>
212-
<dt className="text-neutral-500">Authority</dt>
213-
<dd className="mt-1 text-neutral-100">
214-
{detail.governance.state || "not recorded"}
215-
</dd>
216-
</div>
217-
<div>
218-
<dt className="text-neutral-500">Route</dt>
219-
<dd className="mt-1 text-neutral-100">
220-
{detail.governance.route || "not recorded"}
221-
</dd>
222-
</div>
223-
<div>
224-
<dt className="text-neutral-500">Wallet</dt>
225-
<dd className="mt-1 text-neutral-100">
226-
{detail.governance.walletState || "not recorded"}
227-
</dd>
228-
</div>
229-
<div>
230-
<dt className="text-neutral-500">Spend</dt>
231-
<dd className="mt-1 text-neutral-100">
232-
{detail.governance.spendState || "not recorded"}
233-
</dd>
234-
</div>
235-
<div>
236-
<dt className="text-neutral-500">Deposit</dt>
237-
<dd className="mt-1 text-neutral-100">
238-
{detail.governance.depositState || "not recorded"}
239-
</dd>
240-
</div>
241-
<div>
242-
<dt className="text-neutral-500">Required denials</dt>
243-
<dd className="mt-1 font-mono text-neutral-100">
244-
{detail.governance.requiredDeniedActionCount}
245-
</dd>
246-
</div>
247-
{detail.governance.authorityRoot && (
248-
<div className="tablet:col-span-2">
249-
<dt className="text-neutral-500">Authority root</dt>
250-
<dd className="mt-1 break-all font-mono text-xs text-emerald-100">
251-
{detail.governance.authorityRoot}
252-
</dd>
253-
</div>
254-
)}
255-
</dl>
256-
</DetailSection>
257-
)}
113+
{detail.governance ? (
114+
<PacksActivityDetailGovernance governance={detail.governance} />
115+
) : null}
258116

259-
<DetailSection title="Proof roots">
260-
<ProductRouteProofDetail
261-
testId="packs-expandable-proof-detail"
262-
title="Expandable proof detail"
263-
tone="emerald"
264-
defaultOpen
265-
roots={[
266-
...detail.proofRoots.map((proofRoot) => ({
267-
id: proofRoot.id,
268-
label: proofRoot.label,
269-
root: proofRoot.root,
270-
})),
271-
{
272-
id: "accounting-root",
273-
label: "Accounting root",
274-
root: detail.accounting?.statementRoot,
275-
},
276-
{
277-
id: "authority-root",
278-
label: "Authority root",
279-
root: detail.governance?.authorityRoot,
280-
},
281-
]}
282-
/>
283-
</DetailSection>
117+
<PacksActivityDetailProofRoots detail={detail} />
284118
</div>
285119
</aside>
286120
);
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* Packs detail accounting readback (BTD/BTC, allocation, statement root).
3+
*/
4+
"use client";
5+
6+
import React from "react";
7+
import type { PackActivityDetailProjection } from "@/components/bitcode/activity/PackActivityModel/pack-activity-model";
8+
import { formatSats } from "@/components/packs/models/packs-format";
9+
import { PacksDetailSection } from "@/components/packs/PacksDetailSection/PacksDetailSection";
10+
11+
export type PacksActivityDetailAccountingProps = {
12+
accounting: NonNullable<PackActivityDetailProjection["accounting"]>;
13+
};
14+
15+
export function PacksActivityDetailAccounting({
16+
accounting,
17+
}: PacksActivityDetailAccountingProps) {
18+
return (
19+
<PacksDetailSection title="Accounting">
20+
<dl className="grid gap-3 text-sm tablet:grid-cols-2">
21+
<div>
22+
<dt className="text-neutral-500">BTD/BTC state</dt>
23+
<dd className="mt-1 text-neutral-100">
24+
{accounting.state || "not recorded"}
25+
</dd>
26+
</div>
27+
<div>
28+
<dt className="text-neutral-500">BTD range</dt>
29+
<dd className="mt-1 text-neutral-100">
30+
{accounting.btdRangeState || "not recorded"}
31+
</dd>
32+
</div>
33+
<div>
34+
<dt className="text-neutral-500">BTC settlement</dt>
35+
<dd className="mt-1 text-neutral-100">
36+
{accounting.btcSettlementState || "not recorded"}
37+
</dd>
38+
</div>
39+
<div>
40+
<dt className="text-neutral-500">Treasury route</dt>
41+
<dd className="mt-1 text-neutral-100">
42+
{accounting.treasuryRouteState || "not recorded"}
43+
</dd>
44+
</div>
45+
<div>
46+
<dt className="text-neutral-500">Contributors</dt>
47+
<dd className="mt-1 font-mono text-neutral-100">
48+
{accounting.contributorCount}
49+
</dd>
50+
</div>
51+
<div>
52+
<dt className="text-neutral-500">Allocated</dt>
53+
<dd className="mt-1 font-mono text-neutral-100">
54+
{formatSats(accounting.allocatedContributorSats)}
55+
</dd>
56+
</div>
57+
{accounting.statementRoot && (
58+
<div className="tablet:col-span-2">
59+
<dt className="text-neutral-500">Accounting root</dt>
60+
<dd className="mt-1 break-all font-mono text-xs text-emerald-100">
61+
{accounting.statementRoot}
62+
</dd>
63+
</div>
64+
)}
65+
</dl>
66+
</PacksDetailSection>
67+
);
68+
}

0 commit comments

Comments
 (0)