Skip to content

superseded Superseded. Another plan owns this decision now.

What is still current: Decision 2 survives intact and is still the rule: a holding outside coverage is shown at market value and nothing else. The label, the section split and the CROSS wire key do not.

Landed: v0.29.0; replaced by the All view, PR #843 (v0.59.0)

Header updated 2026-09-14. The body below is frozen history. All plans.

The /portfolio "Other" tab — decision record ​

Replaced by the All view — see the header at the top of this page.

The tab this document designs was replaced in 2026-09 by the All view (portfolio-all-view-plan.md), which is the whole portfolio at market value in dollars: the two books' positions, the financed ones and the holdings outside coverage in ONE list, with a value history in place of the per-denomination return curves. Decision 2 below (a holding outside coverage is shown at market value and nothing else) survives intact and is still the rule; decisions 1, 5 and 6 (the label, the section split, the CROSS wire key) do not.

The product decisions in §1 were settled with the owner and are not open questions. Read AGENTS.md first; its shipping, review, UI and docs rules bind here.


0. Problem ​

The portfolio reports three asset classes (USD, ETH, BTC) plus a fourth view for positions financed across two of them. Everything else a tracked wallet holds at a covered venue was absent from the product entirely.

That population is not marginal and it is not noise. The read path has always classified it (outsideLegGroups in src/lib/portfolio/pnl.ts) and the API has always served it (PositionsResponse.outside), with a value and a reason per group. Nothing rendered it. On production today it holds, among other things, an equity-backed dollar token pledged as Morpho Blue collateral at roughly $182k and a governance token supplied to Aave at roughly $8.7k; the class also covers gold tokens (XAUt, PAXG) and the long tail of unmapped venue assets.

So a holder saw a portfolio smaller than the one they have, with nothing on screen saying why. The two obvious repairs are both wrong:

  • Count it in the books. There is no denomination to state a return in. Gold is not dollars; an equity-backed token that redeems only for whitelisted parties has no par claim to mark against. Booking it forces a fabricated redemption rate and puts a price move inside a yield figure.
  • Keep hiding it. A silent omission is the failure that started this.

1. Decision (settled) ​

  1. The fourth view is relabelled "Other". It holds two clearly separated sections:
    • Cross-currency positions — exactly today's content, untouched: the charts, the performance figures, the financed-position table.
    • Holdings outside coverage — new: what the wallet holds at a covered venue in an asset that settles in none of the three books.
  2. Holdings outside coverage are shown at market value and nothing else. No yield, no rate, no chart, no total. None of those figures exists for an asset with no home denomination, and the section's job is to say so by showing nothing where they would go.
  3. Not one figure enters the books. Tracked value, Net APY, projected income, the allocation split, every curve and the aggregate hero are unchanged, to the cent. Visibility changed; the accounting did not.
  4. A levered holding renders as a pair: the asset leg(s) and the debt leg(s) in one block with a net. Somebody running levered apxUSD did not buy a bare debt, and splitting the position into a holding and a loose borrowing misstates both.
  5. Each group carries a short plain-language reason, served by the API so the classification and the sentence explaining it cannot drift apart.
  6. The wire key stays CROSS. Only the label changed. Renaming the key would break any client bundle still running the previous deploy for the length of a rollout and buys nothing a user can see; this is the same rule managed_strategy_fund follows (src/lib/portfolio/api-types.ts).

2. Scope ​

  • src/lib/portfolio/api-data.ts — the reason labels a reader now sees, rewritten to say what the holding is rather than how it was classified; labelForRow consults the registry's on-chain ticker for tokens the curated symbol map deliberately leaves out.
  • src/lib/portfolio/registry.ts — loadPortfolioTokens also returns every active row's symbol (the query already selected it).
  • src/components/portfolio/signed-in-model.ts — outsideHoldings (the display model) and outsidePassesMinValue (the floor), both pure and unit-tested.
  • src/components/portfolio/PortfolioDashboard.tsx — the label, the two named sections, the new table.
  • scripts/fixture/seed.sql — three holdings outside coverage on the fixture wallet, deterministic, offline.
  • tests/e2e/portfolio.spec.ts, docs/portfolio.md.

