From 3cfd8e3e700d288878594d67de1930f023b3fdbd Mon Sep 17 00:00:00 2001 From: Yazi Date: Thu, 8 Oct 2026 14:53:11 +0100 Subject: [PATCH] docs(app): document per-route state instead of claiming the flows are done "Send, claim, decline and lock-detail work end to end" overstated it: /payee and /attester are stubs, the Playwright e2e is still fixme, and there is no rate provider so displays are USD-only. List each route's real state and the rules the repo enforces (chain-is-truth, fragment never leaves the browser, no hard-coded country). --- README.md | 40 +++++++++++++++++++++++++++++++++++++--- ROADMAP.md | 1 + 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index beb43f6..ead4785 100644 --- a/README.md +++ b/README.md @@ -4,23 +4,57 @@ [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE) [![Live demo](https://img.shields.io/badge/demo-live-brightgreen)](https://kinlock-app.vercel.app) -Next.js web app: payee requests, sender flow, claim, attester checks, receipts. - -> **Status: active development, testnet only.** Send, claim, decline, and lock-detail flows work end to end against testnet; the payee dashboard and attester tooling are still open. See `ROADMAP.md`. +The Next.js front end: payee payment requests, the sender's lock flow, the payee's claim, and public receipts. **Testnet only, with test assets.** **Live app (testnet):** [kinlock-app.vercel.app](https://kinlock-app.vercel.app) · **Docs:** [kinlock-org.github.io](https://kinlock-org.github.io) +## What works today + +| Route | State | +|---|---| +| `/` | Landing, how-it-works, principles | +| `/request` | Payee builds a payment-request link (amount, reference, schedule) | +| `/send` | Sender picks a payee, sets a schedule, sees preflight results, funds a lock, gets a claim link | +| `/claim/[id]` | Payee verifies the reference against the on-chain `ref_hash`, then releases or declines | +| `/locks/[id]` | Lock detail with refund, shown only when the contract allows it | +| `/r/[txHash]/[eventIndex]`, `/verify` | Receipts rebuilt from chain data. "Payment to verified payee" — nothing more | +| `/payee`, `/attester` | **Stubs** (`M3-11`, `M3-12`) | + +> **Status: the core money flows are real; the dashboard isn't.** Claim, decline, refund, receipts and verify read the **chain** through the SDK, never the indexer. `/send` is `M3-06` in progress, the Playwright happy path (`M3-19`) is still a `test.fixme` stub, and there is no live FX rate yet, so amounts show **USD only with a note**. Full state in `ROADMAP.md` (Phase 4: 11 `DONE`, 6 `IN PROGRESS`, 9 `TODO`). + +## The rules this repo exists to enforce + +- **Chain is truth.** Every page that can move money calls `getLock` / `getPayee` / `verifyReceipt`. Postgres-backed lists are for dashboards, and the dashboard doesn't read them for actions. +- **The claim-link fragment stays in the browser.** `#r=…&s=…` is never sent to a server, logged, or handed to a third party. `/claim/*` gets a nonce-only CSP from `proxy.ts`, `Referrer-Policy: no-referrer` site-wide, and **zero third-party scripts or analytics**. Saved claim links live in `localStorage` with an export option. +- **Money is shown honestly.** `AssetLabel` always prints code *and* issuer; `AmountDisplay` formats from `bigint`; `DateTimeDisplay` shows UTC and local time. `RateProvider` is an interface with no implementation yet — the rate path fails closed to USD-only rather than inventing a number (`M3-16`, `DEC-10`). +- **Nothing is hard-coded to a country.** Country and `local_currency` come from registry data. Every user-visible string lives in `messages/en.json`, keys are type-checked, and a test fails the build on an inline string; `Intl` handles numbers, currencies and dates (`M3-23` done — other locales are deferred, not missing by accident). +- **Only `lib/sdk.ts` talks to the contract.** Wallets go through `lib/wallet/` using the Stellar Wallets Kit, so Freighter, xBull, Albedo and Lobstr are swappable. + +One caveat worth knowing: until the app is wired to the deployed indexer, `/send` and `/request` offer the payees in `lib/payees/known.ts` — a checked-in snapshot of the three testnet fixture payees (KE, NG, PH). It is not trusted for display: each entry's `payee_id` and `meta_hash` are re-derived locally and matched against a live `getPayee` chain read, and status and payout address always come from chain. + ## Quick start + ``` pnpm install pnpm dev +pnpm lint && pnpm typecheck && pnpm test # biome, tsc, vitest (13 files, 69 cases) +pnpm e2e # placeholder until M3-19 ``` +Next.js 16 (App Router), React 19, TypeScript 7, Tailwind 4. Node 20 in CI. + +Configuration is Zod-validated in `lib/config.ts`. Required: `NEXT_PUBLIC_STELLAR_NETWORK`, `NEXT_PUBLIC_RPC_URLS`, `NEXT_PUBLIC_CONTRACT_ID`, `NEXT_PUBLIC_USDC_CONTRACT_ID`, `NEXT_PUBLIC_USDC_ISSUER`. `NEXT_PUBLIC_INDEXER_URL` is optional — only list views need it, and the deployed testnet app currently runs without it. Copy `.env.example` to `.env.local`; never commit values. + +Deploys to Vercel are currently **manual** (`vercel --prod`) — there's no deploy workflow, so a merged PR does not go live by itself. + ## Read first + - `docs/ARCHITECTURE_ESSENTIALS.md` (short; read at the start of every task) - `AGENTS.md` (rules for humans and agents) and `CLAUDE.md` - `ROADMAP.md`: **every PR updates it** +`next.config.ts`, `proxy.ts`, `app/claim/**` and `lib/claim-links/**` are **security-sensitive**: ask before changing them, and label the PR accordingly. + Docs in `docs/` are read-only copies synced from [Kinlock-Org/.github](https://github.com/Kinlock-Org/.github). Found a documentation gap (missing, unclear, or outdated docs)? File it at [Kinlock-Org.github.io](https://github.com/Kinlock-Org/Kinlock-Org.github.io/issues/new/choose) with `area:app`, the org's documentation hub, not here. diff --git a/ROADMAP.md b/ROADMAP.md index f87e8b7..fca11ea 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -524,6 +524,7 @@ Newest first. One entry per PR. Required for every contribution (see §2). | Date | PR / ref | Repo | Rows touched | Summary | |---|---|---|---|---| +| 2026-10-08 | `docs/readme-refresh` | app | no row changes | README rewritten with per-route state: seven real flows, `/payee` and `/attester` still stubs, money pages reading chain, the claim-link CSP rules, USD-only display while `M3-16` is open, and the fixture-payee snapshot `/send` uses until it is wired to the indexer | | 2026-10-08 | `fix/indexer-url-optional` | app | no row changes | Fix: owner reported `/send`, `/verify`, `/request` "not functioning" after the previous hotfix. Found the real root cause, one level deeper than that hotfix: `lib/config.ts`'s `PublicConfigSchema` required `NEXT_PUBLIC_INDEXER_URL`, but `@kinlock/sdk`'s own `KinlockConfig.indexerUrl` is typed `?: string` (optional) precisely so chain-only operations keep working without an indexer. Since `NEXT_PUBLIC_INDEXER_URL` genuinely isn't set (no indexer deployed, `M2-18`), *every* `publicConfig()`/`kinlockConfig()` call was throwing before any RPC request was even attempted, breaking `verifyReceipt`, and by the same path `createLock`/`release`/`refund`/`decline`, anywhere they're called without an indexer-fetch guard already in front of them (`/verify`'s "Check" button made zero network requests; confirmed with Playwright). `next.config.ts`'s rewrites already treated a missing indexer URL as expected (`if (!indexer) return {...empty...}`), so this was a `kinlock-app`-only validation bug, not a real product gap. Changed `NEXT_PUBLIC_INDEXER_URL` to `z.url().optional()` to match the SDK's own contract; added a regression test. Reproduced against the exact production condition before and after (dev server with the real indexer-less `.env.local`): before, `verifyReceipt` on `/verify` made 0 RPC requests and always hit the generic error; after, it makes a real `soroban-testnet.stellar.org` request and correctly resolves "Not valid / not found" for a made-up hash. `/send` and `/request` still correctly show "can't be loaded" for the payee list, since there's genuinely no indexer yet; that part is accurate, not a bug. Verified: `pnpm lint`, `pnpm typecheck`, `pnpm test` (94/94, new test added), `pnpm build`, plus the live repro above, before redeploying | | 2026-10-08 | `fix/verify-publicconfig-crash` | app | M3-13 (no status change, still `DONE`) | Hotfix: `/verify` and `/r/[txHash]/[eventIndex]` were returning `500` in production (reported by the owner after the previous deploy). Root cause: `publicConfig()` validates the entire shared config, including `NEXT_PUBLIC_INDEXER_URL`, which isn't set in this Vercel project (no indexer deployed yet, `M2-18`); `/send`/`/request`/`/payee` happen to avoid calling it because they only reach it after a successful indexer fetch, but `useReceiptPage.ts`/`useVerifyForm.ts` called it unconditionally just to label the asset. Fixed by wrapping that call in a `try/catch`, degrading to no asset label (the existing fallback for a missing `lock`), same as `ReceiptResult.tsx` already does. Reproduced the exact failure locally first (dev server with `.env.local`'s real, indexer-less config, hitting `/verify` and `/r/...` directly: both 500 before the fix), then confirmed both return 200 after it, before redeploying. Verified: `pnpm lint`, `pnpm typecheck`, `pnpm test` (93/93), `pnpm build`, plus the live repro above | | 2026-10-08 | `feat/site-polish` | app | DONE: M3-25 (new row) | Added `app/icon.svg`/`app/apple-icon.png` (favicon and iOS icon, from the org's new lock mark in `kinlock-icon/`). Built `components/nav/SiteHeader.tsx`: a persistent header on every page, desktop nav (Send/Request/Verify) plus two separately-styled, separately-destined buttons (outline "Documentation" to `kinlock-org.github.io`, filled-neutral "GitHub" to `github.com/Kinlock-Org`), collapsing to a hamburger disclosure menu below `md` (closes on route change and Escape). Fixed the landing footer's stale docs link, which pointed at a raw GitHub markdown file instead of the real docs site. Installed the `emilkowalski/skills` `animate` skill and used it for the menu's entrance and a staggered hero fade/slide-up on the landing page (CSS-only, no new dependency, both gated under `prefers-reduced-motion`). Verified: `pnpm lint`, `pnpm typecheck`, `pnpm test` (93/93), `pnpm build` all green; visually checked desktop, mobile closed/open, and dark mode with Playwright screenshots. Not done here: the GitHub org avatar itself (no API/CLI command sets it; recommended `kinlock-icon/icon-light-1024.png`, needs a human to upload via org settings) |