From 18fa2bac19acc39023044086efc5ba0cd6d54d03 Mon Sep 17 00:00:00 2001 From: abrcrmb Date: Thu, 8 Oct 2026 12:24:27 +0100 Subject: [PATCH] fix: NEXT_PUBLIC_INDEXER_URL wrongly required, broke verify/send/request lib/config.ts required NEXT_PUBLIC_INDEXER_URL, but @kinlock/sdk's own KinlockConfig.indexerUrl is typed optional: chain-only operations (verifyReceipt, getLock, release, refund, decline, createLock) don't need an indexer. Since no indexer is deployed yet (M2-18) and the env var genuinely isn't set, every publicConfig()/kinlockConfig() call threw before any RPC request was attempted. /verify's Check button made zero network requests as a result. next.config.ts's rewrites already treated a missing indexer URL as expected; this was an app-side validation bug, not a real product gap. Changed the field to z.url().optional() to match the SDK's own contract, and added a regression test. --- ROADMAP.md | 3 ++- lib/config.test.ts | 6 ++++++ lib/config.ts | 9 +++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 3e33d99..f87e8b7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -4,7 +4,7 @@ | | | |---|---| -| **Last updated** | 2026-10-08 (hotfix: `/verify` and `/r/...` 500 in production) | +| **Last updated** | 2026-10-08 (fix: `NEXT_PUBLIC_INDEXER_URL` wrongly required, broke verify/send/request) | | **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) | @@ -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 | `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) | | 2026-10-08 | `docs/verified-payee-wording` | app | no row changes | Synced `AGENTS.md`/`docs/PRD.md`/`docs/ARCHITECTURE_ESSENTIALS.md` from `Kinlock-Org/.github` (`scripts/sync-docs.sh`), and updated `messages/en.json` (`app.tagline`, `pages.home.heroHeadline`/`heroSubtext`/`lockBody`/`sendersBody`, `pages.send.intro`): "a verified school or landlord" replaced with "a verified payee" everywhere it was used as a generic stand-in for the product's reach, rather than naming the two current MVP categories factually. Verified: `pnpm lint`, `pnpm typecheck`, `pnpm test` (92/92, including the inline-strings guard) all green. Canonical doc source edited in `.github` PR #23 | diff --git a/lib/config.test.ts b/lib/config.test.ts index ff8e256..fdd4508 100644 --- a/lib/config.test.ts +++ b/lib/config.test.ts @@ -21,6 +21,12 @@ describe("loadPublicConfig", () => { expect(() => loadPublicConfig({ ...env, NEXT_PUBLIC_STELLAR_NETWORK: "mainnet" })).toThrow(); }); + it("allows a missing indexer URL (no indexer deployed yet, M2-18); chain-only features must still work", () => { + const c = loadPublicConfig({ ...env, NEXT_PUBLIC_INDEXER_URL: undefined }); + expect(c.NEXT_PUBLIC_INDEXER_URL).toBeUndefined(); + expect(c.NEXT_PUBLIC_CONTRACT_ID).toBe(env.NEXT_PUBLIC_CONTRACT_ID); + }); + it("refuses missing or malformed values", () => { for (const bad of [ { NEXT_PUBLIC_CONTRACT_ID: "GABC" }, diff --git a/lib/config.ts b/lib/config.ts index 1e176fd..c6eab04 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -21,8 +21,13 @@ const PublicConfigSchema = z.object({ ) .pipe(z.array(z.url()).min(1)), NEXT_PUBLIC_CONTRACT_ID: z.string().regex(/^C[A-Z2-7]{55}$/), - /** List API: lists, dashboards, preflight warnings, receipt lookups. Never money pages. */ - NEXT_PUBLIC_INDEXER_URL: z.url(), + /** + * List API: lists, dashboards, preflight warnings, receipt lookups. Never money pages. + * Optional (matches the SDK's own `KinlockConfig.indexerUrl?: string`): until an indexer is + * deployed (M2-18), chain-only features (verify, lock reads, release/refund/decline) must keep + * working; only indexer-backed lists and preflight checks degrade, each with its own message. + */ + NEXT_PUBLIC_INDEXER_URL: z.url().optional(), /** The USDC token contract (Stellar Asset Contract) passed to `createLock`. */ NEXT_PUBLIC_USDC_CONTRACT_ID: z.string().regex(/^C[A-Z2-7]{55}$/), /** USDC issuer, shown with the asset code everywhere (AssetLabel). */