3. Non-goals ​

  • No change to any performance figure, curve or classification rule. The engine (pnl.ts, segments.ts) is untouched except for label text.
  • No new data path. The API already returned everything rendered here.
  • No coverage expansion. Nothing is booked that was not booked before. Whether an asset should be covered stays the coverage-policy question in src/lib/portfolio/buckets.ts, and the WS8 alert stays the queue for it.
  • No total across the section. Groups carry their own units, and a figure across them could only come from an exchange rate.

4. Decisions taken where the spec left judgment ​

  • What "the unit" means for a holding outside coverage. A leg whose book is the EXCLUDED sentinel is valued MARKET-only in dollars by construction (snapshot.ts), so its unit is USD; every other leg's unit is its book. A group whose legs agree on one unit gets a net; one that spans units states a dash, which is the same discipline the cross-currency section applies to itself.

  • The section is MARKET-valued regardless of the price-basis toggle. An EXCLUDED leg has no redemption mark at all (there is no book unit to redeem into), so following the toggle would blank half the section on one setting.

  • A debt-only group is not listed. It is reachable (an isolated Morpho market whose collateral row failed to read for more than one tick, or residual bad debt after a seizure; both land on cross-book in pnl.ts), and a bare borrowing under a heading that says "holdings" reads as an asset the wallet owns. An Aave/Spark bare-debt sub-group cannot reach here at all: such an account is a financed position and charts in the section above (M22).

  • The floor is tested per LEG, in that leg's own unit. Same thresholds, same parsing and the same stored setting as the holdings tables, with none of their four never-hide exemptions, which is what the product decision asked for. Per leg rather than on the net because a levered holding's net equity is not its size: an unmapped collateral funded by a large borrow nets to dust while the borrow is live and liquidatable. That is a value test, not an exemption. A leg whose mark could not be READ is not "below" the floor either, since it has no figure to compare; treating a pricing outage as a small number is the M9 failure the app exists to avoid.

  • The settings gear still stands down in this view, and the reveal is component state. The gear's one field is a floor for one denomination and this view is on none. What the floor hides is stated in the section's own header with a Show all beside it — the same "never disagree silently with the count above you" rule the holdings header follows — but pressing it flips a LOCAL flag rather than writing the stored setting. The first cut wrote { USD: "", ETH: "", BTC: "" } through to localStorage, which silently destroyed floors the reader had configured in two denominations this view does not show, with no control on screen to restore them (review finding 1, PR #537). Local state also makes the control reversible in place (Hide small holdings), which the holdings tables' own Show all cannot be: it empties the very field the value would have to be retyped into. Leaving the view drops the reveal, so a floor turned off once to inspect one row does not persist.

  • One section renders, or two, and never an empty shell. Each band renders only when it holds something, exactly as an empty denomination is not a tab. The view itself is offered when EITHER band has content, so it can never come up blank. defaultView is handed that same combined presence, which means a wallet whose only asset is an unmapped token now lands on the one screen that shows it instead of on "No USD positions".

  • The section is MARKET-valued for every group, not only the ones that have no choice. An unknown-asset group has no redemption mark at all, so it could only ever be market-valued. A directional-pair or cross-book group is made of real-book legs that DO carry one, so it could follow the price-basis toggle. It deliberately does not: the band has one column, headed Market value, and a column that silently means two different things depending on which row you are reading is worse than one that always means the same thing. The toggle is a statement about how covered positions are valued, and these are not covered positions.

5. Open questions for review ​

  • Holdings outside coverage are grouped by wallet and ordered by group key inside it, not by size. Ranking by size needs one unit to rank in, which is the same reason crossAssetGroups takes that order. If the section ever grows past a handful of rows, ordering the single-unit groups by value and leaving the mixed-unit ones last would be the next step.

Private documentation. creddit.xyz