Release: mobile/UX foundations, governance vote UX + DB-cached tallies, signing & infra fixes#303
Conversation
…NNSESSION) Production was 500ing every query with "(EMAXCONNSESSION) max clients reached in session mode - max clients are limited to pool_size: 15". Verified root cause (live + multi-agent audit): - src/server/db.ts created the @prisma/adapter-pg pool with no `max`, so node-postgres defaulted to 10 connections per warm Vercel instance. A couple of instances overrun Supabase's session-mode pool (15 client slots) -> EMAXCONNSESSION on every query, including user.createUser. - The retry wrapper amplified it: it called $connect() against the dead pool between retries and, once connectionTimeoutMillis is finite, would treat the "timeout exceeded when trying to connect" acquire error as a retryable connection error. Changes: - Cap the pool: max: 2, idleTimeoutMillis 10s, connectionTimeoutMillis 10s (finite timeout fails fast instead of pg's default infinite wait). - isConnectionError(): never retry pool-saturation errors (max clients reached / pool_size / EMAXCONNSESSION / connect-timeout). - Drop the $connect() reconnect between retries (the driver-adapter pool reconnects lazily; forcing connect just adds load). The Prisma globalThis singleton was verified correct and left unchanged. The 5 interactive $transaction blocks are pure-DB (no external I/O held), so no leak fix is required for this to hold. NOTE (maintainer action, env — cannot be done in code): point production DATABASE_URL at the Supabase TRANSACTION pooler (port 6543, ?pgbouncer=true) and keep DIRECT_URL on the direct connection (5432) for migrations. The session pooler (5432) is the wrong mode for serverless; this code change is the necessary client-side cap and works as an interim mitigation too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Break grouped quarters into individual Month 2–12 sections, each with Quirin/Andre task tables matching Month 1's format - Add Document Sign-Off flagship feature (MVP→v1→v2→v3) woven across months - Add Mesh 2.0 upgrade; extend FROST research to include Lemour PQC multi-sig - Shift schedule one month earlier (June work completed); April is buffer - Drop completed items (Aiken crowdfund, full address, pagination, collateral, 404) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs(roadmap): month-by-month breakdown with per-owner tasks
…e signature mismatch DRep votes (and any tx with Conway voting_procedures) failed client-side with "Wallet returned witness that does not verify against tx body hash", and would be rejected on-chain (InvalidWitnessesUTXOW). Root cause: the tx is BUILT with core-cst (MeshTxBuilder's default CardanoSDK serializer), so the wallet signs the body hash core-cst produces. But the witness verify + merge used core-csl (whisky) calculateTxHash / Transaction reconstruction, which re-serializes the body to different bytes (voting_procedures map order, set tag 258) → a different hash → valid witnesses fail to verify. The two serializers agree for ordinary txs, so only Conway-vote-shaped txs broke. Move the verify/merge/hash onto core-cst (the 2.0 stack, already a dependency), so build and verify use one encoder and the original body bytes every signer signed are preserved: - mergeSignerWitnesses: verify new witnesses against resolveTxHash(originalTx) and merge via addVKeyWitnessSetToTransaction (preserves body bytes). Drops the body-swap workaround — every co-signer now signs the same stored body. - filterWitnessesToScripts: rebuild the witness set with core-cst so dropping extraneous vkeys no longer re-encodes the body. - diagnoseTxWitnesses + the server signature check in transactions.ts: hash with resolveTxHash so vote signatures are recognised (equal to the old hash for ordinary txs). Verified invariants (tests): adding/filtering vkeys via core-cst preserves resolveTxHash; a witness over resolveTxHash verifies; co-signers accumulate without re-encoding. tsc clean; full suite 362 passed. Follow-up (not in this PR): the server v1 bot path (signTransaction.ts + addUniqueVkeyWitnessToTx) still uses core-csl calculateTxHash and needs the same core-cst migration for bot-submitted votes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(governance): verify/merge witnesses with core-cst (DRep-vote signature mismatch)
…sets Foundational mobile fixes (PR 1 of the UX/mobile quick-wins pass): - viewport meta gains viewport-fit=cover so env(safe-area-inset-*) resolves (without it the insets are always 0). - Full-height containers use 100dvh instead of 100vh/h-screen so the layout isn't clipped by mobile-Safari / wallet-webview dynamic toolbars (_app, layout root + inner content column). - Main header grows by the safe-area top inset on mobile so it clears the notch/status bar, and honors side insets in landscape. - Mobile nav drawer offsets by the safe-area top, uses 100dvh, and pads the bottom for the home indicator. - Bottom Sheet variant pads the bottom safe area. Desktop is unchanged (insets are 0; header rule is scoped below md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR 2 of the UX/mobile quick-wins pass. Buttons/inputs were 32-36px, below the recommended 44px touch target. A single @media (pointer: coarse) rule enlarges interactive controls on touch devices only — no per-call-site edits and zero change to desktop density. Plain text links are intentionally excluded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…loading/empty conflation PR 3 of the UX/mobile quick-wins pass. - Add shared `Skeleton` (shadcn) and `EmptyState` (Card-based) primitives. - Wallet detail routes (info/transactions/governance/assets) rendered a blank fragment while `appWallet` loaded — replace with a `WalletDetailSkeleton` so there's no white flash on every wallet open. - all-transactions: it showed "No transactions yet" *while still loading* (undefined === loading). Split into a skeleton (loading) vs an `EmptyState` (loaded and empty). - proposals: plain-text "No proposals found" → `EmptyState`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR 6 of the UX/mobile quick-wins pass (input correctness).
- Recipient address fields used invalid type="string" with mobile autocorrect/
autocapitalize active — which can silently corrupt case-sensitive bech32
addresses. Now type="text" with inputMode="text" autoCapitalize="off"
autoCorrect="off" spellCheck={false}.
- Amount fields get inputMode="decimal" (numeric keypad on mobile).
- Base Input uses text-base on mobile (sm:text-sm on desktop) so iOS Safari
no longer zooms in on focus (<16px triggers it).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(mobile): viewport-fit + dvh + safe-area insets (foundations)
feat(mobile): >=44px touch targets on coarse-pointer devices
feat(ux): Skeleton + EmptyState primitives; fix blank pages & loading/empty bug
fix(mobile): bech32-safe address inputs + decimal keypad + no iOS zoom
… + scroll) PR 4 of the UX/mobile quick-wins pass. Centered Dialogs overflowed small screens — content (and action buttons) got clipped off-screen, especially the wide governance modals. Make the base DialogContent mobile-safe without changing desktop layout: w-[calc(100%-1.5rem)] (stay within the viewport with a small margin), max-h-[90dvh] + overflow-y-auto (scroll internally instead of off-screen). The max-w-lg cap and centered position are unchanged at desktop. BallotModal and RegisterDrepModal switch their max-h from vh to dvh so the mobile toolbar doesn't hide the bottom. (Kept dialogs centered rather than converting to a bottom sheet to avoid restructuring positioning on the shared base that backs every dialog; the overflow fix is the critical part. Needs a quick on-device / Chrome-MCP visual check before promoting past preprod.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…egister
PR 5 of the UX/mobile quick-wins pass.
- src/utils/errors.ts `getFriendlyError(error)` maps the common raw errors
(CIP-30 {code:-2}, account-changed, 429/too-many-requests, insufficient
funds, Blockfrost/UTXOS, user-decline) to short human messages, falling back
to the raw message.
- src/utils/toast-error.ts `toastError(error, title?)` — destructive toast with
the normalized message (additive; doesn't touch the TOAST_LIMIT=1 reducer).
- Adopt in DRep registration's catch (raw e.message -> getFriendlyError), while
keeping the "Copy Error" action for raw debug details.
Other error-prone flows (new-transaction, WalletAuthModal) can adopt the
helper incrementally.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(mobile): keep dialogs within the viewport (responsive width + dvh + scroll)
feat(ux): friendly-error helper + toastError wrapper
The transactions pagination bar overflowed its card on the right and used hardcoded dark colors + a raw <select>. Rewrite the body (props unchanged): - container: themed `border bg-card`, `flex-wrap min-w-0 gap-3` so the groups reflow instead of spilling off the right edge. - sort toggle: outline Button, icon-only on mobile (label hidden < sm). - page size: shadcn Select (was a native unstyled <select>); same options + reset-to-page-1 behavior. - nav: compact icon-only prev/next (h-9 w-9), muted tabular-nums page indicator; rely on Button's native disabled styling. PaginationProps is unchanged, so both call sites (transactions + DRep list) behave identically. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The on-chain history rows (desktop table + mobile cards) led with the raw
truncated tx hash — the least human-meaningful field — while the actual
description ("Ballot Vote: …") was buried below. Flip the hierarchy to match
the pending-tx cards:
- primary: the dbTransaction description, or a "Sent"/"Received" fallback (and
the cert label on desktop) so rows without a DB record aren't identified by
a bare hash.
- date below.
- hash demoted to a quiet muted mono link with the external-link arrow.
Also standardize the hash truncation on getFirstAndLast(hash, 8, 8) (was two
different inline substring schemes) and drop the now-redundant standalone
description block on mobile. Cardanoscan links, outputs, certs, signers, and
the row actions menu are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Long token names ($drep.collective) and raw quantities pushed the value/ticker past the card's right edge (the clipped "↗1 $S…"). Root cause: the flex row + left group had no min-w-0, so text couldn't shrink, and the name/quantity/ticker were never truncated. - min-w-0 on the row + flex-1 min-w-0 on the left group (lets text shrink). - 60px avatar wrappers flex-shrink-0 (so the image never compresses instead). - name h3 truncates (+ title tooltip) and is bounded via truncateTokenSymbol for the raw-unit hex fallback; remove ml-auto from the link. - value block flex-shrink-0; quantity via numberWithCommas (tabular-nums); ticker truncates with a max width + title. Full name stays reachable via tooltip + the Cardanoscan token link. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ry per action
The per-proposal voting controls were confusing: a raw OS <select> for the
Yes/No/Abstain choice, plus TWO competing actions (a green "Add to Ballot"
button in proposals.tsx AND a mystery ballot icon in VoteButton).
- Replace the native <select> with a shadcn segmented Yes/No/Abstain control
(color-coded selected state, icons). Guard `(v) => v && setVoteKind(v)` so a
re-click can't blank the vote read into the tx.
- One clear primary: the Vote button now states the choice ("Vote Yes",
"Vote Yes (Proxy)") and uses the themed primary style.
- One ballot entry: the icon-only ballot button becomes a labeled "Add to
ballot" / "In N ballots" secondary, with a tooltip distinguishing the two
flows (vote on-chain now vs collect for co-signers).
- Remove the now-duplicate green "Add to Ballot" buttons (mobile + desktop) in
proposals.tsx; keep the View links.
vote()/voteProxy() bodies, proxy path, keepRelevant, metadata label 674, the
closed-vote Lock state, and all toasts are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The proposal meta row showed the governance action type as plain uppercase
text ("TREASURY WITHDRAWALS"), visually indistinguishable from the other meta
fields and hard to scan in a long list.
Add a GovernanceTypeChip that renders each Conway action type as a color-coded
outline Badge with an icon:
- treasury_withdrawals amber Coins
- info_action blue FileText
- parameter_change purple Settings2
- hard_fork_initiation orange GitBranch
- no_confidence red XCircle
- new_constitution teal FileText
- new/update_committee indigo Users
- unknown slate Hash (Title-Cased fallback label)
Used in both the mobile and desktop meta rows (single shared component, no
view drift). Purely presentational — no data or vote-flow changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(ux): themed, non-overflowing, mobile-friendly pagination
fix(ux): lead transaction rows with the label, demote the hash
fix(assets): stop token rows overflowing the Assets card
# Conflicts: # src/components/pages/wallet/governance/proposals.tsx
# Conflicts: # src/components/pages/wallet/governance/proposals.tsx
feat(governance): color-coded proposal type chips
# Conflicts: # src/components/pages/wallet/governance/proposals.tsx
feat(governance): clearer ballot voting UX (segmented vote + one entry per action)
SEO: - Central src/lib/seo.ts; route-aware <Metatags> (titles, canonical, OG, Twitter, robots noindex, JSON-LD) hoisted out of the ssr:false boundary - /robots.txt + /sitemap.xml; static public/og-image.png (+ generator script) - NEXT_PUBLIC_SITE_URL env; favicon/manifest fixes; drop duplicate metatags Landing & public pages: - Live mock-data feature previews replace PNG screenshots (auto-track the UI) - Unified SiteFooter across all public routes; hero polish; /features refresh - Multisig 3-of-5 signing explainer; animated feature-card SVG icons - API-docs intro; reconcile feature set (drop orphan "chat", add staking) Background: - Layered animated aurora + WebGL frosted-marble hero (cursor parallax, domain-warped veins); reduced-motion-safe Fixes: - Footer/min-h-screen flex-shrink overlap in RootLayout - Extract GovernanceTypeChip to a shared, lightweight module Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Detect installed wallet providers; when none are present, lead with a friendly "Get Started with Multisig using UTXOS" CTA (with an escape hatch to open the full picker) so non-crypto users don't hit a wallet wall. - When a wallet extension is detected, keep the normal flow but stop recommending UTXOS specifically: drop the "Recommended" badge/tint and the homepage "New to crypto? Try UTXOS" box. - Redesign the connect dropdown — cleaner UTXOS card (brand tile, network toggle + connect row) and wallet rows that show each wallet's real icon. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wallet-aware connect CTA + polished wallet/UTXOS connector
Fix flaky IPFS up/download, add rationale drafting persisted to the DB, surface rationale in pending-tx review, and add ballot CSV import/export. IPFS reliability - New src/lib/ipfs.ts gateway helpers + /api/ipfs/resolve, a multi-gateway server proxy (dedicated Pinata gateway first, then public fallbacks, 6s/ gateway, 2MB cap, cached). Reads route through it instead of hitting the frequently-504ing ipfs.io directly. - Uploads (pinata-storage put + image/put) return a dedicated-gateway URL via ipfsGatewayUrl() instead of a hardcoded ipfs.io URL. - NEXT_PUBLIC_PINATA_GATEWAY_URL added to env (optional, bare host accepted; scheme normalised in code). Rationale drafting + DB cache - Draft comments persist on blur via updateProposalRationale; uploads also cache the comment in the Ballot row (no schema change). - transaction-card VoteRationale shows each vote's rationale, preferring the DB cache and falling back to the IPFS proxy, with a gateway "source" link. Ballot CSV - New BallotCsv (papaparse + react-dropzone): columns proposal_id,title,vote,comment,anchor_url,anchor_hash. Import merges by proposal_id; blank cells preserve existing values; export is quoted via Papa.unparse. Hardening (from adversarial review) - resolve.ts follows redirects manually and only to allow-listed gateway hosts (SSRF guard), and pins Content-Type: application/json + nosniff. - fetchIpfsJson rejects non-https non-IPFS anchors and adds a timeout backstop. - Auto-load effect merges by proposalId+anchor so refetches don't clobber in-progress edits, aborts in-flight fetches, and de-dupes re-fetches. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reliable IPFS, rationale drafting/caching, and ballot CSV
The last open Dependabot alert (brace-expansion <=2.0.1, GHSA-v6h2-p8h4-qcjw, ReDoS) was a copy bundled inside npm@9.9.4, which @cardano-sdk/crypto pulls in via `npm@^9.3.0`. That npm dependency is spurious — @cardano-sdk/crypto declares it but never imports it in its compiled code — so it only drags in npm's vendored dependency tree (including the vulnerable brace-expansion and the noise behind `npm audit`'s large count). A `brace-expansion` override can't fix this: npm overrides don't reach into a bundled package's vendored node_modules, and a blanket brace-expansion override instead downgrades the legitimate brace-expansion@5.x that glob@11 / minimatch@10 require (named `expand` export), breaking glob. Override `npm` to ^11 instead (resolves to 11.17.0), whose bundle ships the patched brace-expansion@5.0.6. Net effect: - zero vulnerable brace-expansion instances remain in the tree - glob / minimatch still resolve and brace-expand correctly - safe because @cardano-sdk/crypto never executes npm The lockfile was regenerated with npm 10.9.3 (matching CI's node:22-slim, not the local npm 11) via `npm install --package-lock-only`, so it stays in sync for the `npm ci` CI runs — npm 11 drops @auth/core's optional @simplewebauthn entries that npm 10 keeps, which made `npm ci` fail. Churn is limited to the npm subtree. The other 14 Dependabot alerts were already remediated in the committed lockfile (next@16, form-data@4.0.5, pbkdf2@3.1.5, ip/tar-fs absent, etc.) and dismissed. Verified: `npm ci` (npm 10.9.3) succeeds; tsc --noEmit clean; next build --webpack succeeds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
deps: override npm to ^11 to clear bundled brace-expansion ReDoS
fix(db): cap pg pool + stop retrying pool saturation (Supabase EMAXCONNSESSION)
…urrent-vote UX
Redesign the proposals list into cards (replacing the desktop table) and surface
the "current voting state" two ways, per the requested mockup:
1. Live Yes/No/Abstain tally bar per proposal.
- Blockfrost exposes no aggregate and no voting power, so the tally is counted
by distinct voter (each voter's latest vote) from the proposal votes feed,
labeled "by votes" — not stake-weighted.
- Cached in the DB (new ProposalTally model) via a read-through tRPC router
(governance.getProposalTallies / refreshProposalTally). The list reads the
cache; refreshes are driven by user activity (on load when stale/missing,
and on expand). A 10-min TTL guards both client and server so redundant
activity doesn't re-hit Blockfrost.
2. The wallet's own vote as a "Voted Yes/No/Abstain" pill next to the type chip.
Also refines VoteButton: the segmented control no longer pre-selects Abstain
(it starts unselected and adopts the on-chain vote when known), the primary
button is disabled until a choice is made and its label reflects intent
(Vote / Change to / Re-submit, + Proxy), and vote()/voteProxy() guard against an
empty selection.
Backend: ProposalTally model + migration; governanceRouter registered in root.
NOTE: requires `prisma migrate deploy` (creates "ProposalTally") before the
tally router works in production.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(governance): proposal cards + DB-cached live vote tallies + current-vote UX
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| let s = input.trim(); | ||
| if (!s) return null; | ||
| if (s.startsWith("ipfs://")) s = s.replace(/^ipfs:\/\/(ipfs\/)?/i, ""); | ||
| const m = s.match(/\/ipfs\/(.+)$/i); |
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.
Promotes everything currently on
preprodtomain(41 commits). Summary by theme:Governance & voting
ProposalTallytable via a read-through tRPC router, refreshed on user activity with a 10-min TTL); "Voted X" pill; VoteButton no longer pre-selects Abstain.Transactions & assets UX
Mobile / UX foundations (#287–#292)
toastError.Infra & deps
EMAXCONNSESSIONpool exhaustion (fix(db): cap pg pool + stop retrying pool saturation (Supabase EMAXCONNSESSION) #284).prisma migrate deploy— feat(governance): proposal cards + DB-cached live vote tallies + current-vote UX #302 adds theProposalTallytable (migration20260614120000_add_proposal_tally). The governance tally router errors until it exists. Confirm the production deploy runs migrations.multisig-v1-smokehas been flaky on a depleted on-chain test wallet (UTxO Balance Insufficient), unrelated to these diffs.🤖 Generated with Claude Code