diff --git a/.changelog/unreleased/642-dx1-versioning-feedback-docs.md b/.changelog/unreleased/642-dx1-versioning-feedback-docs.md new file mode 100644 index 00000000..58b8746b --- /dev/null +++ b/.changelog/unreleased/642-dx1-versioning-feedback-docs.md @@ -0,0 +1,11 @@ +--- +type: added +area: docs +pr: 642 +breaking: false +--- + +Documentation pages now carry a cookieless "was this helpful" control, and +the docs site can serve archived versions under a `/` prefix with a +version picker, alongside new `/concepts/unified-liquidity` and a rewritten +`/concepts/funding-and-fees` page. diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore index 0062c61f..a11303ce 100644 --- a/apps/docs/.gitignore +++ b/apps/docs/.gitignore @@ -1,3 +1,6 @@ .nitro-static/ .nitro/ .output/ +# DX-050: version snapshots are reproducible via scripts/snapshot-version.ts +# and are not authored content — see that script's header comment. +content-versions/ diff --git a/apps/docs/content/concepts/funding-and-fees.mdx b/apps/docs/content/concepts/funding-and-fees.mdx index 9ff0deec..6b9f8eb7 100644 --- a/apps/docs/content/concepts/funding-and-fees.mdx +++ b/apps/docs/content/concepts/funding-and-fees.mdx @@ -1,12 +1,91 @@ --- title: Funding and fees -description: How funding, transaction charges, borrowing costs, and price impact affect a perpetual position. -updated: 2026-08-24 +description: Every cost a leveraged position can accrue on SO4 — position fee, price impact, funding, borrow fee, and the network fee — with a worked multi-interval example. +updated: 2026-08-31 status: stable --- -Trading cost is more than a displayed fee because time, market balance, execution, and the Stellar transaction can each affect the result. +Opening or holding a position on SO4 can cost five distinct things: a position fee when size changes, price impact on the entry or exit price, recurring funding while the position stays open, a borrow fee on the collateral it locks up, and a network fee paid to the keeper that executes the order. All five are computed client-side today; none of them read a live value from the deployed contracts yet, which the [gaps](#where-the-client-diverges-from-the-contracts) section below covers in detail. -Funding transfers value between long and short positions. Price impact changes the execution estimate as a trade changes market exposure. Open interest describes positions that remain active. +## The five costs -Always review the transaction simulation and wallet prompt before signing. See [risk](/concepts/risk) for failure modes. +### Position fee + +Charged whenever a position's size changes, on both the increase (open) and the decrease (close) side, at `positionFeeBps` from `DEFAULT_FEE_CONFIG` — currently 10 basis points (0.10%) of the size delta. `useTradeFees.ts` accepts an `isIncrease` parameter but does not use it to vary the rate, so opening and closing the same size cost the same fee. A `Swap` trade uses `swapFeeBps` instead, also 10 basis points today. + +Source: `positionFeeBps` and `swapFeeBps`, `apps/web/src/features/trade/lib/data-store.ts`; applied in `apps/web/src/features/trade/hooks/useTradeFees.ts`. + +### Price impact + +An estimate shown as `priceImpactUsd` and, in the trade panel, as a percentage move applied to the entry price (`getEstimatedEntryPrice` in `apps/web/src/features/trade/lib/pricing.ts`). In the current client this is a flat `PRICE_IMPACT_BPS = 5` (0.05%) applied to every trade regardless of size or which side of the pool it falls on — not a function of pool imbalance, despite what the name suggests a GMX-style system would compute. + +Source: `PRICE_IMPACT_BPS`, `apps/web/src/features/trade/hooks/useTradeFees.ts`. + +### Funding + +A recurring transfer between the long and short sides of a market, intended to keep the market's price near the reference price named in [oracles](/concepts/oracles). `useFundingRate.ts` reads a signed `funding_factor_per_second` from `SyntheticsReaderClient.get_funding_info` — the comment on the generated type says it "mirrors `gmx_types::FundingInfo`" — and converts it to a fractional per-hour rate: `ratePerHour = fundingFactorPerSecond × 3600 / 10^30`. Under the GMX v2 convention this contract mirrors, a positive rate means the side with larger open interest pays the smaller side; SO4's client does not separately label which side is paying, so read the sign from the raw rate. + +Funding settles in fixed 8-hour epochs (`FUNDING_INTERVAL_MS` in `useFundingRate.ts`); the trade panel's `FundingRate` component (`apps/web/src/features/trade/components/FundingRate.tsx`) shows the current per-hour rate next to a countdown to the next epoch boundary, not the amount accrued so far. + +Source: `useFundingRate.ts`, `FundingRate.tsx`. + +### Borrow fee + +A fee for holding an open position, charged against the collateral it locks up rather than against trade size. `DEFAULT_FEE_CONFIG.borrowingRatePerHour` sets it to `0.0001` (0.01%/hour) in `data-store.ts`, and `useMarketsInfo.ts` reports the same fixed `0.0001` per market. A separate, unused helper, `estimateBorrowFeePerHour` in `trade-math.ts`, computes a different figure from a `borrowingFactorBps` parameter divided by both `10_000` and `24` — the extra division by 24 does not match a function documented as a per-hour rate, and this helper is not called from `useTradeFees` or any component today. + +Source: `borrowingRatePerHour`, `apps/web/src/features/trade/lib/data-store.ts`; `estimateBorrowFeePerHour`, `apps/web/src/features/trade/lib/trade-math.ts`. + +### Network (execution) fee + +Paid to the keeper that executes the order in a second transaction, shown in the trade panel as "Execution fee" with the tooltip "Paid to network keepers who execute your order." `minExecutionFeeXlm` is `0.3` XLM in `DEFAULT_FEE_CONFIG`; `useTradeFees.ts` converts it to a USD estimate using the current XLM mid price, falling back to `0.17` USD/XLM if no live price is available. + +Source: `minExecutionFeeXlm`, `data-store.ts`; conversion in `useTradeFees.ts`. + +## Summary + +Values below are current as of `apps/web/src/features/trade/lib/data-store.ts` and `apps/web/src/features/trade/hooks/useTradeFees.ts` on this branch; funding is the one row read live per market rather than configured client-side. + +| Cost | Current value | Charged when | +| --- | --- | --- | +| Position fee | 0.10% of size (10 bps) | Every increase or decrease of size | +| Swap fee | 0.10% of size (10 bps) | A `Swap`-type trade | +| Price impact | 0.05% of size (5 bps), flat | Every trade, same rate regardless of size | +| Funding | Signed rate from `SyntheticsReader`, per-hour, settled every 8 hours | Continuously, while a position is open | +| Borrow fee | 0.01%/hour (1 bp) of collateral | Continuously, while a position is open — not currently shown in the trade panel | +| Network (execution) fee | 0.3 XLM minimum per keeper execution | Once per increase and once per decrease | + +## Worked example: costs over a held position + +A trader opens a $10,000 long position and holds it across three consecutive 8-hour funding epochs (24 hours) before closing it. Assume the XLM price used for the execution-fee estimate is $0.17, and assume `SyntheticsReader.get_funding_info` for this market yields a `ratePerHour` of 0.00003 (0.003%/hour) for the whole window — an illustrative rate, not a value read from a deployed contract. + +**Open:** + +- Position fee: `10,000 × 0.0010 = $10.00` +- Price impact: `10,000 × 0.0005 = $5.00` +- Execution fee: `0.3 × 0.17 = $0.051` +- Open subtotal: `$15.051` + +**Held for 24 hours (3 × 8-hour epochs), funding only — the client does not accrue a borrow fee into any total today:** + +- Funding per hour: `10,000 × 0.00003 = $0.30` +- Funding over 24 hours: `0.30 × 24 = $7.20` + +**Close** (the client charges the same position fee and price impact rate on a decrease as on an increase, and a second execution fee for the keeper that executes the close): + +- Position fee: `10,000 × 0.0010 = $10.00` +- Price impact: `10,000 × 0.0005 = $5.00` +- Execution fee: `0.3 × 0.17 = $0.051` +- Close subtotal: `$15.051` + +**Total for the round trip:** `$15.051 + $7.20 + $15.051 = $37.302` — of which funding, the only cost that grows with how long the position stays open, is $7.20 for this one day. Held for a week at the same rate instead of a day, funding alone would be `10,000 × 0.00003 × 24 × 7 = $50.40`, more than three times the combined open and close costs. + +## Where the client diverges from the contracts {#where-the-client-diverges-from-the-contracts} + +- `fetchFeeConfig` in `data-store.ts` returns a hard-coded `DEFAULT_FEE_CONFIG` for every market; despite its name, it does not read DataStore. Position fee, swap fee, and the execution-fee minimum are the same for every market until that changes. +- Price impact is a flat rate, not a function of trade size or which side of the pool a trade adds to or removes from — see [price impact](#price-impact) above. +- The borrow fee is configured in two places that disagree (`data-store.ts` and `trade-math.ts`) and is not applied to `totalFeesUsd` in `useTradeFees.ts` or shown as a row in the trade panel. A position accrues it in the protocol regardless of what the interface currently displays. +- Funding is the one cost read live from a contract (`SyntheticsReader.get_funding_info`); everything else in this page is a client-side estimate that can differ from what the transaction actually settles for. + +## Related + +[/concepts/liquidation](/concepts/liquidation) — funding and borrow fees reduce the collateral backing a position, which moves its liquidation price. [/concepts/oracles](/concepts/oracles) — the price funding and price impact are measured against. [/concepts/risk](/concepts/risk) — the broader list of what can go wrong, including these costs being estimates. diff --git a/apps/docs/content/concepts/unified-liquidity.mdx b/apps/docs/content/concepts/unified-liquidity.mdx new file mode 100644 index 00000000..fb768f4e --- /dev/null +++ b/apps/docs/content/concepts/unified-liquidity.mdx @@ -0,0 +1,70 @@ +--- +title: Unified liquidity +description: Why one GM pool backs both sides of a market's trades, what a depositor is exposed to, and how the pool's balance limits leverage and price impact. +updated: 2026-08-31 +status: stable +--- + +Every SO4 market has exactly one pool, and that pool is the counterparty to every trade on the market — long or short, opening or closing. There is no separate book for longs and no separate book for shorts; a single balance of the long token and TUSDC backs both sides at once. This is what "unified liquidity" means in practice: one deposit funds every position, in either direction, that the market can hold. + +## How this differs from isolated liquidity + +An isolated-liquidity design gives each side of a market — or each market entirely — its own segregated pool, so a loss on one book cannot touch capital sitting in another. SO4's GM pool design does the opposite on purpose: the same TWBTC/TUSDC pool, for example, backs every long and every short on BTC/USD, and a liquidity provider's deposit is exposed to both sides simultaneously rather than to a single direction. + +The trade-off is capital efficiency against concentration. A unified pool needs less total capital to support a given amount of open interest, because a long trader's gain and a short trader's loss can net out of the same balance instead of requiring two separately funded books. It also means a depositor cannot choose to fund only the long side or only the short side — the deposit backs whatever the market's traders are doing, in aggregate, at all times. + +## What a depositor is exposed to + +A GM pool deposit is not a fixed-yield position. Depositing mints GM tokens representing a share of the pool (see [/guides/pools](/guides/pools) for the deposit and withdrawal mechanics); that share's value moves with three things happening at once: + +- **Net trader profit and loss.** When traders on the market are net profitable, the pool pays their gains out of its own balance — the pool is the counterparty, not a matched order book. When traders are net losing, those losses accrue to the pool as a gain. +- **The long token's price.** The pool holds a real balance of the long token (`longTokenAmount` in `PoolValueInfo`, from `SyntheticsReaderClient.getMarketPoolValueInfo`), so a depositor carries that token's price exposure directly, on top of any trader PnL. +- **Fee and funding income.** Position fees, borrow fees, and net funding paid by traders (see [/concepts/funding-and-fees](/concepts/funding-and-fees)) flow toward the pool as `totalBorrowingFees` and related fields in the same `PoolValueInfo` response. + +Stated without euphemism: if traders on a market are heavily net long into a sustained price rise, the pool is functionally short that token over the same period and loses value as the price rises, regardless of how the long token's own price performs in isolation. If traders are heavily net short into a decline, the pool loses in the opposite direction. A depositor who wants exposure to a token's price without also taking the other side of that market's trader positioning is holding the wrong instrument. + +## How pool composition limits leverage and price impact + +The pool's current balance directly caps how much size the market can support on each side. `useMarketsInfo.ts` computes the liquidity available to new long or short size as the pool's total USD value minus the open interest already outstanding on that side: + +``` +availableLiquidityLong = max(poolValueUsd - openInterestLong, 0) +availableLiquidityShort = max(poolValueUsd - openInterestShort, 0) +``` + +Both `poolValueUsd` (from `getMarketPoolValueInfo`) and `openInterestLong` / `openInterestShort` (from `getOpenInterest`) are read from `SyntheticsReaderClient` per market. As open interest on one side grows toward the pool's value, the liquidity left for more size on that side shrinks toward zero — a pool with heavy long skew has little room left for new longs even if the pool itself is large, because the same balance is already backing the existing long exposure. `maxLeverage` is currently a fixed `50` in `useMarketsInfo.ts` rather than a value read from `DataStore`, so it does not yet reflect this constraint per market. + +Price impact (see [/concepts/funding-and-fees](/concepts/funding-and-fees)) is meant to widen as a trade pushes a market further toward one-sided open interest, discouraging trades that would leave the pool more exposed. The current client applies a flat rate regardless of pool composition; that gap is documented on the funding-and-fees page rather than repeated here. + +## Where this lives in the contracts + +`SyntheticsReader` is the read path for all of this: `get_market_pool_value_info` for the pool's balances and PnL, and `get_open_interest` for the outstanding long and short exposure per market — see [/reference/synthetics-reader](/reference/synthetics-reader) for the full field list and units. Trades that change pool exposure enter through `ExchangeRouter`, described in [/reference/exchange-router](/reference/exchange-router). + +```text + Trader submits an order + │ + ▼ + ExchangeRouter.create_order() ──▶ OrderVault holds collateral + │ + ▼ + Keeper executes the order + │ + ▼ + Position opens or changes size against the market's one pool + │ + ├──▶ openInterestLong / openInterestShort shift (get_open_interest) + │ + ▼ + Pool's long-token and TUSDC balance, and its PnL, shift with the trade + (get_market_pool_value_info) ──▶ every depositor's GM share revalues +``` + +Caption: one order changes the same pool that every depositor's GM share is priced against — there is no separate ledger per side or per depositor. + +## Worked example + +A BTC/USD pool is worth $500,000 and currently carries $180,000 of long open interest and $60,000 of short open interest. Available liquidity for new longs is `500,000 − 180,000 = $320,000`; for new shorts it is `500,000 − 60,000 = $440,000`. A trader then opens a $250,000 long. Long open interest becomes `180,000 + 250,000 = $430,000`, and available liquidity for further longs drops to `500,000 − 430,000 = $70,000` — even though the pool's own value has not changed, the room left for the same side has shrunk by more than the new position's size relative to what remained, because the new long consumed most of what was available. A depositor who joined before this trade is now backing a pool with a much larger, and much more one-sided, long exposure than when they deposited. + +## Related + +[/guides/pools](/guides/pools) covers adding and removing liquidity and the deposit worked example. [/concepts/funding-and-fees](/concepts/funding-and-fees) covers the fee and price-impact side of the same mechanism. [/concepts/risk](/concepts/risk) is the full list of what can go wrong on the depositor side. diff --git a/apps/docs/content/developers/local-setup.mdx b/apps/docs/content/developers/local-setup.mdx index af24e112..e11d05b6 100644 --- a/apps/docs/content/developers/local-setup.mdx +++ b/apps/docs/content/developers/local-setup.mdx @@ -104,7 +104,7 @@ bun run --cwd apps/web typecheck bun run --cwd apps/web build ``` -**Key rule: run the whole gate, not a subset.** Even if you only changed a comment, run all commands. Turbo caches aggressively, so the full run is usually just a few seconds. +**Key rule: run the whole gate, not a subset.** Even if you only changed a comment, run all commands. Turbo caches aggressively, so the full run usually takes a few seconds. The gate exists to catch: diff --git a/apps/docs/content/guides/troubleshooting.mdx b/apps/docs/content/guides/troubleshooting.mdx index 0a1ac9f7..13cbe6ab 100644 --- a/apps/docs/content/guides/troubleshooting.mdx +++ b/apps/docs/content/guides/troubleshooting.mdx @@ -18,11 +18,11 @@ Likely causes, in order: 1. **The signature dialog is still open or was missed.** A Soroban transaction only leaves "pending" once your wallet returns a signed envelope. Check the wallet extension for a pending approval popup — it can open behind the browser window. 2. **The transaction expired before inclusion** (`tx_too_late` in [/reference/errors#tx-too-late](/reference/errors#tx-too-late)). Slow signing or a busy network can push the transaction past its time bounds. 3. **The submitted fee was too low for current network conditions** (`tx_insufficient_fee`). More likely during periods of high testnet/mainnet load. -4. **The RPC node is degraded.** If every submission from your session hangs, not just one, the endpoint itself may be slow — see "Network is busy" in [/reference/errors#timeout-or-try-again](/reference/errors#timeout-or-try-again). +4. **The RPC node is degraded.** If every submission from your session hangs, not only one, the endpoint itself may be slow — see "Network is busy" in [/reference/errors#timeout-or-try-again](/reference/errors#timeout-or-try-again). **Checks:** - Look for a wallet popup, including behind other windows. -- Reload the page and check whether the transaction hash (if you have one) appears on [Stellar Expert](https://stellar.expert) — if it does, it succeeded and the UI simply didn't confirm; if it doesn't after a few minutes, it did not land and is safe to resubmit. +- Reload the page and check whether the transaction hash (if you have one) appears on [Stellar Expert](https://stellar.expert) — if it does, it succeeded and the UI failed to confirm; if it doesn't after a few minutes, it did not land and is safe to resubmit. - Retry once. Soroban transactions are not resubmitted automatically by design — see [Write SO4 transactions](/developers/writing-transactions). ## Wallet will not connect @@ -72,7 +72,7 @@ Likely causes, in order: Likely causes, in order: 1. **Indexer lag**, same as "Balance not updating" above — positions listed through the indexer reflect its current sync height, not the chain tip. -2. **The order hasn't executed yet.** Opening a position through a limit or trigger order creates an *order* first; it only becomes a *position* once that order executes. Check the orders list, not just positions. +2. **The order hasn't executed yet.** Opening a position through a limit or trigger order creates an *order* first; it only becomes a *position* once that order executes. Check the orders list, not only positions. 3. **The transaction that would have created it failed.** Check `TxStatus` for a failure state and the message against [/reference/errors](/reference/errors) — a position that never opened has nothing to display. **Checks:** @@ -88,7 +88,7 @@ Likely causes, in order: 1. **An oracle read failed or is stale.** SO4 sources prices from more than one oracle; a temporarily stale or unavailable feed can leave the chart without fresh data rather than showing wrong data. 2. **A market data subscription dropped.** Realtime chart updates depend on a live connection; a network blip can leave the chart showing the last received point without visibly erroring. -3. **The selected market has no recent activity.** A thinly-traded market can look "stuck" simply because there's nothing new to plot. +3. **The selected market has no recent activity.** A thinly-traded market can look "stuck" because there is nothing new to plot. **Checks:** - Switch to a different, more active market and confirm its chart updates — this isolates whether the problem is connection-wide or market-specific. diff --git a/apps/docs/content/meta.json b/apps/docs/content/meta.json index 8ec13c91..d42d0768 100644 --- a/apps/docs/content/meta.json +++ b/apps/docs/content/meta.json @@ -23,7 +23,8 @@ "concepts/risk", "concepts/funding-and-fees", "concepts/liquidation", - "concepts/oracles" + "concepts/oracles", + "concepts/unified-liquidity" ] }, { diff --git a/apps/docs/content/reference/order-vault.mdx b/apps/docs/content/reference/order-vault.mdx index 77ceb5f5..f90fb7d0 100644 --- a/apps/docs/content/reference/order-vault.mdx +++ b/apps/docs/content/reference/order-vault.mdx @@ -62,7 +62,7 @@ In practice, this means the refund path is governed by the order lifecycle rathe - a failed or expired order should leave the vault in a recoverable state such that the original user can receive the unused collateral back; - a cancellation path releases the escrowed amount through the vault transfer-out primitive. -The key security rule is simple: the order-vault escrow is only as safe as the order handler's settlement logic. If the order never reaches a valid executed state, the refund path must release the deposit to the correct account and not leave a stranded balance in custody. +The key security rule is simple: the OrderVault escrow is only as safe as the order handler's settlement logic. If the order never reaches a valid executed state, the refund path must release the deposit to the correct account and not leave a stranded balance in custody. ## Worked example: cancelled order {#worked-example} @@ -98,9 +98,9 @@ For an order that fails at execution or expires before it can be filled, the sam ## Implementation notes {#implementation-notes} -The generated client is intentionally minimal. It records only the two transfer primitives the order-vault contract needs to support escrow movement: +The generated client is intentionally minimal. It records only the two transfer primitives the OrderVault contract needs to support escrow movement: - `recordTransferIn` to accept collateral into custody - `transferOut` to release collateral when the order settles or is cancelled -This is why the vault is best thought of as the escrow ledger for live orders rather than as a full order-management API. The actual lifecycle rules live in the order and execution handlers, while the vault simply enforces the asset movement that those lifecycle decisions require. +This is why the vault is best thought of as the escrow ledger for live orders rather than as a full order-management API. The actual lifecycle rules live in the order and execution handlers, while the vault only enforces the asset movement that those lifecycle decisions require. diff --git a/apps/docs/content/reference/synthetics-reader.mdx b/apps/docs/content/reference/synthetics-reader.mdx index 633adfa9..781f373c 100644 --- a/apps/docs/content/reference/synthetics-reader.mdx +++ b/apps/docs/content/reference/synthetics-reader.mdx @@ -140,4 +140,4 @@ The generated reader relies on several infrastructure contracts at read time: - `oracle` for pool pricing and value calculations - `orderHandler` for account-order lookups -This is why the raw contract API has explicit address arguments even for a read-only query. The reader is intentionally not a generic `DataStore` client: it is a protocol-specific view layer that translates data-store state into the market, pool, funding, and position objects the interface actually needs. +This is why the raw contract API has explicit address arguments even for a read-only query. The reader is intentionally not a generic `DataStore` client: it is a protocol-specific view layer that translates DataStore state into the market, pool, funding, and position objects the interface actually needs. diff --git a/apps/docs/content/resources/security.mdx b/apps/docs/content/resources/security.mdx index 6af8cf0d..bcbee745 100644 --- a/apps/docs/content/resources/security.mdx +++ b/apps/docs/content/resources/security.mdx @@ -25,7 +25,7 @@ Current deployments are testnet-only — see [Contract addresses](/reference/con Report privately instead, by messaging the project maintainer directly: [t.me/ibrahimijai](https://t.me/ibrahimijai). Include: -- What you found and why it's a vulnerability, not just unexpected behavior. +- What you found and why it's a vulnerability rather than unexpected behavior. - Steps to reproduce, or a proof of concept if you have one. - The affected contract, file, or endpoint. - Your assessment of severity and impact, if you have one — helpful, not required. @@ -55,7 +55,7 @@ There is no bug bounty program. Reports are still welcome and will be credited ( - **No independent audit** (above) — the largest single caveat on this page. - **Oracle dependence.** Pricing relies on external oracle feeds; a stale, manipulated, or unavailable feed can affect liquidations and execution prices. See [Risk](/concepts/risk). - **Testnet-only deployment.** Current contract addresses are testnet; testnet tokens and state carry no real value and can be reset. -- **Interface trust.** The interface prepares transactions for your wallet to sign; a compromised build of the interface (not just the contracts) could construct a malicious transaction. Always review what you're signing in your wallet, not just in the browser UI. +- **Interface trust.** The interface prepares transactions for your wallet to sign; a compromised build of the interface, not only the contracts, could construct a malicious transaction. Always review what you're signing in your wallet, not only in the browser UI. - **Rapidly changing code.** The protocol and interface are under active development; behavior documented today can change before an equivalent audit or review catches up. The full, longer list of what can go wrong — market, liquidation, oracle, contract, network, interface, and custody risk — lives at [/concepts/risk](/concepts/risk); this page covers the security-process side specifically (audits, disclosure, scope), not the trading-risk side. diff --git a/apps/docs/nitro.config.ts b/apps/docs/nitro.config.ts index 68d7578f..e05c83a9 100644 --- a/apps/docs/nitro.config.ts +++ b/apps/docs/nitro.config.ts @@ -26,6 +26,16 @@ export default defineNitroConfig({ { route: "/old-path", handler: "./redirect.ts" + }, + // DX-061: "was this helpful" feedback. `serverDir` is unset, so file-based + // route scanning under routes/ never runs (see scanServerRoutes in + // nitro's own source) — /old-path above only works because it is + // registered explicitly here, and this endpoint needs the same explicit + // registration for the same reason. + { + route: "/api/feedback", + method: "post", + handler: "./routes/api/feedback.post.ts" } ] }) diff --git a/apps/docs/public/assets/doc-version-picker.js b/apps/docs/public/assets/doc-version-picker.js new file mode 100644 index 00000000..7d9574e7 --- /dev/null +++ b/apps/docs/public/assets/doc-version-picker.js @@ -0,0 +1,19 @@ +// DX-050: versioned documentation routing — the version picker. +// +// Every