Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

| | |
|---|---|
| **Last updated** | 2026-10-08 (M3-26: interim chain-verified payee source, no indexer yet) |
| **Last updated** | 2026-10-08 (retire M3-26's interim payee source; real indexer wired up) |
| **Docs baseline** | v0.3, worldwide scope (`PRD.md`, `ARCHITECTURE.md`, `ARCHITECTURE_ESSENTIALS.md`, `AGENTS.md`, `CLAUDE.md`, `project_structure.md`) |
| **Current phase** | Phase 0 (Foundations) → starting Phase 1 (M0 Validate) |
| **Readiness** | See [§3](#3-progress-snapshot) |
Expand Down Expand Up @@ -315,7 +315,7 @@ Readiness % = `DONE ÷ (all rows − DEFERRED − DROPPED)`. Conditional rows (P
| M3-23 | Externalize all UI strings (`messages/en.json`) and add `Intl`-based locale-aware formatting; handle zero- and three-decimal currencies, non-Latin text, and RTL-safe layout | app | P0 | DONE | M3-01 | Lint or test fails on inline strings; formatting tests across at least 3 locales and currencies |
| M3-24 | CI guard: fail on hard-coded country, currency, anchor, or locale literals outside registry data, tests, and fixtures | all | P1 | TODO | M3-01, M2-01 | Guard runs in app and sdk CI; passing and failing examples tested |
| M3-25 | Favicon/app icon, persistent site header (nav, docs and GitHub links), mobile hamburger menu, reduced-motion-safe entrance motion | app | P2 | DONE | M3-01, M3-04 | Favicon set in every browser; nav usable one-handed on a narrow viewport; `prefers-reduced-motion` honored |
| M3-26 | Interim chain-verified payee source for `/send` and `/request`, since no indexer is deployed (`M2-18`) | app | P1 | DONE | M3-05, M3-06 | `/send` and `/request` list real, on-chain `Active` payees and complete their flow up to wallet connect; superseded once `M2-18` ships |
| M3-26 | Interim chain-verified payee source for `/send` and `/request`, since no indexer is deployed (`M2-18`) | app | P1 | DONE | M3-05, M3-06 | `/send` and `/request` list real, on-chain `Active` payees and complete their flow up to wallet connect; **retired 2026-10-08**, superseded now that `M2-18`'s indexer is live (code removed, pages point at the real indexer again) |

---

Expand Down Expand Up @@ -525,6 +525,7 @@ Newest first. One entry per PR. Required for every contribution (see §2).

| Date | PR / ref | Repo | Rows touched | Summary |
|---|---|---|---|---|
| 2026-10-08 | `chore/retire-known-payees-fallback` | app | M3-26 (retired, status unchanged) | `kinlock-sdk`'s indexer (`M2-18`) is live on Railway (`indexer-production-705a.up.railway.app`, healthy, `lagLedgers: 0`), deployed by a teammate while `M3-26` was in flight. Set `NEXT_PUBLIC_INDEXER_URL` in Vercel (production and preview). Reverted `/send` and `/request` from `M3-26`'s interim `lib/payees/*` (chain-verified snapshot) back to the real `listPayees()`, and deleted that module: carrying two parallel payee sources going forward would be the dead-code / duplicate-abstraction AGENTS.md explicitly rules out, and the real indexer serves the identical three payees plus whatever gets registered next. Verified: `pnpm lint`, `pnpm typecheck`, `pnpm test` (94/94), `pnpm build`, then `pnpm start` locally against the real indexer URL confirmed `/send` and `/request` still list all three payees, this time genuinely through `M2-18`'s pipeline. `M2-18` itself is still `IN PROGRESS` in `kinlock-sdk`'s `ROADMAP.md` (daily backup schedule and a restore-vs-live comparison remain), not this repo's row to close |
| 2026-10-08 | `feat/known-payees-chain-read` | app | DONE: M3-26 (new row) | `/send` and `/request` showed "can't be loaded" for every payee, since no indexer is deployed (`M2-18`, still `TODO`). Investigated whether `kinlock-registry`'s real `payees/` data could substitute, but it's empty (`.gitkeep` only) and the indexer's own registry-join (`kinlock-sdk/services/indexer/src/registry/sync.ts`) reads a local filesystem checkout, not something `kinlock-app` should fetch live or re-implement wholesale. Built a scoped interim source instead: `lib/payees/known.ts` is a committed snapshot of `kinlock-registry/fixtures/payees/` (the three fictional testnet fixtures), and `lib/payees/registry.ts` joins each one with a live chain read (`getPayee`, hard rule 3: chain is truth) before showing anything, verifying the on-chain `meta_hash` still matches this snapshot's hash of the full record, i.e. exactly the binding the real indexer enforces (same `canonicalize`/`sha256` algorithm, deliberately duplicated here as kinlock-sdk's indexer already duplicates it from kinlock-registry). `status` and `payout` are always the live on-chain values, never the snapshot's. Wired into `/send` and `/request` in place of the broken `listPayees()` call. While investigating, found all three fixtures were in fact already registered and `Active` on the real testnet contract from earlier work this session (not something this PR did); verified against the deployed contract directly (`stellar contract invoke ... get_payee`). Verified end to end with Playwright against the real local build (no indexer configured, matching production): both pages list all three payees, `/request` generates a real prefilled `/send` link, `/send`'s full review flow (schedule, take-back date, USD total, preflight) completes up to "Connect your wallet first" (the correct, by-design stopping point; actually signing needs a real wallet extension, out of scope for an automated check). Also installed the `stellar` CLI (Homebrew) and generated a throwaway testnet identity (`kinlock-fixture-attester`) while investigating, in case registration had been needed; turned out not to be. Verified: `pnpm lint`, `pnpm typecheck`, `pnpm test` (101/101, 7 new), `pnpm build` all green. Superseded once `M2-18` (real indexer) ships |
| 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 |
Expand Down
5 changes: 2 additions & 3 deletions app/request/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
/** Payee creates a payment-request link. Roadmap M3-05. */
import { type MessageKey, t } from "@/lib/i18n/messages";
import { type IndexedPayee, payable } from "@/lib/indexer";
import { listKnownPayees } from "@/lib/payees/registry";
import { type IndexedPayee, listPayees, payable } from "@/lib/indexer";
import { RequestForm } from "./RequestForm";

export const dynamic = "force-dynamic";

export default async function Page() {
let payees: IndexedPayee[] | null = null;
try {
payees = payable(await listKnownPayees());
payees = payable(await listPayees());
} catch {
payees = null;
}
Expand Down
5 changes: 2 additions & 3 deletions app/send/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/** Sender flow: request-link prefill, preflight, USD (indicative local currency once DEC-10). Roadmap M3-06. */
import { SavedClaimLinks } from "@/components/claim-links/SavedClaimLinks";
import { type MessageKey, t } from "@/lib/i18n/messages";
import { type IndexedPayee, payable } from "@/lib/indexer";
import { listKnownPayees } from "@/lib/payees/registry";
import { type IndexedPayee, listPayees, payable } from "@/lib/indexer";
import { publicConfig } from "@/lib/sdk";
import { SendForm } from "./SendForm";

Expand All @@ -11,7 +10,7 @@ export const dynamic = "force-dynamic";
export default async function Page() {
let payees: IndexedPayee[] | null = null;
try {
payees = payable(await listKnownPayees());
payees = payable(await listPayees());
} catch {
payees = null;
}
Expand Down
57 changes: 0 additions & 57 deletions lib/payees/known.ts

This file was deleted.

84 changes: 0 additions & 84 deletions lib/payees/registry.test.ts

This file was deleted.

61 changes: 0 additions & 61 deletions lib/payees/registry.ts

This file was deleted.

Loading