Repository navigation
v0.5.2
The product talks about what a person can do, never about what it lacks or cannot do in the main path: a copy-wide audit rewrote the sentences that led with a limitation, and a test now refuses their return. Alongside it, a round of small pieces both apps had quietly written twice (a step's status word, the chart's price-and-date join, a dialog's close label, a phrase-copy warning) now have one shared home, and Home's emptiness check no longer leads with "Add money" over a wallet that still has money in an archived portfolio or in Earn, unread.
Breaking
For both apps:
- Home's emptiness rule:
homeViewtakes a fifth, required argument,archivedHeld: ArchivedHeldState({ holds: boolean; earnUnknown: boolean }). Passholds: truewhile any archived portfolio holds a tracker or money moved into it, andearnUnknown: truewhile any archived portfolio's Earn position has not been confirmed as zero (still loading, or its read failed). Either one now keeps Home from leading with "Add money" over a wallet that is not really empty. The web app can delete its ownhomeOverHeldMoneywrapper andwalletIsEmptyinsrc/components/portfolio/homeState.tsand callhomeViewdirectly with the two booleans it already computes for them. Separately,homeViewno longer treats an unread Earn total (earn === null) as confirmed zero: a wallet whose active-portfolio Earn figure has not come back yet now keeps both buttons instead of assuming there is nothing to show. -
chartReadout(...)answerstext, the price and date already joined ("$100.00 · 14 Jul 2026, 12:00"). The web app'sPriceChartbuilt this join itself (`${readout.price} · ${readout.date}`); read.textand delete the local join. - Removed from the add-money explainer: the card-payments sentence is gone from
portfolioCopy.addMoney.steps.get.detail(see Changed).
Added
Pieces reported by both apps as written twice, now with one shared home (a phone variant only where the wording truly differs):
commonCopy.showLabel,.hideLabel(a reveal control's accessible name),.increaseLabel,.decreaseLabel(a stepper's accessible name),.percentSpoken(a stepper's spoken value),.closeLabel(a close control's accessible name where plain "Close" is ambiguous),.nothingHereYet,.discardThis,.keepEditing,.discard(a sheet's unsaved-changes prompt),.stepStatus(a progress step's status word: waiting, in progress, done, failed, not done),.andList(joins a list the way a sentence does: "SOL", "SOL and USDC", "SOL, USDC and SPYx").commonCopy.backand.closealready covered the plain labels; apps reading a local "Back" or "Close" string should read those instead.marketsCopy.detail.chartHint("Hover to see the price and date.") andmobileMarketsCopy.detail.chartHint("Press and hold to see the price and date."): the one-line hint under a tracker's chart.mobileOnboardingCopy.phrase.wordLabel(position, word), the accessible name for one word of the recovery-phrase grid, and.phrase.copy, the phrase-copy warning (confirmTitle,confirmBody,copyAnyway,copied,copiedNote,failed) a phone warns with before copying a recovery phrase to the clipboard.onboardingCopy.phrase.revealand.hiddenalready covered the reveal button and the concealed word; the mobile app's ownPhraseGrid.tsxduplicates both verbatim today and should read them instead.mobileWalletCopy.protection.refused: what stands in place of a secret (an address, a recovery phrase) the phone could not confirm is kept out of screenshots and recordings.mobileSendCopy.camera: the camera-permission screen that stands in for the scanner (purpose,allow,off,offDetail,openSettings).mobileAppCopy.runtimeFailure: what the phone says when its own security checks fail on startup, before anything else opens (title,detail,configFailure,closing).
Changed
The owner's rule on copy: the product states the working route as steps, and never volunteers a limitation in the main path (onboarding, Welcome, Home, add money, funding, markets, trade, Earn, send). A network a feature does not reach now says where it does run, the way errorsCopy.trade.mainnetOnly already did for Live trading's own refusal message. Every string that changed:
| Where | Was | Is |
|---|---|---|
portfolioCopy.addMoney.steps.get.detail |
"USDC is a digital dollar: 1 USDC = $1. NoirWire cannot take card payments yet. Buy USDC in any app or service that can send it on the {network} network. No account with us is needed." | "USDC is a digital dollar: 1 USDC = $1. Send it from any app or wallet that supports USDC on the {network} network. You do not need an account with us." |
commonCopy.tradingUnavailableOn |
"Live trading is unavailable on {network}. You can explore trackers, but cannot place an order here." | "Live trading runs on Solana mainnet. You can explore trackers on {network}." |
errorsCopy.trade.mainnetOnly |
"Live trading is only available on mainnet." | "Live trading runs on mainnet." |
errorsCopy.earn.mainnetOnly |
"Earning is only available on Solana mainnet." | "Earning runs on Solana mainnet." |
earnCopy.mainnetOnly |
"Earn is available on Solana mainnet only." | "Earn runs on Solana mainnet." |
earnCopy.notHere |
"Earn is available on Solana mainnet only. Nothing can be lent or withdrawn on {network}." | "Earn runs on Solana mainnet. Switch from {network} to lend or withdraw." |
pieCopy.order.tradingUnavailable |
"Live tracker trading is unavailable on {network}. The pie is saved, and can be invested on mainnet." | "The pie is saved. Live tracker trading runs on mainnet; switch from {network} to invest it." |
tests/unit/copyVariants.test.ts gained PRODUCT_LIMITATION_PHRASES and productLimitationsIn, alongside the existing REPLACED_NAMES: a test refuses the card-payments sentence and the "unavailable on" / "mainnet only" / "only available on" / "not supported" phrasings wherever they reappear in the everyday copy (the Privacy and Risks pages are excluded, same as the existing name check).