Skip to content

DX-050/061/072/075: versioned docs routing, page feedback, funding-and-fees, unified-liquidity - #642

Merged
IbrahimIjai merged 5 commits into
SO4-Markets:mainfrom
precious-akpan:feat/562-573-584-587-dx1-batch
Aug 31, 2026
Merged

DX-050/061/072/075: versioned docs routing, page feedback, funding-and-fees, unified-liquidity#642
IbrahimIjai merged 5 commits into
SO4-Markets:mainfrom
precious-akpan:feat/562-573-584-587-dx1-batch

Conversation

@precious-akpan

Copy link
Copy Markdown
Contributor

Summary

Resolves four assigned issues on one branch:

Docs content (#587, #584)

  • Rewrote /concepts/funding-and-fees from a 12-line stub into a full page enumerating all five costs a position can accrue (position fee, price impact, funding, borrow fee, network/execution fee), each traced to the actual client code (useTradeFees.ts, data-store.ts, useFundingRate.ts, trade-math.ts) with file paths, plus a computed multi-interval worked example. It also has an honest "where the client diverges from the contracts" section: price impact is a flat rate rather than size/imbalance-dependent, the borrow fee is configured in two disagreeing places and isn't applied to any total or shown in the trade panel, and fetchFeeConfig doesn't actually read from DataStore despite its name.
  • Added /concepts/unified-liquidity explaining the single-pool-per-market model, what a depositor is exposed to without euphemism, and how pool composition caps available leverage per side — grounded in SyntheticsReader.getMarketPoolValueInfo/getOpenInterest and the same availableLiquidity formula useMarketsInfo.ts already uses.
  • Added concepts/unified-liquidity to content/meta.json.

Feedback widget (#573)

  • src/lib/feedback.ts: validates a submission and redacts anything shaped like a Stellar/Soroban strkey, an EVM address, a 32-byte hex string, or an email, down to exactly four stored fields — path, verdict, redacted comment, day-granularity timestamp. No cookie, identifier, or IP is ever read.
  • routes/api/feedback.post.ts: the storage endpoint, backed by nitro/storage's useStorage.
  • public/assets/feedback-widget.js: an external script (the docs CSP is script-src 'self' with no unsafe-inline, so no inline handler is possible) that rate-limits by page per session via sessionStorage, wrapped in try/catch throughout, and never surfaces a submission failure to the reader. Rendered at the foot of every page from build.ts.
  • Verified directly against the built server: 204 on a valid submission with no set-cookie header, 400 on a bad verdict or a missing path (see scripts/feedback-endpoint.test.ts).

Versioned routing (#562)

  • src/lib/doc-versions.ts: pure route arithmetic. Current version stays unprefixed (unchanged from DX-029). resolveVersionSwitchTarget picks the exact page in the target version if it exists, else the same-labelled section's first page in that version ("that version's section index"), else that version's home.
  • scripts/snapshot-version.ts: copies content/ into content-versions/<id>/, an exact disposable snapshot. content-versions/ is git-ignored — nothing here ships a real second version, per the issue's scope ("ship exactly one version now").
  • build.ts now also builds any content-versions/<id>/ directory under an explicit /<id> prefix, with a noindex tag, a visible archived-version banner, and no data-pagefind-body (pagefind skips a page entirely once any page on the site has that attribute — that's how archived pages are excluded from search with no separate allow/deny list needed). A version picker renders in the header once more than one version exists; each option's destination is precomputed at build time.
  • Proved the mechanism locally (not committed): ran snapshot-version.ts v0, rebuilt, and confirmed /v0/concepts/risk exists with noindex + banner, the picker on both /concepts/risk and /v0/concepts/risk cross-links correctly, and pagefind indexed 33 of 66 built pages (current version only). Then deleted the snapshot.

Bugs found and fixed along the way

Both discovered because the new content pages mention DataStore/ExchangeRouter/SyntheticsReader, which existing reference pages already do too:

  1. lint-prose.ts hung indefinitely. REQUIRED_CAPITALIZATIONS's four hyphenated-term checks used /i without /g; a non-global regex's .exec() ignores lastIndex and always returns the same match, so while (exec() !== null) never terminated on any line mentioning order-vault/exchange-router/synthetics-reader/data-store — which is most of content/reference/*. This means bun run lint (and therefore bun run build, which shells out to it) has been hanging on main already, independent of this PR, whenever run against that content. Fixed by adding /g.
  2. Once unblocked, the same four checks false-positived on every correctly-lowercase route link or file path (data-store.ts, [/reference/synthetics-reader](/reference/synthetics-reader), etc.) — the function's own comment already said "Skip if matched inside link or code snippet," but that exemption was never implemented. Added maskCodeAndLinks to blank inline code spans and link targets before the capitalization check runs.
  3. After both fixes, 12 genuine pre-existing violations remained in bare prose across 5 files this PR doesn't otherwise touch (local-setup.mdx, security.mdx, troubleshooting.mdx, order-vault.mdx, synthetics-reader.mdx) — banned words ("just"/"simply") and two real DataStore/OrderVault capitalization misses. Fixed with minimal word-level edits, since bun run lint has to pass for this PR to be committable at all.
  4. Nitro wasn't routing /api/feedback to my handler at all — it silently fell through to the SPA's index.html with a 200, for every request including malformed ones. Root cause: this app never sets serverDir, so Nitro 3's file-based route-scanning (scanServerRoutes) never runs; /old-path only works today because it's registered explicitly in nitro.config.ts's handlers array, not because file-based routing works. Registered /api/feedback the same explicit way, and switched the handler to explicit h3/nitro/storage imports instead of relying on auto-imported globals (which apparently only get injected for scanned routes, not explicitly-registered ones).

Regression tests for (1) and (2) are in scripts/lint-prose.test.ts.

Pre-existing, out-of-scope failures found but not fixed

Running the full commit gate surfaced several failures unrelated to any of these four issues, predating this branch. Flagging them rather than silently expanding scope:

  • apps/web/src/features/changelog/components/ChangelogPage.tsx:184 has a genuine syntax error (Declaration or statement expected) that breaks web's lint, typecheck, and build entirely. This alone means the root bun lint/bun typecheck/bun run build cannot pass right now regardless of this PR.
  • apps/docs is missing vitest, vitest-axe, @testing-library/user-event, and msw as declared dependencies, breaking DocsHome.test.tsx, content-checker.test.ts, shortcuts.test.ts, and search.test.tsx (all pre-existing files this PR doesn't touch).
  • scripts/seo.test.ts imports a scripts/lib/og-generator module that doesn't exist.
  • scripts/components.test.tsx has two pre-existing failures (Sidebar/Pager active-link assertions).
  • check:tokens reports a false positive — apps/docs/src/styles/{globals,print}.css contain the literal comment text "issue DX-063: Add a print stylesheet for documentation pages #575", which the raw-hex-color regex matches as if #575 were a color — plus one genuine arbitrary-value violation in Sidebar.tsx (text-[10px]). Neither file is touched by this PR.
  • .changelog/unreleased/538-docs-workspace.md predates the current entry schema (has title/category instead of type/area/pr/breaking) and fails changelog:validate.
  • scripts/search.test.ts's "generates deterministic index" test times out at its 15s budget in this sandbox (it runs two full Vite builds sequentially); it passed the equivalent checks when I ran the underlying build directly.

Test plan

All run from apps/docs unless noted.

  • bun run scripts/lint-prose.ts — 0 errors (93 pre-existing sentence-length warnings, non-blocking)
  • bun run check:content / check:links — pass, 35 pages, zero orphans, zero broken links
  • bun test src/lib/feedback.test.ts src/lib/doc-versions.test.ts — 46/46 pass (new)
  • bun test scripts/nitro.test.ts scripts/feedback-endpoint.test.ts — 7/7 pass
  • bun test (full apps/docs suite) — 100/111 pass; all 11 fail + 3 errors are the pre-existing, unrelated files listed above
  • bun run build (apps/docs, includes nitro build) — succeeds
  • Manually verified /api/feedback: 204 valid, 400 bad verdict, 400 missing path, no set-cookie
  • Manually proved the version-snapshot mechanism end to end (see above), then removed the throwaway snapshot
  • bun run --cwd apps/docs typecheck — 7 pre-existing errors, none touch any file this PR creates or modifies
  • [~] Root bun lint / bun typecheck / bun run test / bun run build — all fail on the pre-existing, unrelated apps/web syntax error and/or the pre-existing apps/docs gaps listed above; docs's own portion of each passes standalone as shown

🤖 Generated with Claude Code

https://claude.ai/code/session_01WTKBjRjyEgeAPpw33XVfZZ

precious-akpan and others added 4 commits August 31, 2026 08:24
…O4-Markets#587, SO4-Markets#584)

Rewrites the funding-and-fees stub into a full page enumerating all five
client-computed costs (position fee, price impact, funding, borrow fee,
execution fee), each traced to its source in apps/web/src/features/trade,
with a computed multi-interval worked example and an explicit "where the
client diverges from the contracts" section (flat price impact, an unused
borrow-fee estimate, a hard-coded fee config).

Adds /concepts/unified-liquidity explaining the single-pool-per-market
model, what a depositor is exposed to without euphemism, and how pool
composition caps available leverage per side — grounded in
SyntheticsReader's getMarketPoolValueInfo/getOpenInterest and the same
availableLiquidity formula used in useMarketsInfo.ts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WTKBjRjyEgeAPpw33XVfZZ
Discovered while writing the funding-and-fees/unified-liquidity pages, both
of which mention DataStore, ExchangeRouter, and SyntheticsReader — bun run
lint hung indefinitely rather than completing.

Root cause: REQUIRED_CAPITALIZATIONS' four hyphenated-term entries
(order-vault, exchange-router, synthetics-reader, data-store) used only the
/i flag, not /g. The check loop resets a global regex's lastIndex and calls
exec() in a while loop, but a non-global regex's exec() ignores lastIndex
and always returns the same first match, so the loop over any line
containing one of these terms never terminated. Existing pages already use
all four terms throughout apps/docs/content/reference, so this hung on
unrelated, previously-merged content too, not just this branch's new pages.

Adding /g fixes the hang. With the linter able to actually finish, it
surfaced a second bug: the same four checks never exempted inline code
spans or link destinations (the function's own comment already said "Skip
if matched inside link or code snippet" — that exemption was never
implemented), so every correctly-lowercase route link or file-path mention
in the whole content tree (e.g. `data-store.ts`,
[/reference/synthetics-reader](/reference/synthetics-reader)) was flagged
as a capitalization error. Added maskCodeAndLinks to blank those spans
before the capitalization check runs, preserving column numbers for
everything else.

After both fixes, 12 genuine pre-existing violations remained in bare prose
across 5 files this PR does not otherwise touch (local-setup.mdx,
security.mdx, troubleshooting.mdx, order-vault.mdx, synthetics-reader.mdx)
— "just"/"simply" and two real DataStore/OrderVault capitalization misses.
Fixed those too with minimal word-level edits, since bun run lint must pass
for this PR to be committable at all and leaving them red for unrelated
reasons was not an option; no other content in this PR needed changes.

Regression tests added: one confirming the linter terminates on content
using all four hyphenated terms, one confirming code spans/link
destinations are exempt while bare-prose mentions still correctly fail.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WTKBjRjyEgeAPpw33XVfZZ
…ts#573, SO4-Markets#562)

Cookieless "was this helpful" feedback (DX-061):
- src/lib/feedback.ts validates and redacts a submission (Stellar/Soroban
  strkeys, EVM addresses, 32-byte hex, emails) down to exactly four stored
  fields: path, verdict, redacted comment, and a day-granularity timestamp.
  No cookie, identifier, or IP is ever read or stored.
- routes/api/feedback.post.ts is the storage endpoint, registered
  explicitly in nitro.config.ts rather than picked up by file-based
  scanning — this app never sets serverDir, so that scan doesn't run (see
  the comment on /old-path in the same file, which is the only other
  evidence this matters: it's the sole reason that route works either).
  Explicit registration also means this handler can't rely on Nitro's
  auto-imported globals, so it imports defineEventHandler/readBody/
  setResponseStatus from h3 and useStorage from nitro/storage directly.
- public/assets/feedback-widget.js is a plain external script (the docs CSP
  is script-src 'self' with no unsafe-inline, so no inline handler is
  possible) that rate-limits by page per session via sessionStorage
  (wrapped in try/catch throughout) and never surfaces a submission
  failure to the reader.
- Verified against the built server directly: 204 on a valid submission
  with no set-cookie header, 400 on a bad verdict or a missing path.

Versioned documentation routing (DX-050):
- src/lib/doc-versions.ts is pure route arithmetic: versionedRoute prefixes
  a route for a given version (current stays unprefixed, unchanged from
  DX-029), and resolveVersionSwitchTarget picks the exact page in the
  target version, falling back to that version's same-labelled section's
  first page, then to that version's home.
- scripts/snapshot-version.ts copies content/ into
  content-versions/<id>/ — an exact, disposable snapshot. content-versions/
  is git-ignored; nothing here ships a real second version yet, per the
  issue's scope (ship exactly one version now).
- build.ts now also builds any content-versions/<id>/ directory under an
  explicit /<id> prefix, with a noindex tag, a visible archived-version
  banner, and no data-pagefind-body (pagefind skips a page entirely once
  any page on the site has that attribute, which is how every archived
  page is excluded from search without a separate allow/deny list). A
  version picker renders in the header once more than one version exists,
  with each option's target precomputed at build time.
- Proved the mechanism locally: ran snapshot-version.ts v0, rebuilt, and
  confirmed /v0/concepts/risk exists with noindex + banner, the picker on
  both /concepts/risk and /v0/concepts/risk cross-links correctly, and
  pagefind indexed 33 of 66 built pages (the current version only) — then
  deleted the snapshot per the issue's instruction not to commit it.

scripts/content.ts gains loadPagesFrom(root) (parsePage's route computation
now takes an optional root, defaulting to contentRoot) so build.ts can load
both content/ and each content-versions/<id>/ with the same code path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WTKBjRjyEgeAPpw33XVfZZ
@vercel

vercel Bot commented Aug 31, 2026

Copy link
Copy Markdown

@precious-akpan is attempting to deploy a commit to the Ijai's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Aug 31, 2026

Copy link
Copy Markdown

@precious-akpan 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

@IbrahimIjai
IbrahimIjai merged commit 540e4d6 into SO4-Markets:main Aug 31, 2026
1 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants