Skip to content

Route every money and time display through the shared components, and enforce it #40

Open

Description

@abrcrmb

Task

AmountDisplay, AssetLabel, RateNotice and DateTimeDisplay already exist. Find every place a page formats an amount, an asset or a timestamp without them, replace those call sites, and add a test that fails when someone hand-rolls formatting again.

Context

  • Components: components/money/AssetLabel.tsx:8, components/money/AmountDisplay.tsx:10, components/money/RateNotice.tsx:4, components/time/DateTimeDisplay.tsx:5. Formatting helpers: lib/i18n/format.ts:9,20,27.
  • M3-15 is IN PROGRESS because the components exist but universal use is not proven — that is the remaining work.
  • Hard rules this enforces: always show asset code and issuer, never code alone (§8.3); dates in UTC and local (§8.3); amounts are decimal strings, never number (§8.2).
  • lib/i18n/inline-strings.test.ts:22 already scans app/ and components/ for hard-coded UI strings and passes — the same guard style works for formatting calls.
  • USDC is 7 decimals; messages/en.json holds the copy; the inline-string guard is the precedent to copy.

Before you start: mandatory context review

Kinlock moves other people's money, so the docs are binding constraints, not background reading.
They record decisions this task must not silently reopen. Read them in this order and tick each box:

  • docs/ARCHITECTURE_ESSENTIALS.md — start here, it is short (AGENTS.md §1)
  • docs/ARCHITECTURE.md — only the sections named in Context above
  • docs/PRD.md — if this task touches user-facing behaviour or scope
  • AGENTS.md §3 (hard rules), §4 (deferred list), §8 for this repo, §12 (roadmap rule)
  • ROADMAP.md — the row(s) listed under Related roadmap rows, including their Depends on

In your first PR comment, name the hard-rule numbers or doc sections that constrain this task. That
line is how we know the review happened.

Then:

  • If code and docs disagree, or two docs disagree: stop and say so on this issue. Do not pick one
    silently, and do not "fix" either side without approval (AGENTS.md §1).
  • If the task looks like it needs anything from the deferred list (§4), a change to a hard rule (§3),
    or a new dependency (§2): raise it here first. Those need a human; deciding them is not your job.
  • If a row you are told to finish is BLOCKED or depends on a row that is not DONE, say so here
    instead of starting.

Why we need this

  • Two formatters for the same number is how a sender sees 1,000.00 USDC on one page and 1,000 on the next and starts wondering which is real.
  • The issuer is what distinguishes USDC from a look-alike token. Displaying a code alone is a genuine scam surface, not a style preference.
  • A guard test turns three rules that live in prose into something CI enforces.

Scope

In scope

  • Call-site replacements in app/(marketing)/, app/payee/, app/request/, app/verify/, app/_components/, components/
  • lib/i18n/format.ts — display-only helpers, if a gap needs closing
  • A new guard test, e.g. lib/i18n/format-usage.test.ts
  • components/ui/ if a small shared piece is missing

