Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
66861d1
feat: anchoring, visual tests, Storybook, and webhook delivery
stephanieoghenemega-eng Aug 25, 2026
90589b9
Fix #170: add standalone chain-only reconciliation for payments
thefadah Aug 26, 2026
7381fc3
Fix #169: generate SDK settlement-hook types from OpenAPI spec
thefadah Aug 26, 2026
7ab25f8
Fix #171: additive rendezvous-hashing shard router for multi-tenant D…
thefadah Aug 26, 2026
e46358c
Fix #172: ABI version registry and factory for ReceiptAnchor in @acce…
thefadah Aug 26, 2026
1f10a0e
feat(web): derive block-explorer links from the configured network
mhikel66 Aug 26, 2026
37f943a
refactor(web): route explorer links through explorerUrl and document …
mhikel66 Aug 26, 2026
edef9b6
fix(web): stop the refund panel falling through on the submitting phase
mhikel66 Aug 26, 2026
86458c6
test(web): drive the refund panel into every phase
mhikel66 Aug 26, 2026
21516cc
feat(web): add focus-trap and focus-restoration helpers for modals
mhikel66 Aug 26, 2026
de38077
fix(web): expose the payment-details modal as a real dialog
mhikel66 Aug 26, 2026
340ae87
test(web): assert the payment modal's dialog semantics
mhikel66 Aug 26, 2026
e99c556
feat(web): page the full payment history and scope analytics by range
mhikel66 Aug 26, 2026
86cf383
fix(web): aggregate Revenue by Route over the whole selected range
mhikel66 Aug 26, 2026
abc9991
chore(web): reformat verify/page.tsx to unbreak the format check
mhikel66 Aug 26, 2026
70d2384
chore: stop tracking scratch artifacts and document the .scratch/ con…
bade2brazy Aug 26, 2026
c4ee584
ci(sync): fix self-cancel, the hour-boundary gap, and the cessation b…
bade2brazy Aug 26, 2026
ea7cc36
docs(deployment): record the indexer-scheduling decision and the mult…
bade2brazy Aug 26, 2026
6df69b3
docs(readme): document ASSET_CONTRACT_IDS and the single-token refund…
bade2brazy Aug 26, 2026
0145c9b
perf+fix(db): pool connections, run ensureSchema once, close the sche…
bade22brazy Aug 26, 2026
a26297c
fix(db): migration mirroring the parity + keyset-index fixes (#91, #93)
bade22brazy Aug 26, 2026
b9065a5
feat(api): /api/health cursor-lag check polled from outside the sync …
bade22brazy Aug 26, 2026
b1713c5
ci: staleness-check workflow that alerts a human (#90)
bade22brazy Aug 26, 2026
8bb0af7
docs: indexer runbook, one entry per alert (#90)
bade22brazy Aug 26, 2026
230fcb1
test(sdk): real end-to-end coverage for the x402 middleware flow
Jaydbrown Aug 26, 2026
6d68d9d
refactor(web): build revenue analytics from pre-aggregated buckets
Jaydbrown Aug 26, 2026
87631b5
feat(web): server-side revenue analytics aggregation
Jaydbrown Aug 26, 2026
def7f78
test(web): cover the revenue analytics aggregation route
Jaydbrown Aug 26, 2026
d28cb90
feat(web): paginate the settlements table with the payments cursor
Jaydbrown Aug 26, 2026
f86dd11
test(web): cover the settlements pagination merge
Jaydbrown Aug 26, 2026
c190ec6
feat(web): reusable ErrorBoundary for isolating widget crashes
Jaydbrown Aug 26, 2026
b7748a9
feat(web): wrap the dashboard widgets in ErrorBoundary
Jaydbrown Aug 26, 2026
766e481
feat(web): disable refund actions while the browser is offline
Jaydbrown Aug 26, 2026
d36569e
test(web): cover the ErrorBoundary fallback behaviour
Jaydbrown Aug 26, 2026
d7d29ff
feat(sdk): support signing on edge and browser runtimes via WebCrypto…
timo126 Aug 27, 2026
d862b7d
fix(wave): comprehensive audits, cache header enforcement, and docs f…
MFrancis-dev Aug 27, 2026
90ed1db
fix(web): solve dashboard totals, tab visibility, skeleton shift, and…
Teescom Aug 27, 2026
4f0f02b
security(sdk): document and narrow the merchant signing key (#266)
timo126 Aug 27, 2026
e8d2cce
feat(sdk): expose strict Order and Product types with a typed client …
meem08 Aug 27, 2026
69b2a5b
feat(web): cache merchant profile reads with tag-based invalidation (…
Ajibose Aug 27, 2026
cbe0d53
feat(sdk): add AccensaClient with custom contract initialization, and…
Ajibose Aug 27, 2026
b33f69f
feat(indexer): batch sync missed blocks (#253)
Aj-Kayvee Aug 27, 2026
518fe35
feat(indexer): structured failure logging for sync (#135)
larryjay007 Aug 27, 2026
e48ce38
feat(indexer): structured failure logging for sync (#135)
allessandrobianchi Aug 27, 2026
1769abf
fix(ci): bump pinned pnpm from v9 to v11 to match pnpm-workspace.yaml
larryjay007 Aug 27, 2026
fd3a89c
fix(ci): bump pinned pnpm from v9 to v11 to match pnpm-workspace.yaml
allessandrobianchi Aug 27, 2026
e472f0f
feat: Add rate limiting to public API routes (#251)
Aj-Kayvee Aug 27, 2026
e8be1f3
Fix UI glitches and limits
Aug 27, 2026
6e0f699
Merge branch 'main' into fix-ajayi-issues
mallison031 Aug 27, 2026
521fc83
Merge pull request #272 from pyper01/fix-ajayi-issues
mallison031 Aug 27, 2026
b1bd1a8
feat: wallet adapter, deterministic timestamps, merkle proof fix, dem…
dotunv Aug 27, 2026
34a6e1d
feat(demo-merchant): add loading state to pay buttons during checkout…
Hamda-gbade Aug 27, 2026
8ce8e74
feat(demo-merchant): add loading state to pay buttons during checkout…
allessandrobianchi Aug 27, 2026
e6f4d42
feat(indexer): handle Stellar re-orgs and cursor rollbacks (#276)
allessandrobianchi Aug 27, 2026
4aea8d0
feat(indexer): handle Stellar re-orgs and cursor rollbacks (#276)
Hamda-gbade Aug 27, 2026
9373a79
feat(indexer): handle Stellar re-orgs and cursor rollbacks (#276)
allessandrobianchi Aug 27, 2026
7a08c82
feat(web): add server-side pagination to transaction history (#259)
allessandrobianchi Aug 28, 2026
3b2cb1a
feat(web): add server-side pagination to transaction history (#259)
meem08 Aug 28, 2026
888fd03
feat(web): add server-side pagination to transaction history (#259)
allessandrobianchi Aug 28, 2026
1387fbb
Merge pull request #263 from larryjay007/feature/issue-135-sync-failu…
allessandrobianchi Aug 28, 2026
2df8844
Merge pull request #263 from larryjay007/feature/issue-135-sync-failu…
mallison031 Aug 28, 2026
32ee9e8
Merge pull request #263 from larryjay007/feature/issue-135-sync-failu…
allessandrobianchi Aug 28, 2026
8162418
feat: real-time indexer updates via Server-Sent Events (#163) (#280)
allessandrobianchi Aug 28, 2026
4a8b891
feat: real-time indexer updates via Server-Sent Events (#163) (#280)
allessandrobianchi Aug 28, 2026
25a015b
feat: track historical merchant configuration changes (#157)
allessandrobianchi Aug 28, 2026
4af71b1
feat: payments filter (#158)
allessandrobianchi Aug 28, 2026
61395d9
feat: payments filter (#158)
allessandrobianchi Aug 28, 2026
662915c
feat: micro-frontend scaffold (#162)
allessandrobianchi Aug 28, 2026
8843512
feat: micro-frontend scaffold (#162)
allessandrobianchi Aug 28, 2026
b574de2
feat(sdk): add multi-currency price formatting helpers (#144)
Aug 28, 2026
3b9337c
feat: custom 404 page + SDK timeout configuration (#134 #137)
Aug 28, 2026
a21f74b
Merge pull request #274 from dotunv/feat/multi-wallet-timestamps-merk…
wagmiiii Aug 28, 2026
5321581
feat: multi-currency price formatting helpers for SDK (#144)
Aug 28, 2026
e414a8e
feat: SDK bridgeless React Native + RBAC Zanzibar migration (#166 #18…
matteorossi-codes Aug 28, 2026
54561b7
feat: paginated sync history + contract event indexing (#146 #150)
Aug 28, 2026
eae1c67
feat: date filter, webhook verification, dashboard analytics (#142 #1…
francescoricc1 Aug 28, 2026
0ca7b10
feat: SDK verifyAuth + webhook management UI (#141 #147)
Aug 28, 2026
1cbed4e
Merge pull request #252 from bade2brazy/feat/wave-94-95-96-97
wagmiiii Aug 28, 2026
32b2d16
feat(sdk): standardize error handling with typed error classes (#258)
meem08 Aug 28, 2026
0ae670b
fix(dashboard): make payment rows keyboard accessible
Adejumo-2 Aug 28, 2026
b7a7335
fix: resolve pre-existing CI failures across typecheck, lint, format,…
Adejumo-2 Aug 28, 2026
daca525
fix(sdk): add missing timeoutMs to AccensaClientOptions
Adejumo-2 Aug 28, 2026
b3bb9cd
fix(web): handle large decimal amounts safely in the UI (#136) (#295)
Faith3112 Aug 28, 2026
0d5a5f8
Merge pull request #256 from bade22brazy/feat/wave-90-91-92-93
wagmiiii Aug 28, 2026
bf72b0b
Resolve merge conflicts
wagmiiii Aug 28, 2026
df8ff63
Merge pull request #261 from Jaydbrown/wave/accensa-app-gap-closing
wagmiiii Aug 28, 2026
be439aa
Resolve merge conflicts
wagmiiii Aug 28, 2026
fed1fe7
Resolve merge conflicts
wagmiiii Aug 28, 2026
a728a9d
Merge pull request #245 from thefadah/fix/issues-169-172
wagmiiii Aug 28, 2026
0a04dcc
Resolve conflicts by preferring PR changes
wagmiiii Aug 28, 2026
67a36b6
Merge pull request #238 from stephanieoghenemega-eng/feat/issues-15-3…
wagmiiii Aug 28, 2026
c9bcbc3
Resolve conflicts using PR version
wagmiiii Aug 28, 2026
df4d692
Merge pull request #294 from Adejumo-2/fix/190-keyboard-accessible-pa…
wagmiiii Aug 28, 2026
0b991d9
feat: WASM crypto signer + offline-first sync engine (#164 #175) (#293)
lorenzo-romano Aug 28, 2026
cb7fa10
Auto resolve
wagmiiii Aug 28, 2026
d9d1e0f
Merge pull request #292 from luciusverus-cyber/feat/verifyauth-webhoo…
wagmiiii Aug 28, 2026
4954ba7
Merge pull request #291 from codexhange/feat/index-events-paginate-sy…
wagmiiii Aug 28, 2026
d27d4af
Auto resolve
wagmiiii Aug 28, 2026
b3c372c
Merge pull request #289 from allessandrobianchi/feat/micro-frontend-s…
wagmiiii Aug 28, 2026
599656a
Auto resolve
wagmiiii Aug 28, 2026
444bef5
Merge pull request #288 from allessandrobianchi/feat/payments-filter-158
wagmiiii Aug 28, 2026
a3982c2
Auto resolve
wagmiiii Aug 28, 2026
131bc40
Merge pull request #287 from codexhange/feat/multi-currency-formatter…
wagmiiii Aug 28, 2026
abc17ae
Auto resolve
wagmiiii Aug 28, 2026
9a0b2bc
Merge pull request #285 from luciusverus-cyber/feat/404-page-sdk-time…
wagmiiii Aug 28, 2026
26cb003
Merge pull request #284 from nekwasar/feat/price-formatter
wagmiiii Aug 28, 2026
be135be
Merge pull request #283 from nekwasar/feat/micro-frontend-scaffold-162
wagmiiii Aug 28, 2026
c3e9409
Merge pull request #282 from nekwasar/feat/payments-filter-158
wagmiiii Aug 28, 2026
7b6d0d3
Auto resolve
wagmiiii Aug 28, 2026
ce599f9
Merge pull request #281 from allessandrobianchi/feat/merchant-config-…
wagmiiii Aug 28, 2026
93dd5f8
Auto resolve conflicts preferring PR
wagmiiii Aug 28, 2026
c4a988d
Merge pull request #249 from mhikel66/fix/issues-185-186-188-191
wagmiiii Aug 28, 2026
7870880
docs: add per-page SEO metadata (#279)
0dillon Aug 28, 2026
a406074
feat(sdk): add rate-limit retry wrapper and read caching (#155 #160) …
nekwasar Aug 28, 2026
57567be
chore: adopt prettier formatter and fix git conflict markers (#98)
samlogy1 Aug 29, 2026
55ac9ac
Merge branch 'main' into chore-98-adopt-formatter
timo126 Aug 29, 2026
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
35 changes: 29 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
with:
node-version: '22'
- name: Install pnpm
run: npm install -g pnpm@9
run: npm install -g pnpm@11
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Check formatting
Expand All @@ -33,7 +33,7 @@ jobs:
with:
node-version: '22'
- name: Install pnpm
run: npm install -g pnpm@9
run: npm install -g pnpm@11
- name: Install dependencies
run: pnpm install --frozen-lockfile
# Lints apps/web and packages/sdk from the root.
Expand All @@ -51,12 +51,16 @@ jobs:
with:
node-version: '22'
- name: Install pnpm
run: npm install -g pnpm@9
run: npm install -g pnpm@11
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test SDK
working-directory: ./packages/sdk
run: pnpm test
- name: Verify generated API types are reproducible
run: |
pnpm --filter @accensa/sdk gen:api
git diff --exit-code -- packages/sdk/generated/api-types.ts
- name: Verify conformance vectors are reproducible
run: |
node packages/sdk/scripts/generate-vectors.mjs
Expand Down Expand Up @@ -86,7 +90,7 @@ jobs:
with:
node-version: '22'
- name: Install pnpm
run: npm install -g pnpm@9
run: npm install -g pnpm@11
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test web
Expand All @@ -95,6 +99,25 @@ jobs:
DATABASE_URL: postgres://postgres:password@localhost:5432/accensa_test
run: pnpm test

test-reconcile:
name: test (reconcile-payments)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install pnpm
run: npm install -g pnpm@9
- name: Install dependencies
run: pnpm install --frozen-lockfile
# Unit tests only: synthetic and fixture data, no live RPC or database.
# The live reconciliation itself runs on a schedule, in reconcile.yml.
- name: Test reconcile-payments
working-directory: ./scripts/reconcile-payments
run: pnpm test

typecheck:
name: typecheck
runs-on: ubuntu-latest
Expand All @@ -105,7 +128,7 @@ jobs:
with:
node-version: '22'
- name: Install pnpm
run: npm install -g pnpm@9
run: npm install -g pnpm@11
- name: Install dependencies
run: pnpm install --frozen-lockfile
# Typechecks apps/web and packages/sdk from the root.
Expand All @@ -125,7 +148,7 @@ jobs:
with:
node-version: '22'
- name: Install pnpm
run: npm install -g pnpm@9
run: npm install -g pnpm@11
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Next.js app
Expand Down
59 changes: 59 additions & 0 deletions .github/workflows/reconcile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Reconcile Payments

# Independent proof that `payments` can be rebuilt from the Stellar ledger
# alone - see scripts/reconcile-payments/README.md and issue #170. Runs on a
# schedule and fails loudly on any row-level mismatch. A reconciliation nobody
# runs is the same as no reconciliation, which is what let a seven-day
# indexer outage pass unnoticed - see the issue for the full history.
#
# Required repository secrets:
# DATABASE_URL same production Postgres connection string the
# indexer uses (apps/web's DATABASE_URL)
# MERCHANT_ADDRESS same merchant Stellar account the indexer watches
# STELLAR_RPC_URL optional; defaults to the public testnet RPC
# ASSET_CONTRACT_IDS optional; defaults to apps/web's default SAC

on:
workflow_dispatch:
schedule:
- cron: '30 5 * * *' # daily

concurrency:
group: reconcile-payments
cancel-in-progress: false

jobs:
reconcile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install pnpm
run: npm install -g pnpm@9
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Rebuild from chain and diff against production
working-directory: ./scripts/reconcile-payments
env:
MERCHANT_ADDRESS: ${{ secrets.MERCHANT_ADDRESS }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
STELLAR_RPC_URL: ${{ secrets.STELLAR_RPC_URL }}
ASSET_CONTRACT_IDS: ${{ secrets.ASSET_CONTRACT_IDS }}
run: |
if [ -z "$MERCHANT_ADDRESS" ] || [ -z "$DATABASE_URL" ]; then
echo "::error::MERCHANT_ADDRESS and DATABASE_URL secrets are required."
exit 1
fi
# Full retention window: the RPC does not serve anything older, so
# there is nothing beyond this to reconstruct or to fault the tool for.
node cli.mjs --merchant "$MERCHANT_ADDRESS" --database-url "$DATABASE_URL" --out report.json
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: reconcile-payments-report
path: scripts/reconcile-payments/report.json
retention-days: 30
51 changes: 51 additions & 0 deletions .github/workflows/stale-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Indexer staleness check

# External-of-the-sync check for cursor lag and cessation (issue #90).
# Runs on its own schedule so that if sync.yml stops being scheduled, this
# still fires and the failure is visible. Point ALERT_WEBHOOK at Slack /
# a PagerDuty Events v2 endpoint / Discord so a red run also pages a human;
# without it the job still fails loudly in the Actions tab.

on:
workflow_dispatch:
schedule:
- cron: '17 */2 * * *'

concurrency:
group: indexer-stale-check
cancel-in-progress: false

jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Poll /api/health
env:
HEALTH_URL: ${{ secrets.HEALTH_URL }}
ALERT_WEBHOOK: ${{ secrets.ALERT_WEBHOOK }}
run: |
if [ -z "$HEALTH_URL" ]; then echo "HEALTH_URL secret is not set"; exit 1; fi

resp=$(curl -sS --max-time 30 -w '\n%{http_code}' "$HEALTH_URL" || echo $'\n000')
body=$(printf '%s' "$resp" | sed '$d')
code=$(printf '%s' "$resp" | tail -n1)
echo "HTTP $code"
echo "$body"

status=$(printf '%s' "$body" | grep -oE '"status":"[a-z]+"' | head -1 | cut -d'"' -f4)

if [ "$code" != "200" ] || [ "$status" = "critical" ]; then
msg="Accensa indexer health: HTTP $code status=${status:-unknown}. $body"
if [ -n "$ALERT_WEBHOOK" ]; then
curl -sS -X POST -H 'Content-Type: application/json' \
--data "$(printf '{"text":%s}' "$(printf '%s' "$msg" | python3 -c 'import json,sys;print(json.dumps(sys.stdin.read()))')")" \
"$ALERT_WEBHOOK" || true
fi
echo "::error::$msg"
exit 1
fi

if [ "$status" = "warn" ]; then
echo "::warning::indexer health is 'warn' — cursor lag rising, see /api/health"
fi
echo "indexer health OK"
23 changes: 9 additions & 14 deletions .github/workflows/sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,6 @@ jobs:
fi
echo "Unauthenticated GET correctly rejected with 401."

# Regression probe: with CRON_SECRET unset, `Bearer ${CRON_SECRET}`
# used to render as the literal string "Bearer undefined", and a
# caller sending that exact header compared equal and bypassed
# auth. The no-header check above can't catch this - it never sends
# a header at all.
code=$(curl -sS --max-time 60 -o /dev/null -w '%{http_code}' \
-H "Authorization: Bearer undefined" "$SYNC_URL")
if [ "$code" != "401" ]; then
echo "::error::GET with 'Authorization: Bearer undefined' returned HTTP $code," \
"expected 401. The unset-CRON_SECRET bypass has regressed."
exit 1
fi
echo "'Authorization: Bearer undefined' correctly rejected with 401."

- name: Call sync endpoint in a loop
env:
SYNC_URL: ${{ secrets.SYNC_URL }}
Expand Down Expand Up @@ -105,6 +91,15 @@ jobs:
echo "::warning::Cursor fell outside RPC retention; $skipped ledgers skipped."
fi

echo "Delivering queued payment webhooks..."
webhook_url="${SYNC_URL%/sync}/webhooks/deliver"
webhook_response=$(curl -sS --max-time 30 -w '\n%{http_code}' \
-H "Authorization: Bearer $CRON_SECRET" "$webhook_url" || echo "curl failed")
webhook_body=$(printf '%s' "$webhook_response" | sed '$d')
webhook_code=$(printf '%s' "$webhook_response" | tail -n1)
echo "webhook HTTP $webhook_code"
echo "$webhook_body"

echo "Sleeping for 5 minutes..."
sleep 300
done
50 changes: 50 additions & 0 deletions .github/workflows/visual.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Visual regression

on:
pull_request:
branches: ['main']
paths:
- 'apps/web/**'
- '.github/workflows/visual.yml'
push:
branches: ['main']
paths:
- 'apps/web/**'
- '.github/workflows/visual.yml'

jobs:
visual:
name: playwright (web)
runs-on: ubuntu-latest
timeout-minutes: 20
defaults:
run:
working-directory: ./apps/web
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install pnpm
run: npm install -g pnpm@9
working-directory: .
- name: Install dependencies
run: pnpm install --frozen-lockfile
working-directory: .
- name: Install Playwright Chromium
run: pnpm exec playwright install --with-deps chromium
- name: Run visual tests
env:
JWT_SECRET_KEY: visual-regression-test-secret
CI: true
run: pnpm test:visual
- name: Upload failure snapshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-visual-diff
path: |
apps/web/e2e/__screenshots__
apps/web/test-results
if-no-files-found: ignore
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ dist/
# full copies of the tree.
.claude/

# Local scratch: PR bodies passed to `gh pr create --body-file`, issue dumps,
# one-off fix scripts. See CONTRIBUTING.md — keep these under .scratch/ so they
# never end up tracked at the repo root (body.txt / issues.json did once).
.scratch/
/body.txt
/issues.json

# This is a pnpm workspace; pnpm-lock.yaml is the only lockfile.
# Stray npm/yarn lockfiles drift from it and change which package manager
# Vercel detects for a project.
Expand Down
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ We welcome contributions from the community! Whether it's a bug fix, new feature
5. **Create a new branch** for your feature or bug fix (`git checkout -b feature/my-new-feature` or `bugfix/issue-123`).
6. **Make your changes** and test them thoroughly.

## Scratch files

Anything that is tooling residue rather than part of the project — a PR body you
passed to `gh pr create --body-file`, a dump of issue data, a one-off migration
or fix script — goes under `.scratch/` at the repo root. That directory is
`.gitignore`d, so it cannot be committed by accident.

`body.txt` and `issues.json` were both tracked at the repo root once. Do not
re-create that: if you need a file like that, put it in `.scratch/`.

## Code Style

This project uses [Prettier](https://prettier.io/) for code formatting. Before committing, run:
Expand Down
Loading
Loading