release: v0.5.3 - #135
Merged
Merged
release: v0.5.3#135
Conversation
Adds bytes_per_sec to DownloadProgress (computed over a 1s rolling window of cumulative-byte samples), and accepts an optional expected_size param preferred over response.content_length() for percent. Both fix the Settings → Updates progress display where percent jumped around and no speed was shown — root cause was unreliable Content-Length on the GitHub asset CDN redirect. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New `UpdateContext` singleton wraps `useUpdateCheck` so a download kicked off by the Dashboard "update available" toast is visible when the user navigates to Settings → Updates. Provider mounted in main.tsx; Dashboard and UpdateSection now consume the context instead of instantiating their own hook copies. - Toast Download action now starts the download AND navigates to /settings with `state.section = 'update'` so the existing progress UI does the showing. - `useUpdateCheck` adds `bytes_per_sec` to the progress payload and an optional `expectedSize` arg passed through to the Rust command. The initial `downloading` state seeds `total_bytes` from `expectedSize` so the byte counters render correctly before the first event. - `UpdateSection` renders speed (formatBytes/sec) and an ETA mm:ss computed from remaining bytes / rate; both hidden when rate is 0. - Dashboard test mocks the new UpdateContext. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous guard set `startupCheckedRef.current = true` in the effect body before the timer fired. Under React.StrictMode (dev-only double mount): first effect set ref=true and scheduled timer A; cleanup cleared timer A; second effect saw ref already true and returned early. No timer ever fired, so the Dashboard "update available" toast never appeared in `tauri dev`. Production builds skip the double-invoke and were unaffected. Move the guard inside the timeout callback so StrictMode's setup → cleanup → setup pattern correctly schedules a fresh timer that runs the check exactly once. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The 40-line scrollable preview is fine for a glance but painful to actually read. Adds an "Expand" button next to the "Release notes:" label that opens a max-w-2xl modal mirroring the DescriptionModal pattern (focus trap, modal stack, Escape, backdrop click). The inline preview stays for quick scanning. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a Cancel button to the Settings → Updates downloading state. The
Rust side reads a shared `Arc<AtomicBool>` flag inside the chunk loop;
a new `cancel_update_download` Tauri command flips it. On the next
chunk boundary, `download_update` drops the file handle, removes the
.download tmp file, and returns `Err("cancelled")`.
The frontend hook recognizes the sentinel: instead of surfacing it as
an error, it restores the prior `available` state (info captured via
effect on every available transition — setState updaters in React 18
can run lazily, so a closure-time capture would still see null).
Without prior info the state falls back to idle.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ownload-flow Fix update toast Download action + accurate download progress
Surfaces non-protocol wallet txs (plain ADA send/receive) so the History tab can show a complete picture of a payment credential's on-chain activity. Reuses the existing HistoryRecord shape; classifies by whether the user appears in inputs (send) or only outputs (receive). Excludes any tx that touches the encryption/bidding contract addresses since those are surfaced by /history. Cached for 60s with stale fallback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends TransactionType with 'send' and 'receive' for plain wallet activity, adds English fallback labels and i18n entries (history.txType.send/receive) across all 17 locales. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds chainApi.getWalletActivity, fetches it in parallel with the protocol history during recovery, and dedupes by txHash before reconciling. The filter dropdown now exposes Sent ADA / Received ADA so users can scope the list to plain wallet movement. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Refresh button only re-queried current encryption/bid UTxOs, so an incoming receive (or outgoing send) wouldn't appear unless the user also hit Recover. Activity is cached server-side for 60s, so calling it on every refresh is cheap and matches the user's mental model: refresh shows new things. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ults Two related fixes: 1. Tauri loads Express from src-tauri/resources/be/dist/, not be/dist/. Backend changes were silently invisible until a manual `npm run bundle:be` refreshed the bundled copy. run.sh now does this automatically before `tauri dev`. CLAUDE.md updated to flag the same trap for future readers. 2. The activity endpoint cached empty results for 60s, which masked newly indexed receives. Empty responses are now uncached, and a structured log line on the empty path makes Koios-vs-filter issues debuggable from the server log. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Koios /tx_info responses use 'tx_timestamp' (UNIX seconds), not 'block_time'. The existing classifyTx and the new classifyActivityTx both did 'tx.block_time * 1000', which is NaN when block_time is undefined and JSON-serializes as null. Why this matters for the History tab: reconcileWithOnChain in fe/src/services/transactionHistory.ts sorts records descending by timestamp and slices to 100. With null timestamps coercing to 0, every wallet activity record sorted to the very end and got truncated when local + on-chain rows exceeded 100 — so receives never appeared. Date-range filters also dropped them (null >= cutoff is false). Now reads 'tx_timestamp ?? block_time ?? 0' so both endpoint shapes work and the existing /history records also get real timestamps for the first time. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
reconcileWithOnChain only added new records; it never updated existing ones. So once a tx had been persisted with a stale field (e.g. timestamp 0 from the now-fixed block_time bug), later reconciles dropped the better on-chain data on the floor. The receive at the top of /api/chain/activity sat at position 73+ in the History tab because the localStorage copy still held timestamp 0. Now backfills timestamp / amountLovelace / counterparty / confirmedAtBlock on existing records when the local copy is missing those values. Two new tests cover the upgrade-from-stale-zero and the backfill paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The History tab list lives in its own scroll container (max-height calc(100vh - 320px)), so the global ScrollToTop never appeared no matter how far you scrolled inside the list. ScrollToTop now optionally accepts a scrollContainer ref and binds to that element's scroll/scrollTo when provided, falling back to window otherwise. HistoryTab hoists the virtualizer's parentRef to the tab and passes it to both the virtualizer and the button so the same scroll element drives both. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…y-monitor Wallet activity monitor in History tab
Adds two new CSV formatter functions for tax-reporting exports: salesToCSV joins seller listings with their bidsMap (highest pending bid + accepted-bid sale amount) and purchasesToCSV joins buyer bids with the encryption map for the seller PKH. Status labels follow the draft spec (Open/Pending/Sold for sales; Active/Locked/Won/Cancelled/ Rejected for purchases). 21 new unit tests cover empty inputs, status mapping, missing optional fields, and CSV escaping. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds an Export-as-CSV button to the MySales toolbar (next to Refresh) that runs the currently filtered listings through salesToCSV and saves via the native dialog. File name is veiled-sales-YYYY-MM-DD.csv. Reuses the history.exportTitle/Aria/edTo/Failed translation keys — no new locale strings required. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mirrors the MySales export wiring: an Export-as-CSV button in the purchases toolbar (after Refresh) runs the currently filtered+sorted bids through purchasesToCSV and saves via the native dialog. File name is veiled-purchases-YYYY-MM-DD.csv. Reuses the history.exportTitle/Aria/edTo/Failed translation keys. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mocks salesToCSV/purchasesToCSV and exportTextFile, then asserts:
button is absent in the empty state, present and enabled when there
is data, and clicking it calls exportTextFile with a
veiled-{sales,purchases}-YYYY-MM-DD.csv filename.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous export only covered live bid UTxOs, so once a buyer decrypted their winning bid the row vanished from the CSV — useless for tax records. Now the export also emits one row per re-encryption UTxO the user owns or has since resold (Won/Resold), with the bid amount recovered from local tx history (place-bid records keyed by bid token name, captured during fetchData via bidSecretStorage). Rows dedupe against active bids on encryptionToken to skip the brief accepted-but-not-completed window. The export button moved to a top-level action row so it stays visible when only purchased encryptions remain. New tests cover the dedupe path, Won/Resold status mapping, and bid-amount recovery. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the local-state salesToCSV / purchasesToCSV exports with
a single tax-records report sourced from the chain. The unifying
insight: every taxable event in PEACE is a re-encryption tx — the
post-SNARK transaction that consumes the bid UTxO, transfers the
encryption to the buyer, and pays the seller. That tx alone carries
the bid datum, the encryption datum, and the actual lovelace
exchanged, so it's the canonical event for both sides of the deal.
Backend: GET /api/chain/reencryption-history/:pkh intersects encryption
and bidding contract activity to find re-encryption candidates,
fetches tx_info, and emits one event per tx where the user was
either the bidder (output encryption datum's owner_vkh, parsed via
the existing parseEncryptionDatum) or the seller (first non-contract
input cred — the seller signs and provides fees). bidAmountLovelace
is read directly from the consumed bid input value;
futurePriceLovelace from the new encryption datum's new_price.
Re-sales by other users are filtered out. Cached for 60s, capped at
200 candidates per query, with stale-cache fallback.
Frontend: chainApi.getReencryptionHistory + reencryptionHistoryToCSV
produce one CSV with columns Date, Side (Sale|Purchase), Token Name,
Bid Amount (ADA), Future Price (ADA), Seller PKH, Buyer PKH, Tx
Hash, Block Height. Both MySales and MyPurchases tabs trigger the
same export (filename veiled-tax-records-{date}.csv), with the
button moved to a top-level row that stays visible in the empty
state — historical re-encryption events remain accessible even
when the user has no current listings or bids. The local
transactionHistory match (capped at 50 records) and CompletedPurchaseRow
machinery are removed; chain data is the source of truth.
Tests: 7 new backend tests cover Sale/Purchase classification, re-sale
exclusion, empty intersection, sort order, validation, and 503 path.
Frontend tests verify the button is reachable from the empty state and
that clicking it queries the chain endpoint and saves the unified CSV.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the user reported the tax CSV came back empty, the code couldn't say *why* — backend errors were swallowed in chainApi, the extractReencryptionEvent helper returned bare null on every skip path, and contract inputs were matched only by payment_addr.bech32 (which Koios doesn't always populate on inputs, in which case every candidate silently dropped to "no bid input found" and the array came back []). Backend (chain.ts): - isBiddingInput / isContractInput now match by asset policy first (bid UTxOs carry biddingPolicyId, encryption UTxOs carry encryptionPolicyId — assets are reliable on inputs even when bech32 is missing) with bech32 retained as a fallback. - extractReencryptionEvent returns 'no-bid-input' | 'no-seller-input' | 'no-buyer-output' | 'parse-fail' on skip so the route's summary log can attribute every dropped tx. Datum parse failures also log individually with the offending tx hash. - Route logs candidate-scan counts (encryption/bidding tx totals, intersection size) and extraction summary (fetched, extracted, per-reason skips, matchedUser) — visible in `bash run.sh` console. Frontend: - chainApi.getReencryptionHistory now throws on backend failure (was silently returning []). Tab handlers already wrap in try/catch and surface "Export failed" via exportMessage. - Both tab handlers log the event count to console.info before formatting, and show "No completed re-encryption events found on chain yet." when the array is empty — distinguishes empty-result from a failed call. No test changes needed: the fixtures already populate asset_list with contract policies, so the new asset-policy path is exercised by the existing happy-path tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The contract address_txs intersection silently returned 0 candidates
when /address_txs paginated past the user's actual activity, leading
to empty CSV exports for users with multiple completed purchases. The
fundamental problem: scanning the whole chain blindly when we already
know which encryption tokens the user has bid on (locally) and which
txs they signed (from credential history).
Backend (POST /api/chain/reencryption-history/:pkh now takes
{ encryptionTokens: string[] } in the body):
- Buyer side: for each encryption token the user has bid on, walk
its full asset history via /asset_txs. The user's purchase
re-encryption is in there regardless of whether they still own the
token (covers Won and Resold cases). Per-token failures are
logged and skipped, never abort the whole query.
- Seller side: getCredentialTxs(pkh) returns every tx the user
signed; the seller signs the re-encryption, so this captures all
sales (including resales of items previously bought).
- Hash sets are deduped before the tx_info batch fetch, so a tx
that surfaces on both sides costs one extraction. Cap at 500.
Reuses the existing getAssetTxs (originally added for decryption
levels) — I had nearly duplicated it; removed the duplicate.
Frontend:
- chainApi.getReencryptionHistory now POSTs and takes
encryptionTokens. Both tabs gather them via listBidSecretTokens()
before exporting (returns [] when locked or empty — handled).
- MySalesTab adds the bidSecretStorage import (already present in
MyPurchases).
Tests rewritten to drive the new shape: getAssetTxs and
getCredentialTxs mocks, POST + body, dedup-across-sources test,
graceful per-token-failure test. Token name fixtures use hex
matching the route's validation regex.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The 503 came from the outer try/catch around getTxInfoWithAssets — Koios's POST /tx_info caps at ~50 hashes per call, but a heavy user's getCredentialTxs(pkh) plus 6 tokens of asset history easily exceeds that. Now the batch is split into chunks of 50 with per-chunk try/catch, so a single oversized chunk (or one transient Koios hiccup) only logs a warn and the rest of the export still goes through. The 503 response also now includes `detail` with the underlying error message (cause logged with stack to backend already), and apiFetch propagates code+message+detail in the thrown Error so the WebView console shows what actually failed instead of a generic "Unable to fetch re-encryption history". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two changes to make the empty-CSV case actually diagnosable from the WebView console alone, no BE log spelunking: 1. Backend response now includes `meta` with candidate counts for every successful query: encryptionTokens, buyerSideHashes, sellerSideHashes, candidates, fetched, extracted, per-skip-reason counts, matchedUser. Both tab handlers log the whole block via console.info, and the empty-result toast now reads e.g. "(43 candidates, 12 re-encryption txs found, 0 matched your wallet)" so the failure mode is visible without restarting the BE. 2. PKH equality check is now lowercase on both sides. Hex is case-insensitive but JS string === isn't — if Koios ever returns uppercase creds while the wallet provides lowercase (or vice versa) every row would silently drop. Cheap insurance. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diagnostic meta from a real wallet showed 14 candidate txs had bid inputs but my "first non-contract input cred = seller" heuristic couldn't find a wallet input on any of them, dropping the entire buyer's purchase row in the process. PEACE re-encryption txs evidently don't always carry a wallet-fee input cred in tx_info (possibly paid via collateral or aggregator pattern). Two changes: 1. Switch the primary seller detection to outputs: the seller is the only party who *receives* the bid lovelace, so a non-contract output whose value covers bidAmountLovelace is them. The input-based heuristic stays as a fallback for txs where outputs are split or aggregated. 2. Drop the hard requirement on sellerPkh — extraction now succeeds with sellerPkh = '' when neither strategy works. The buyer's tax row is the load-bearing one; missing seller is acceptable for purchases. Sales-side rows still need sellerPkh to match the user's PKH for inclusion, so the filter is unchanged for that direction. Meta now reports `extractedNoSeller` (replacing the dropped `skipNoSellerInput` counter) so the volume of seller-blank rows is visible in the diagnostic output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The re-encryption-history extractor was returning 0 events because Koios /tx_info silently strips inline_datum from outputs whose datum exceeds its parser size cap (encryption datums carry BLS12-381 G1/G2 points + Capsule, well over the cap). When Koios kept the wrapper (address, asset_list) but dropped the datum, the extractor counted the output but couldn't parse it. Two compounding bugs fixed: 1. Identify the encryption output by Koios's explicit `tx_index` field, not by array position. Array order can drift from on-chain tx_index when collateral_outputs are present, leading to refs pointing at the seller's wallet payment instead of the encryption UTxO. 2. When inline_datum is stripped from /tx_info, batch a second-pass /utxo_info call (`_extended: true`) which reliably returns inline_datum.bytes for any size, then decode via decodePlutusData. Also: - Parallelize per-token getAssetTxs (was sequential, pushed heavy bidders past the 30s request timeout). - Allow 120s for /reencryption-history specifically — the multi-Koios- roundtrip walk legitimately needs more headroom than 30s. - Split the overloaded `no-buyer-output` skip reason into specific buckets (no-encryption-output, encryption-output-no-datum, encryption-output-no-buyer-pkh, encryption-output-no-token-name) so future support can diagnose empty exports from the meta alone. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 'Your bid will be locked... Why X ADA minimum?' hint sat 4px (mt-1) below the input, which read as cramped against the input border. Bump to 12px (mt-3) so the hint reads as a separate explanatory line rather than a tail attached to the input. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the single phosphor-green accent with three semantic colors so the
visual hierarchy is no longer carried by one shouting hue:
--accent muted lilac #9683c8 primary actions, focus rings, hover
borders, active tab indicator
--accent-text soft mint #7dd3a3 static values: prices, balance, stats
counts (text/icons only, never fills)
--link warm sand #d4a574 inline navigational links (URLs,
"View full →" affordances)
Light theme gets darker variants for AA contrast on bright backgrounds.
tx-celebration glow switches to mint to match the "verified / completed"
semantic family. Status colors (success/warning/error) untouched.
JSX sweep applies each role:
- prices, wallet balance, stats counts → accent-text
- bid count pill on EncryptionCard → accent-text bg + text
- TransactionLink (CardanoScan), Iagon docs, "Select all" → link
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two changes addressing the "Halloween vibe" feedback: 1. Drop --accent-text (soft mint). Static values — prices, balance, stats counts — now use --text-primary (white). Typography weight (font-semibold + tracking-tight + tnum) carries the "this is a hero number" hierarchy without color. Removes the green-next-to-lilac proximity that was reading as Halloween, while keeping the green energy reserved for status badges and the brief tx-celebration glow. 2. Bolder --accent: #9683c8 → #8b6dd6, and --accent-hover bumped from #b3a3d6 to #a78bfa so the hover state is clearly brighter. btn-primary now also picks up the shadow-glow on hover so it reads as pressable. tx-celebration glow switches to --success (green) since mint is gone. Brand-emphasis pills (wallet balance, bid count) still use lilac accent-muted bg + accent text. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four small follow-ups picked up during manual review of the polish pass: - Wrap .t-* typography classes in @layer components so per-instance Tailwind text-* utilities (text-3xl, text-lg, etc.) override the role defaults instead of losing on source-order. Switch the wordmark (WalletSetup, WalletUnlock), onboarding step title, and EmptyState title from inline `style={{ fontFamily: 'var(--font-display)' }}` to the .t-display class. Activates the previously-dead type tokens. - Apply .field-invalid pattern to UpdatePriceModal and CreateListingModal price inputs (was only used in PlaceBidModal). Also adds tnum to both so digits don't wiggle on input. - Theme tx-celebration glow via a new --tx-glow CSS variable instead of hardcoded rgba — light theme now gets a slightly dimmer glow that reads correctly against bright surfaces. - Drop light-theme atmospheric noise opacity 0.04 → 0.022. The brighter the surface, the more visible the grain — at 0.04 it was reading as dirt on white card backgrounds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LibraryCard's no-image fallback rendered a bare 160px panel with no margin or rounded corners, while ListingImage size="md" (used when an imageLink is present) renders 160px + my-4 (32px) + rounded-md. The grid's cards-with-images were ~32px taller than cards-without, breaking the equal-height row that MarketplaceTab and MySalesTab already get. EncryptionCard and SalesListingCard avoid this by always rendering ListingImage and letting it handle the no-link branch internally (same h-40 + my-4 + rounded-md). LibraryCard needs its custom CategoryIcon fallback though, so instead align the wrapper styles to match ListingImage's no-link branch exactly. Drops the cardSize-based imgHeight variable (h-28 / h-40 / h-52) since ListingImage at size="md" is hardcoded h-40 anyway — cardSize was only varying the no-image path, which was the source of the inconsistency. cardSize still controls inner padding and description line-clamp. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…splay face Three small alignments to the four shell stats cards: - Active (selected) card now also gets the -translate-y-0.5 lift, not just the lilac glow. Hover and active had two different effects (lift vs glow) which read inconsistently. Active now does both — lifts AND glows — so it stays clearly distinct from hover (which only lifts). - Apply card-stagger entrance (0/50/100/150ms cascade) to match the pattern already used by every Marketplace/Library/MySales/MyPurchases card. Shell now feels like a single coordinated reveal on dashboard load instead of four cards popping in instantly. - Promote the four hero count numbers to the .t-display class — activates the display face (Bricolage Grotesque, falls back to Inter) on the most prominent numbers in the app shell. Drops the redundant font-semibold + tracking-tight since .t-display sets both. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…utton wrapper The previous "View full" affordance was the entire description block wrapped in role=button + tabIndex + onKeyDown. Screen readers announced the whole description text as a button, and the caret only became visible on mouse hover (invisible to keyboard users). Replace with the conventional pattern: - <p> stays plain — screen readers hear it as paragraph text - The "View full →" caret becomes a real <button> that is always visible, keyboard-focusable, and has its own aria-label - The wrapper <div> keeps onClick + cursor-pointer for the mouse convenience of clicking anywhere on the description, but no a11y role/tabIndex — the inner button carries the semantics Net result: "Description text. View full description, button." instead of "View full description, button: <whole description>". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tern
Audit of the marketplace surface (MarketplaceTab + EncryptionCard +
filter helpers + PlaceBidModal) found the existing role split is
already followed correctly — no reassignments needed. PlaceBidModal's
"Listing Details" rows even use a refined three-tier hierarchy in one
line: muted label, secondary technical value (mono hash), primary
decision value (price).
Capture that hierarchy as the reference pattern in the token doc so
future code follows it. Also extend the descriptions to call out the
border cases ("readable-but-secondary values" for hashes, "inactive
chip states" for muted) that the audit confirmed.
No code changes — pure documentation update against the now-clean
marketplace surface.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the signature tx-celebration moment into wallet creation success.
Previously the user clicked "Create wallet" → wallet was minted → they
were navigated straight to Dashboard with no acknowledgement of the
milestone.
Now WalletSetup passes navigate state { justCreated: true }, and
Dashboard's existing nav-state effect fires a celebrate toast on first
mount and clears the state (so back-button doesn't re-celebrate).
To keep the API clean, add toast.celebrate(title, message?, action?)
to useToast — wraps addToast with variant='transaction' so the icon
gets the tx-celebration-icon animation. transactionSuccess() stays
reserved for actual on-chain tx success (it requires a txHash and
adds a CardanoScan link).
Translation keys use defaultValue fallbacks so the moment ships in
English now and can be localized in a separate i18n PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…h-pilot Frontend polish pilot — Marketplace, Library, Wallet, primitives
Promote 5 strings from defaultValue fallbacks to proper keys:
- dashboard:shell.walletCreatedTitle / walletCreatedBody
- card.openDescription / card.viewFull
- toast.queueOverflow (preserves {{count}} interpolation)
Other 17 locales pick these up via the translation backfill flow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The polish pass left three template-literal aria-labels with hardcoded
English suffixes ("new bids", "accepted bids ready to decrypt",
"pending transactions"). Visually invisible — only screen-reader users
saw the English. Replace with i18next plural keys (_one / _other) so
the aria-label translates and reads grammatically at count == 1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add no-restricted-syntax ESLint rule that flags string literals and
template literals in user-facing JSX attributes (aria-label,
aria-description, aria-roledescription, alt, title). Test files are
exempted; placeholder is intentionally not covered (numeric-only in
this app, locale-agnostic).
Backfill the 5 call sites the new rule surfaced:
- LayoutPopover card-size + column-count buttons (4 sites) now look up
layoutPopover.cardSize{Small,Medium,Large} and the new
layoutPopover.columnsCount plural key.
- StorageSection disk-usage segment tooltip uses storage.segmentTooltip
with {{label}}, {{size}}, {{percent}} interpolation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
formatAda + formatDate read i18next.language from the singleton and
pass it to toLocaleString / toLocaleDateString instead of `undefined`,
so number grouping and date format follow the user's selected app
language rather than the OS default. Falls back to undefined when
i18next isn't initialized — keeps unit tests stable.
formatRelativeTime swaps its hardcoded English buckets ("just now",
"5m ago", "2y ago") for Intl.RelativeTimeFormat with style:'narrow'.
English narrow CLDR matches the prior strings exactly except
"just now" → "now"; non-English locales now get their proper CLDR
forms (e.g. "5分钟前" instead of "5m ago"). Uses numeric:'auto' under
60s for the "now" form and numeric:'always' above so "1mo ago" / "1y
ago" don't collapse to "last mo." / "last yr."
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…polish-keys i18n: backfill EN keys for polish-pass defaultValue strings
Centralize Stripe-style thousands-separator formatting helpers in formatAda.ts so currency-style inputs across the app can share a single implementation. formatWithCommas preserves the user's decimal portion verbatim (including trailing dots and trailing zeros) so it is safe to use during typing as well as on blur. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switches the bid amount and future-price inputs from type=number to type=text + inputMode=decimal so they can render thousands separators at rest. Adds Stripe-style format-on-blur / parse-on-focus driven by the shared formatWithCommas/stripCommas helpers, plus a setBidAmount / setFuturePrice indirection so the Suggested / +10 / +25 / Max quick actions also update the formatted display. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire UpdateBidModal's bid-amount and future-price inputs through the shared formatWithCommas/stripCommas helpers so they render thousands separators at rest and revert to a comma-free, fully-selected raw string on focus. Auto-sync of the future-price field now mirrors the display value too. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire UpdatePriceModal's listing price input through the shared formatWithCommas/stripCommas helpers so it renders thousands separators at rest and reverts to a comma-free, fully-selected raw string on focus, matching the Place/UpdateBid modals. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the local formatPrice helper (Intl.NumberFormat-based) with
the shared formatWithCommas so all currency-style inputs use the same
formatting. Behavior tightens slightly: trailing zeros the user typed
("1.10") survive the round-trip instead of being trimmed to "1.1".
Also select-all on focus to match the bid/price modals.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ImportListingModal carried its own copy of the local formatPrice helper (Intl.NumberFormat-based, identical to the one CreateListingModal had). Swap it for the shared formatWithCommas and select-all on focus so all five currency-style inputs in the GUI use the same helpers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Card width depends on the column-count grid setting, but the price font size was tied only to cardSize. Picking large + 4 columns gave a big font in a narrow track, and a 21-character max-supply price like "45,000,000,000.00 ADA" overflowed the card. Wrap each card's <article> with @container and drive the price font off the card's actual inline size. cardSize still acts as the user's preferred tier — it caps the maximum font reached on a wide card — but a narrow card always starts at text-base so the longest possible ADA value fits regardless of column count. Apply across EncryptionCard, SalesListingCard, MyPurchaseBidCard. A new shared getPriceFontClass(cardSize) helper sits next to getGridClasses in useTabFilterState since they're both card-grid layout concerns. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On the narrowest MySales card (large + 4 columns at xl viewport, the
article runs ~280px wide) the inline edit pencil inside the price plus
a longer storage-layer badge ("On-chain") wouldn't fit on the same row
as the price, even after the container-query font ramp. Both elements
were whitespace-nowrap + flex-shrink-0, so the row overflowed and the
price visually hugged the right edge of the card.
Switch the price/badge row to flex-col below @xs and back to flex-row
above it, so wide cards keep the original side-by-side hero layout
while narrow cards stack cleanly without overflow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nput-commas D16: thousands separators on bid/price number inputs
Adds @fontsource-variable/bricolage-grotesque (variable wght+opsz, latin subset, ~131KB) and ships the woff2 in public/fonts/. Uncomments the @font-face block in fonts.css so .t-display now renders in Bricolage Grotesque instead of falling back to Inter. Affected hero spots: WalletSetup title, WalletUnlock title, OnboardingOverlay step titles, EmptyState titles, Dashboard stats counts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Inter and JetBrains Mono woff2 files in public/fonts/ were dropped in without a tracked npm dep — Bricolage was the only font with provenance after the previous commit. Add @fontsource-variable/inter and @fontsource-variable/jetbrains-mono so all three fonts are sourced the same way and can be regenerated from package.json. Inter files are byte-identical to the existing copies (no functional change). JetBrains Mono files swap to the variable wght-normal version (+12KB total across both subsets, gains the full 100-800 weight range). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…age-display-font Install Bricolage Grotesque display font
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Release v0.5.3 — desktop polish, history & activity, and updater UX.
/api/chain/activity/:pkh(Wallet activity monitor in History tab #129); CSV export of historical sales and purchases for tax records (Sales/purchase CSV export for tax records #130)--text-*tokens, wallet welcome treatment (Frontend polish pilot — Marketplace, Library, Wallet, primitives #131)formatAda/formatDate/formatRelativeTime, lint guard against hardcoded a11y strings, EN-key backfill (i18n: backfill EN keys for polish-pass defaultValue strings #132)@fontsource-variable(Install Bricolage Grotesque display font #134)Notable fix:
reconcileWithOnChainnow updates existing records' fields from on-chain instead of insert-only — closes a long-silent stale-field bug.Full notes: CHANGELOG.md
Version bumped in lockstep across
tauri.conf.json,Cargo.toml/Cargo.lock, root + fe + bepackage.json/package-lock.json, andaiken.toml.Test plan
cd app/contracts && aiken check— all contract tests passcd app/snark && go test ./... -count=1— gnark suite passescd app && python -m pytest -s -vv— Python CLI suite passescd app/gui && bash test.sh— fe + be vitest passcd app/gui && bash lint.sh— eslint, tsc, clippy, cargo fmt cleancd app/gui && bash build.sh— production AppImage builds🤖 Generated with Claude Code