Skip to content

feat(docs): try-it-live API console on /docs (spike 662) - #579

Closed
ExcelDsigN-tech wants to merge 44 commits into
PHASE-STELLAR:mainfrom
ExcelDsigN-tech:feat/docs-api-playground
Closed

ExcelDsigN-tech wants to merge 44 commits into
PHASE-STELLAR:mainfrom
ExcelDsigN-tech:feat/docs-api-playground

Conversation

@ExcelDsigN-tech

@ExcelDsigN-tech ExcelDsigN-tech commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds a "Try it live" console to /docs behind phase-docs-playground (default off). It has a typed request builder over the shared route contract, a response inspector that handles the API's awkward cases (JSON-RPC errors inside HTTP 200, the x402 shim's conditional 503, 429 with Retry-After), correlation-ID display, client-only wallet signing for signals, and a testnet fence for state-changing calls. Input is never lost on an error.

Built on #526 (feat/api-versioning-deprecation): it uses that branch's lib/api-contract.ts as the route manifest and shows the X-API-Version echo. Merge #526 first.

Changes

  • lib/api-playground.ts: request builder, testnet fence and response classifier (pure, unit-tested).
  • components/docs-api-playground.tsx: the console UI.
  • app/docs/page.tsx, lib/project-docs-content.ts: a playground section when NEXT_PUBLIC_FEATURE_PHASE_DOCS_PLAYGROUND is set.
  • lib/phase-copy.ts: playground strings, EN and ES.
  • lib/api-contract.ts: market and nft-listings writes verify no signature, so they're signing: "none".
  • scripts/proof-playground-662.cts (npm run proof:662): live proof runner using the same lib code.

Flag and rollback

NEXT_PUBLIC_FEATURE_PHASE_DOCS_PLAYGROUND, build-time, default off. Rollback: unset, rebuild and redeploy. The section disappears and nothing is persisted.

This flag was phase-162 until upstream assigned 162 to SEP-10 auth. It is now a named flag so it cannot collide again.

Tests

  • lib/__tests__/api-playground.test.ts (15): builder validation and encoding, docs-only marking, the fence, JSON-RPC-in-200, shim 503 vs outage, Retry-After forms, the #13 mapping, redaction, version echo, EN/ES key parity and section wiring.
  • tsc --noEmit: 0 errors. vitest run: green with flags off.
  • Local: npm run proof:662 passed against next dev (28 of 40 concurrent calls got 429 with Retry-After: 2), and a headless Chromium run confirmed send, response, correlation ID and input-kept-on-error.

Live proof

Production build run: proof:662 passed 5/5 (console present, JSON-RPC-in-200 classified, shim 503 recognized, correlation ID sent, 28/40 concurrent calls got 429 with Retry-After: 2). Details are in the spike doc under "Lab results". Freighter-signed signals.create on testnet returned 201 with signature_verified: true (local run, 2026-10-18), after the SEP-53 fix from chore/fix-main-baseline. With Freighter on Mainnet the console blocked the send with "Blocked: your wallet is not on testnet. Switch it to Test SDF Network." rpc.call getLatestLedger returned 200 with ledger 5255947 (protocol 29), and an unknown method showed "200 · JSON-RPC error inside an HTTP 200 response". Signal ids 78dtefUb2J and X_QqiTXggA; the blocked mainnet attempt created no signal. Local proof:662 against localhost passed 5/5 (.data/proofs/662-2026-10-18T06-15-36-736Z.json). Still pending: faucet tx hash and PHASELQ contract ID (need the project env), No screenshot was captured; the message text above was copied from the console during the run.

Follow-up

If-Match for edit routes, in-console changeTrust XDR, file upload, localized operation summaries, wiring the unused per-IP limiter, and correlation-ID echo on the listed routes.

Depends on

#574 (chore/fix-main-baseline). Merge that first; until it merges, this diff also shows its commits. This branch also builds on #575 (feat/api-versioning-deprecation, issue #526), so its diff includes those commits too.

Closes #525

PhaseFeatureFlag lost phase-146..153, phase-editions and phase-timed-drops,
and SecurityCounterName lost ~50 counters used by merged spikes. Restoring
them clears 96 of the 120 tsc errors on main. Also registers phase-159,
phase-161, phase-162 and phase-163 for spikes 659, 661, 662 and 663.
…chamber modules

0d14b9a ("fixed") re-encoded several files as cp1252-decoded UTF-8 and
prepended a BOM plus // @ts-nocheck. User-facing strings in the chamber
(e.g. "Transacción", "…") shipped corrupted, and a few glyphs (●, ”)
were lost outright. Files with no later commits are restored from
0d14b9a^; files edited since get only their mojibake comments repaired.