Do not touch (security-sensitive or generated; changing these needs a human first — AGENTS.md §2, docs/project_structure.md §8)

  • app/claim/** and lib/claim-links/** — the claim-link fragment (#r=…&s=…) must never reach a server, log or analytics tool (hard rule 5)
  • next.config.ts, proxy.ts / middleware.ts, CSP headers
  • lib/wallet/** and any direct contract call site — money-moving calls go through the SDK and the wallet adapter only
  • app/send/**, app/locks/**, app/payee/** action logic — release, refund and decline paths stay with maintainers
  • package.json, pnpm-lock.yaml (no new dependencies without approval), .github/workflows/**
  • app/send/**, app/locks/**, app/claim/**, app/r/** — these are the money-action pages; if they need changes, list them on the issue and a maintainer will land them
  • changing rounding, precision or any value semantics — presentation only

Suggested implementation

  1. Read ARCHITECTURE_ESSENTIALS.md, AGENTS.md §8.2 (amount types) and §8.3 (asset and date display).
  2. Inventory first: grep for Intl.NumberFormat, Intl.DateTimeFormat, toLocaleString, toFixed, and raw {amount} interpolations outside lib/i18n/format.ts. Put the list in the PR before you start changing things.
  3. Replace one route at a time, committing each so a review can stop at a route if a replacement is wrong.
  4. The guard test: scan app/ and components/ for those patterns, allowlist lib/i18n/format.ts and components/money/*, components/time/*, and fail otherwise. Copy the structure of lib/i18n/inline-strings.test.ts:22.
  5. Where an amount is shown without its issuer, add AssetLabel. Where a timestamp appears without both UTC and local, use DateTimeDisplay.
  6. AGENTS.md §8.3 says an indicative local-currency equivalent is display-only with a rate-risk note (RateNotice), and never guaranteed — do not build or change rate logic.

A different approach is usually welcome — post it here before building and we will answer. This is a
suggestion, not a spec.

Edge cases and traps

  • Zero, the smallest USDC unit (0.0000001), and a large multi-tranche total: three different formatting traps in one component.
  • 0-, 2- and 3-decimal local_currency display, plus a right-to-left display_name that must not break the layout (§8.3).
  • A long issuer address next to a short asset code is where “show code and issuer” gets hard — check the narrow case.
  • Allowlist only lib/i18n/format.ts, components/money/* and components/time/*. A wider allowlist turns the guard into decoration.
  • Presentation only. If a conversion changes a rendered value, stop — that is M3-15's money-action half, which is not yours to touch.

Done means

  • Every in-scope page renders money through AmountDisplay/AssetLabel and times through DateTimeDisplay
  • No asset appears by code alone in any in-scope view
  • The guard test exists, fails on a hand-rolled formatter you add locally, and passes in CI
  • pnpm lint, typecheck, test, build pass; lib/i18n/inline-strings.test.ts:22 still passes
  • You list the out-of-scope money-action routes that still need conversion in the PR, so M3-15 can be closed honestly

Tests

One new guard test plus any unit case for a helper you add. If you touch lib/i18n/format.ts, extend messages.test.ts-style coverage for the edge cases (zero, 2- and 3-decimal currencies, RTL names) without changing its output for existing cases.

Documentation

Update the README component note if the convention changes. No ADR unless you change the formatting rule itself.

Verify before you open the PR

pnpm install
pnpm lint
pnpm typecheck
pnpm test
pnpm build

Run every command above and paste the real output in the PR. We do not accept "should pass" —
AGENTS.md §6: "You actually ran the commands."

Opening the pull request

  • Assignment is required before you start. Comment on this issue and wait to be assigned. Do not
    open a PR against an issue someone already holds — if it has been quiet, ask us to release it first.
  • Closes #40 in the PR description, so the issue and the PR link automatically.
  • Branch feat/…, fix/…, docs/…, test/… or chore/…. Commit subject example:
    refactor(app): render money and time only through the shared components
  • Leave M3-15 IN PROGRESS unless the money-action routes are also converted (they are not yours to touch); add a row for the remainder.
  • Follow .github/PULL_REQUEST_TEMPLATE.md (AGENTS.md §7). In "Hard rules / invariants touched",
    give numbers, not prose.
  • Small and focused: one logical change. Do not mix a refactor into it.
  • Do not add the security-sensitive label — that is the maintainer's flag for contract-logic PRs.
  • Do not reformat or touch files outside Scope. Unrelated diff noise costs review time and can
    break the roadmap merge.
  • Never commit to main; open a PR. Never force-push.
  • Evidence for the PR: Attach before/after screenshots of one converted route showing the smallest and the largest amount it can render.

How we review

Two maintainers: one reviews correctness against the hard rules and the roadmap row's Done when, the
other reviews diff scope and whether the tests were actually run. Order is hard-rule correctness → tests
really executed → diff stays inside Scope → style last. First response on a PR: 2 business days.

What fails review fastest: a change outside Scope, a deleted or weakened assertion, a claim with no
command output behind it, and a ROADMAP.md with no Changelog entry (AGENTS.md §12).
For this issue, look first at the inventory list you post before changing anything, and the guard test's allowlist — that is where a false pass hides.

Tier and effort

tier/medium · roughly 1–2 days. Wave complexity tag: Medium = 150 points — the published mapping is Trivial 100 · Medium 150 · High 200
(Drips' maintainer guide); the cycle's
own values are confirmed on drips.network/wave/stellar before
the Wave opens. Kinlock's tier rules and what each tier means for review:
docs/wave-issue-format.md.

Related roadmap rows

M3-15 — AssetLabel, AmountDisplay, UTC+local time components (IN PROGRESS)

M3-15's Done when is "Used everywhere money or time is shown" — the components exist, so this issue is the "everywhere" half plus the guard that keeps it true.

Questions

Comment on this issue — a maintainer answers within 2 business days. Say "I'll take this" so we can flag it on
the Wave board and stop two people doing the same work. If you get half way and stall, post what you
have as a draft PR rather than disappearing; a blocked draft is more useful to us than silence.

Activity

  1. added
    waveScoped for Drips Wave contributors
    area:appOwnership: kinlock-app
    tier/mediumWave tier: standard feature or fix work, a few hours, tests included
    on Oct 8, 2026
  2. jdjioe5-cpu commented on Oct 17, 2026

    @jdjioe5-cpu

    I'll take this! Will inventory every money/time display call site, route in-scope pages through AmountDisplay, AssetLabel, and DateTimeDisplay, build the format-usage guard test (lib/i18n/format-usage.test.ts) modeled after inline-strings, preserve all security/money-action boundaries out of scope, and verify full CI suite.

  3. jdjioe5-cpu commented on Oct 17, 2026

    @jdjioe5-cpu

    PR is ready for review: #48 - Pre-change inventory recorded; in-scope call-sites in RequestForm, ReceiptResult, and SavedClaimLinks routed through AmountDisplay, AssetLabel, and DateTimeDisplay; AmountDisplay supports optional assetCode/issuer for fallback safety; strict format-usage guard test in lib/i18n/format-usage.test.ts passing; money-action routes listed out-of-scope with M3-15 kept IN PROGRESS; full CI test suite green (209/209 tests passed, production build passed).

  4. added
    drips-waveDrips Wave bounty task for Stellar ecosystem contributors
    and removed
    waveScoped for Drips Wave contributors
    drips-waveDrips Wave bounty task for Stellar ecosystem contributors
    on Nov 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:appOwnership: kinlock-apptier/mediumWave tier: standard feature or fix work, a few hours, tests included

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions