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
3 changes: 2 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
Expand Down Expand Up @@ -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 |
Expand Down
6 changes: 6 additions & 0 deletions lib/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
9 changes: 7 additions & 2 deletions lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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). */
Expand Down
Loading