60001c5 (PHASE-STELLAR#33 refactor) moved ChamberLogStream, ChamberCatalogThumb,
PhaserLiqTokenLink and the chamber chrome classes out of fusion-chamber
into components/chamber/* but never committed those files, and left
five one-word "placeholder" files behind. The four modules are recreated
verbatim from the code it deleted; the unused placeholder imports and
files are removed.
- offers accept route: spike 674 collab-split fan-out (3e4b562), with the
  splits binding hoisted so the response can actually read it
- IpfsDisplayImg: `fallback` prop destructure (b89c4f2)
- wash-screening: washScreenReasonForEdition (3e4b562)
- viewer-signature: viewerSignatureShort (f9a08b6; removed by 7cc03e4
  while three components still import it)
- sqlite-db: exhibitions, price_suggestions, moderation_queue and
  quest_arcs tables (b89c4f2). Their absence failed 27 tests.
Real bugs the type errors were hiding:
- nft-index subscribe: validateWebhookUrl accepted https://127.0.0.1 and
  every 172.16/12, 100.64/10 and 0/8 literal (SSRF). Now a proper IPv4
  range check.
- lib/stellar.ts settle verification read meta v1/v3 only and called
  ContractEventV0.contractId(), which no longer exists, so every settle
  check fell through to SETTLE_EVENT_NOT_FOUND. It now reads meta v4
  (protocol 23) and v3 and takes the emitter from ContractEvent.
  Still fails closed on any mismatch.
- artist-attestation passed a string where an ScAddress is required and
  decoded an already-decoded retval, so attestation never read true.

Also: winback route called a nonexistent addNotification (now
createNotification with a winback_reactivation type), wallet-provider
declared `controller` twice, duplicate `ok` keys in ve-lock, and
Buffer/Uint8Array typing in the ipfs and avatar routes. wallet-provider
and stellar.ts are also restored from the 0d14b9a encoding damage.
- artist-profile-client imported describe/it from node:test, so vitest
  found no suite
- world-roles-rbac seeded worldRoles.json while the store reads
  world-roles.json (serverDataJsonPath("worldRoles"))
- avatar-provenance hashed a string through the bytes-only sha256Hex;
  encoding first gives the same digest
- forge-pipeline imported bun:test; the APIs it uses are identical in vitest
- viewer-authorization: Buffer typing for Keypair.sign
eslint.config.mjs imports eslint-config-next, which was never installed,
so npm run lint failed outright. dotenv is imported by scripts that the
root test suite loads (scripts/reset-phase.ts).
- ethnum 1.5.2 -> 1.5.3 in both lockfiles: 1.5.2 fails to compile on
  rustc 1.97 (E0512 transmute), which broke cargo test for both crates
- mock-token test called an associated fn as a method outside a contract
  context; it now goes through the generated MockTokenClient

phase-protocol still does not compile: see the baseline notes. Three
merged spikes each claimed PhaseError discriminants from 18 upward.
tsc is at 0 errors and vitest is green on this branch, so both can gate
PRs now. docs/investigations/main-baseline-2026-10-17.md records what was
fixed and what is blocked (phase-protocol discriminant collision).
…lient)

lib/stellar-wallet-kit.ts is loaded by the wallet provider on every route
and imported randomUUID from node:crypto. The client polyfill has no
randomUUID, so hydration threw and nothing interactive rendered. The nonce
path is browser-only, so it now uses globalThis.crypto.randomUUID().
… behind phase-163 (spike 663, PHASE-STELLAR#526)

- lib/api-contract.ts: typed source of truth for 24 operations, statuses
  read off each handler
- lib/api-versioning.ts: X-API-Version / Accept-Version negotiation,
  typed 406/410, X-API-Version echo, RFC 9745 Deprecation, RFC 8594
  Sunset, Link rel=deprecation/successor-version, OpenAPI 3.1 builder
  and breaking-change diff
- middleware.ts negotiates /api/* when the flag is on; api-observability
  logs and echoes the negotiated version (closed set)
- GET /api/versions serves versions, deprecations, changelog, or the
  OpenAPI document; 404 when the flag is off
- docs/api/openapi.json + changelog.json rendered by npm run api:contract
- CI: blocking gate that diffs against the target branch snapshot

Tests: lib/__tests__/api-versioning.test.ts (18 cases).
…663)

The middleware set `Vary: Accept-Version, X-API-Version` on the same
response where gated metadata, IPFS and OG routes set `Vary:
Authorization`, so a route's cache key could be replaced and a shared
cache could serve one viewer's gated response to another. Vary is now a
superset of every token an /api route uses (API_VARY_TOKENS), and a test
scans app/api so a new token cannot be missed.

- scripts/proof-api-versioning-663.ts (npm run proof:663): read-only
  checks of negotiation and headers on every listed route
- spike doc rewritten against the real issue, with checklist and follow-up
- docs/spikes/PR_663.md
…E-STELLAR#525)

- lib/api-playground.ts: typed request builder over lib/api-contract.ts
  (relative URLs, declared params/fields only, JSON-RPC shape check),
  testnet fence for state-changing and signed calls, and a response
  classifier for JSON-RPC errors in HTTP 200, the x402 shim 503,
  429 Retry-After (seconds or HTTP-date) and 406/410 version rejects
- gate PHASE-STELLAR#13 renders as [ ERROR: BIOMETRIC_TRUST_GATE_CLOSED ]; other
  PhaseError codes use the existing humanizer; secrets are redacted
- components/docs-api-playground.tsx: correlation ID sent vs echoed,
  version echo, client-only SEP-53 signing for signals, Retry-After
  countdown, per-operation input that no error clears
- EN/ES copy in lib/phase-copy.ts; docs section wired in
  project-docs-content and app/docs/page.tsx
- contract: market and listings writes verify no signature, so they
  are signing "none"

Tests: lib/__tests__/api-playground.test.ts (15 cases).
… (spike 662)

- scripts/proof-playground-662.cts (npm run proof:662) runs the console's
  own builder and classifier against a deployment: section present,
  JSON-RPC error inside 200, x402 shim 503, correlation id, and a
  concurrent burst that provokes the proxy's 429 + Retry-After
- console no longer shows the English-only contract summary (i18n rule)
- spike doc rewritten against the real issue, with what was verified
  locally (proof run + headless Chromium), checklist and follow-up
- docs/spikes/PR_662.md
proof:663 passed 15/15 with FEATURE_PHASE_163=1; with the flag off,
/api/versions is 404 and no version headers or negotiation happen.
/api/metadata keeps Authorization in Vary.
proof:662 passed 5/5 against a production build with both flags on.
Wallet signing and faucet steps still need testnet.
SEP-53 wallets sign SHA256("Stellar Signed Message:\n" + message). Signals,
world roles, avatar ownership and gated-preview viewer auth verified the
raw prefixed bytes instead, so every real Freighter signature was rejected
with "Invalid signature: payload not signed by this wallet". The unit
tests passed only because they signed the same raw bytes.

- lib/viewer-signature.ts: sep53SignedBytes + verifySep53Signature, used
  by verifySignalSignature, verifyWorldRoleSignature and the avatar route
- lib/viewer-authorization.ts: viewerSignatureBytes is now the SHA-256
  digest the wallet's key actually signs
- tests sign like a wallet; the official SEP-53 spec vectors are a
  regression test; tests/viewer-authorization.test.ts used an address
  unrelated to its seed and could never pass
Resolve lib/feature-flags.ts: keep the restored phase-146..153, editions
and timed-drops members alongside upstream phase-158..161. The spike flags
move to their own branches.
…api-versioning-deprecation

Keep both flag rows in the docs and re-add phase-163 to lib/feature-flags.ts.
…to feat/docs-api-playground

Keep both flag rows in the docs and re-add phase-162/phase-163 to lib/feature-flags.ts.
Resolves conflicts in the wallet provider, fusion chamber, security
counters, wallet kit, flags and copy. Registers phase-170..173, types the
new copy blocks, restores the legal data paths and QR scanner imports,
and drops the re-added @ts-nocheck and mojibake.
Upstream assigned phase-162..169 to other spikes. A named flag cannot
collide with later numbering.
…cation

# Conflicts:
#	PROJECT_ARCHITECTURE.md
#	lib/feature-flags.ts
…api-versioning

Upstream assigned phase-162..169 to other spikes. Named flags cannot
collide with later numbering.
…yground

# Conflicts:
#	PROJECT_ARCHITECTURE.md
#	docs/TECHNICAL.md
#	lib/feature-flags.ts
#	lib/phase-copy.ts
@drips-wave

drips-wave Bot commented Oct 20, 2026

Copy link
Copy Markdown

@ExcelDsigN-tech Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

# Spike 662 — Interactive docs playground with try-it-live API console

2 participants