diff --git a/apps/loopover-ui/content/docs/ai-summaries.mdx b/apps/loopover-ui/content/docs/ai-summaries.mdx index 4098925661..71bd6e8901 100644 --- a/apps/loopover-ui/content/docs/ai-summaries.mdx +++ b/apps/loopover-ui/content/docs/ai-summaries.mdx @@ -1,6 +1,7 @@ --- title: Optional AI summaries description: A short natural-language summary over the deterministic response. Off by default. Never the source of truth. +eyebrow: Roadmap · exploring --- ## The rule diff --git a/apps/loopover-ui/content/docs/ams-config-precedence.mdx b/apps/loopover-ui/content/docs/ams-config-precedence.mdx index 5b3d749fba..3dca2e4ce1 100644 --- a/apps/loopover-ui/content/docs/ams-config-precedence.mdx +++ b/apps/loopover-ui/content/docs/ams-config-precedence.mdx @@ -1,6 +1,7 @@ --- title: Miner config precedence description: How AMS layers configuration across per-repo goal spec, operator env, CLI flags, and operator policy files -- the order each concern actually implements today. +eyebrow: Maintainers --- AMS does **not** have a single `config.js` resolver. Configuration is layered by concern across @@ -87,8 +88,8 @@ First existing file wins (engine `MINER_GOAL_SPEC_FILENAMES`): This is an **AND** requirement, not "last writer wins". Either side missing or malformed → - dry-run. There is also **no CLI flag wired to governor live mode** today — - `attempt --live` / `loop --live` affect coding-agent spawn mode only (below). + dry-run. There is also **no CLI flag wired to governor live mode** today — `attempt --live` / + `loop --live` affect coding-agent spawn mode only (below). ### Coding-agent execution mode (spawn the driver?) @@ -151,9 +152,9 @@ cached, so a later call retries rather than staying stuck). requiring auth fail the same way they did before this feature existed). - This is a distinct concern from "Discover forge credential env var name" above, which resolves - the *name* of an env var to read, not the token *value* itself; `discover --token-env` is - unaffected by this section. + This is a distinct concern from "Discover forge credential env var name" above, which resolves the + *name* of an env var to read, not the token *value* itself; `discover --token-env` is unaffected + by this section. ### Local SQLite store paths diff --git a/apps/loopover-ui/content/docs/ams-deployment.mdx b/apps/loopover-ui/content/docs/ams-deployment.mdx index 7347bb922c..0e4af4c64e 100644 --- a/apps/loopover-ui/content/docs/ams-deployment.mdx +++ b/apps/loopover-ui/content/docs/ams-deployment.mdx @@ -1,6 +1,7 @@ --- title: AMS deployment guide description: Deploy @loopover/miner in laptop mode (single machine, zero Docker) or fleet mode (containerized workers) -- both 100% client-side, credentials never baked into images. +eyebrow: Maintainers --- Two form factors for running `@loopover/miner`: **laptop mode** (single machine, zero Docker) and @@ -57,9 +58,9 @@ loopover-miner init --interactive # optional: guided prompt for GITHUB_TOKEN `init --interactive` offers "Authorize with GitHub" (device flow — visit a URL, enter a short code, no token to copy or paste) as its first option once the miner's GitHub App OAuth client is configured; the original pasted-PAT prompt stays available as option 2, and is what the wizard - falls back to automatically on any device-flow failure. Unconfigured, the wizard is - byte-identical to the pasted-token-only prompt. Either way, the resulting `GITHUB_TOKEN` acts as - your own GitHub account — there is no separate bot identity. + falls back to automatically on any device-flow failure. Unconfigured, the wizard is byte-identical + to the pasted-token-only prompt. Either way, the resulting `GITHUB_TOKEN` acts as your own GitHub + account — there is no separate bot identity. ### Expected layout after first use @@ -150,9 +151,9 @@ The image entrypoint is `loopover-miner`; pass subcommands after the image name config` and any full-env dump of the running container. For Docker Swarm/Kubernetes-managed secrets (mounted as a file, e.g. at `/run/secrets/github_token`), set `GITHUB_TOKEN_FILE` to that mount path instead — the miner reads and trims the file's contents at startup and uses it exactly - as if `GITHUB_TOKEN` had been set directly. If both are set, the plain `GITHUB_TOKEN` value - always wins. A missing or unreadable `GITHUB_TOKEN_FILE` fails the container fast with a clear - error naming the file path, rather than silently proceeding with no credential. + as if `GITHUB_TOKEN` had been set directly. If both are set, the plain `GITHUB_TOKEN` value always + wins. A missing or unreadable `GITHUB_TOKEN_FILE` fails the container fast with a clear error + naming the file path, rather than silently proceeding with no credential. `docker compose -f docker-compose.miner.yml up -d --scale miner=N` gives every replica the **same** `miner-data` volume — and the miner's SQLite ledgers are **not** safe for concurrent - access, so N replicas on one volume will contend/corrupt. To run N **isolated** workers, give - each its own state: run N separate compose projects (`docker compose -p miner-1 …`, `-p miner-2 - …` — `-p` namespaces the volume) or point each at a distinct `LOOPOVER_MINER_CONFIG_DIR` on its - own mount. For built-in isolated horizontal scaling, use the Kubernetes StatefulSet in + access, so N replicas on one volume will contend/corrupt. To run N **isolated** workers, give each + its own state: run N separate compose projects (`docker compose -p miner-1 …`, `-p miner-2 …` — + `-p` namespaces the volume) or point each at a distinct `LOOPOVER_MINER_CONFIG_DIR` on its own + mount. For built-in isolated horizontal scaling, use the Kubernetes StatefulSet in [`k8s/`](https://github.com/JSONbored/loopover/tree/main/k8s) (per-pod volumes). diff --git a/apps/loopover-ui/content/docs/ams-discovery-plane.mdx b/apps/loopover-ui/content/docs/ams-discovery-plane.mdx index 999feb21cf..eb494a27bb 100644 --- a/apps/loopover-ui/content/docs/ams-discovery-plane.mdx +++ b/apps/loopover-ui/content/docs/ams-discovery-plane.mdx @@ -1,6 +1,7 @@ --- title: Hosted discovery plane — operator guide (opt-in) description: How a loopover-miner instance opts into the optional hosted discovery-index plane, what it may send, and what never leaves the operator's machine. +eyebrow: Maintainers --- Operator-facing guide for the **optional** hosted discovery-index plane. This is the client/miner diff --git a/apps/loopover-ui/content/docs/ams-env-reference.mdx b/apps/loopover-ui/content/docs/ams-env-reference.mdx index 90addd381d..86f261d6a8 100644 --- a/apps/loopover-ui/content/docs/ams-env-reference.mdx +++ b/apps/loopover-ui/content/docs/ams-env-reference.mdx @@ -1,6 +1,7 @@ --- title: AMS environment variable reference description: Every LOOPOVER_MINER_* / MINER_* env var the miner reads, where it's first referenced in code, and its default -- generated from source, never hand-copied. +eyebrow: Maintainers --- import { AMS_ENV_REFERENCE_MARKDOWN } from "@/lib/ams-env-reference"; diff --git a/apps/loopover-ui/content/docs/ams-fleet-manifest.mdx b/apps/loopover-ui/content/docs/ams-fleet-manifest.mdx index 26993590ac..bb83848515 100644 --- a/apps/loopover-ui/content/docs/ams-fleet-manifest.mdx +++ b/apps/loopover-ui/content/docs/ams-fleet-manifest.mdx @@ -1,9 +1,10 @@ --- title: Fleet run-manifest description: The top-level config a fleet operator authors to run the miner across many repos at once -- which repos are in scope and how the worktree/concurrency budget is split. +eyebrow: Maintainers --- -The **fleet run-manifest** is the top-level config a *fleet operator* authors to run the miner +The **fleet run-manifest** is the top-level config a _fleet operator_ authors to run the miner across many repos at once: it declares which repos are in scope for a fleet run and how a finite worktree/concurrency budget is split between them. It is parsed by `parseFleetRunManifestContent` / `parseFleetRunManifest` in `@loopover/engine` @@ -50,7 +51,7 @@ opposite direction of intent: This module produces only the parsed, typed manifest. Driving the fleet concurrency allocator from it is the allocator's own concern, and the cross-repo `portfolio-queue.js` backlog reads the -same repo list — both *consume* this manifest; neither wiring lives here. +same repo list — both _consume_ this manifest; neither wiring lives here. ## Example (`fleet-run.yml`) diff --git a/apps/loopover-ui/content/docs/ams-goal-spec.mdx b/apps/loopover-ui/content/docs/ams-goal-spec.mdx index 505c3705ec..ad5b726c24 100644 --- a/apps/loopover-ui/content/docs/ams-goal-spec.mdx +++ b/apps/loopover-ui/content/docs/ams-goal-spec.mdx @@ -1,6 +1,7 @@ --- title: "MinerGoalSpec (.loopover-miner.yml)" description: Per-repo configuration telling an autonomous LoopOver miner what to look for and how to behave when targeting a repo -- the full field reference. +eyebrow: Maintainers --- Per-repo configuration telling an autonomous LoopOver miner what to look for and how to behave @@ -17,8 +18,8 @@ Discovery order (first match wins): - `.github/loopover-miner.json` - Every field is optional. Unknown keys are ignored; a malformed field falls back to its - documented default with a warning — a broken file never hard-fails the miner. + Every field is optional. Unknown keys are ignored; a malformed field falls back to its documented + default with a warning — a broken file never hard-fails the miner. ## Relationship to `.loopover.yml` @@ -113,13 +114,12 @@ Per-repo kill-switch consulted by the Governor chokepoint before every write act Per-repo dry-run/live execution opt-in consulted by the Governor chokepoint. - A freshly-configured miner always defaults to dry-run (observe/log only, never execute a - write) — this field is the only per-repo path to live mode, and it alone is not sufficient: the - miner's own operator must also separately opt in globally (env var - `LOOPOVER_MINER_LIVE_MODE=live`) before writes actually execute. A repo that wants to guarantee - it never receives live automated writes, regardless of any operator's global setting, should use - `killSwitch.paused: true` instead — the kill-switch always takes precedence over any live-mode - opt-in. + A freshly-configured miner always defaults to dry-run (observe/log only, never execute a write) — + this field is the only per-repo path to live mode, and it alone is not sufficient: the miner's own + operator must also separately opt in globally (env var `LOOPOVER_MINER_LIVE_MODE=live`) before + writes actually execute. A repo that wants to guarantee it never receives live automated writes, + regardless of any operator's global setting, should use `killSwitch.paused: true` instead — the + kill-switch always takes precedence over any live-mode opt-in. - `liveModeOptIn` (string or `null`, default: `null`) — must equal EXACTLY the literal `"live"` to diff --git a/apps/loopover-ui/content/docs/ams-kill-switch-incident.mdx b/apps/loopover-ui/content/docs/ams-kill-switch-incident.mdx index 2326544be7..a7d4bac851 100644 --- a/apps/loopover-ui/content/docs/ams-kill-switch-incident.mdx +++ b/apps/loopover-ui/content/docs/ams-kill-switch-incident.mdx @@ -1,6 +1,7 @@ --- title: Kill-switch incident runbook description: Detect, activate, and audit a misbehaving Rent-a-Loop miner after the kill-switch stops it — grounded in the shipped #5670 mechanism. +eyebrow: Maintainers --- Operator runbook for a **misbehaving autonomous miner loop** once the emergency kill-switch has @@ -17,9 +18,9 @@ been (or must be) engaged. Grounded in the shipped mechanism from #5670 (`packag Two independent triggers compose into one scope per repo: -| Trigger | How | Scope reported | -| --- | --- | --- | -| Global | `LOOPOVER_MINER_KILL_SWITCH` env var (`1` / `true` / `yes` / `on`) | `"global"` — every repo | +| Trigger | How | Scope reported | +| -------- | -------------------------------------------------------------------------------- | ------------------------- | +| Global | `LOOPOVER_MINER_KILL_SWITCH` env var (`1` / `true` / `yes` / `on`) | `"global"` — every repo | | Per-repo | `killSwitch.paused: true` in that repo's `.loopover-miner.yml` (`MinerGoalSpec`) | `"repo"` — that repo only | A global halt **always wins** and is reported as `"global"` even when the per-repo flag is also set. diff --git a/apps/loopover-ui/content/docs/ams-observability.mdx b/apps/loopover-ui/content/docs/ams-observability.mdx index 555ba2d961..4446d987cf 100644 --- a/apps/loopover-ui/content/docs/ams-observability.mdx +++ b/apps/loopover-ui/content/docs/ams-observability.mdx @@ -1,6 +1,7 @@ --- title: Observing your miner description: Point Grafana at redacted AMS reporting exports to see attempt and prediction history without exposing the miner's live local ledgers. +eyebrow: Maintainers --- How to point Grafana at redacted miner reporting exports to see attempt and prediction history @@ -73,9 +74,9 @@ visualize AMS activity, add a dashboard JSON there — or import one at runtime Ledger` datasources above. - Panels should query only the redacted reporting schema (e.g. `SELECT * FROM - attempt_log_events`), never a `payload_json`/`reason` column — the exporter drops both, so a - panel referencing them returns no such column. + Panels should query only the redacted reporting schema (e.g. `SELECT * FROM attempt_log_events`), + never a `payload_json`/`reason` column — the exporter drops both, so a panel referencing them + returns no such column. ## Prometheus metrics (opt-in) diff --git a/apps/loopover-ui/content/docs/ams-operations-runbook.mdx b/apps/loopover-ui/content/docs/ams-operations-runbook.mdx index 54339ec160..5d66ac5674 100644 --- a/apps/loopover-ui/content/docs/ams-operations-runbook.mdx +++ b/apps/loopover-ui/content/docs/ams-operations-runbook.mdx @@ -1,6 +1,7 @@ --- title: AMS operations runbook description: Recover from SQLite lock contention, ledger corruption, and post-upgrade schema migrations in loopover-miner's local state. +eyebrow: Maintainers --- Operator-facing runbook for **local SQLite state**: what the concurrency guarantees actually mean, @@ -45,7 +46,7 @@ Default **5000 ms**; overridable per-test only — production stores always use { title: "Two short-lived writers, same file", description: - "E.g. a CLI command finishing while loop is idle, or Grafana reading while the miner appends. SQLite waits up to 5 seconds for the lock, then proceeds or surfaces \"database is locked\".", + 'E.g. a CLI command finishing while loop is idle, or Grafana reading while the miner appends. SQLite waits up to 5 seconds for the lock, then proceeds or surfaces "database is locked".', }, { title: "Append-only ledgers", @@ -67,9 +68,8 @@ Default **5000 ms**; overridable per-test only — production stores always use **Invariant:** one active loop (or one intentional writer set) per state directory. Horizontal - scale = **isolated state dirs** — separate compose projects, separate - `LOOPOVER_MINER_CONFIG_DIR`, or the Kubernetes StatefulSet pattern in the [AMS deployment - guide](/docs/ams-deployment). + scale = **isolated state dirs** — separate compose projects, separate `LOOPOVER_MINER_CONFIG_DIR`, + or the Kubernetes StatefulSet pattern in the [AMS deployment guide](/docs/ams-deployment). Quick health check — `doctor` includes `laptop-state-sqlite` (file exists + readable) and @@ -118,9 +118,9 @@ Re-run `loopover-miner doctor`. If locks persist with a single process, see **Le below. - Claims are local bookkeeping only. Two miners on different machines claiming the same GitHub - issue is a **fleet coordination** problem (duplicate-cluster adjudication in the engine), not - something SQLite resolves — split state dirs and use operational claim hygiene. + Claims are local bookkeeping only. Two miners on different machines claiming the same GitHub issue + is a **fleet coordination** problem (duplicate-cluster adjudication in the engine), not something + SQLite resolves — split state dirs and use operational claim hygiene. ## Backup and restore @@ -158,7 +158,7 @@ loopover-miner doctor --json # verify afterward`} /> It also removes any leftover `-wal`/`-shm` sidecar files from the live directory after restoring -each store — those hold in-flight writes from *before* the restore, and leaving them in place +each store — those hold in-flight writes from _before_ the restore, and leaving them in place would let SQLite silently replay stale pre-restore writes back on top of the freshly restored file on next open. @@ -184,10 +184,7 @@ Check disk space, permissions (`0600` file, `0700` parent), and backup tools cop **Remediation.** Stop the miner, then back up the whole state directory (even damaged files help post-mortems): - + Choose a recovery tier: @@ -267,8 +264,8 @@ tarball, pin the previous package version, and file an issue with the failing `u store filename. - Upgrade and restart **one worker/state dir at a time** so isolated workers never share a - directory mid-migration. + Upgrade and restart **one worker/state dir at a time** so isolated workers never share a directory + mid-migration. ## Related docs diff --git a/apps/loopover-ui/content/docs/ams-sizing.mdx b/apps/loopover-ui/content/docs/ams-sizing.mdx index dd05079ce8..c6069ff018 100644 --- a/apps/loopover-ui/content/docs/ams-sizing.mdx +++ b/apps/loopover-ui/content/docs/ams-sizing.mdx @@ -1,6 +1,7 @@ --- title: Resource sizing description: Real, measured CPU/RAM/disk numbers for laptop mode and fleet mode, so an operator can size a host or cluster from data instead of guessing. +eyebrow: Maintainers --- Real, measured CPU/RAM/disk numbers for laptop mode and fleet mode, so an operator can size a @@ -24,13 +25,12 @@ captured. Both modes measure `loopover-miner init` (laptop mode only) followed by `loopover-miner discover [...] --json` against real, small public repositories (`octocat/Hello-World`, `octocat/Spoon-Knife`) with **no `GITHUB_TOKEN`** — real, unauthenticated GitHub GET requests, metadata fan-out + deterministic ranking, no writes. - Sizing a real coding-agent attempt would need an operator-supplied `GITHUB_TOKEN` and - coding-agent CLI credentials, and creates a real branch/PR against a real target repository — - not something to spend for a resource-sizing exercise, and not reproducible by a reviewer - without supplying their own credentials. `discover` is measured instead because it is the - dominant, always-run, network- and CPU-bound phase every mode exercises identically; `attempt`'s - resource profile is dominated by the operator's chosen coding-agent CLI process, not by anything - this package controls. + Sizing a real coding-agent attempt would need an operator-supplied `GITHUB_TOKEN` and coding-agent + CLI credentials, and creates a real branch/PR against a real target repository — not something to + spend for a resource-sizing exercise, and not reproducible by a reviewer without supplying their + own credentials. `discover` is measured instead because it is the dominant, always-run, network- + and CPU-bound phase every mode exercises identically; `attempt`'s resource profile is dominated by + the operator's chosen coding-agent CLI process, not by anything this package controls. ## Methodology @@ -56,7 +56,7 @@ Both modes measure `loopover-miner init` (laptop mode only) followed by `loopove node packages/loopover-miner/bin/loopover-miner.js init --json LOOPOVER_MINER_CONFIG_DIR=/tmp/sizing-laptop /usr/bin/time -v \\ - node packages/loopover-miner/bin/loopover-miner.js discover octocat/Hello-World --dry-run --json +node packages/loopover-miner/bin/loopover-miner.js discover octocat/Hello-World --dry-run --json du -sh /tmp/sizing-laptop`} /> @@ -70,10 +70,10 @@ names/volumes):** docker volume create miner-sizing-1 docker run -d --name miner-sizing-1 -v miner-sizing-1:/data/miner --entrypoint sh loopover-miner:sizing \\ - -c "loopover-miner discover octocat/Hello-World octocat/Spoon-Knife --json > /tmp/out.json 2>&1; sleep 20" +-c "loopover-miner discover octocat/Hello-World octocat/Spoon-Knife --json > /tmp/out.json 2>&1; sleep 20" for i in $(seq 1 20); do - docker stats --no-stream --format '{{.Container}}\\t{{.CPUPerc}}\\t{{.MemUsage}}' miner-sizing-1 +docker stats --no-stream --format '{{.Container}}\\t{{.CPUPerc}}\\t{{.MemUsage}}' miner-sizing-1 done docker run --rm -v miner-sizing-1:/data alpine du -ah /data`} @@ -118,7 +118,7 @@ docker run --rm -v miner-sizing-1:/data alpine du -ah /data`} touches the larger per-attempt stores (`attempt-log.sqlite3`, `worktree-allocator.sqlite3`, etc.), which only grow once real attempts run. - The 333 MB fleet image is dominated by the Node 24 base image and `node_modules`; `npm prune - --omit=dev` in the Dockerfile already strips dev dependencies from the runtime stage. +--omit=dev` in the Dockerfile already strips dev dependencies from the runtime stage. See the [AMS deployment guide](/docs/ams-deployment) for the laptop-mode and fleet-mode setup walkthroughs these numbers apply to, and the [AMS operations diff --git a/apps/loopover-ui/content/docs/ams-unattended-scheduling.mdx b/apps/loopover-ui/content/docs/ams-unattended-scheduling.mdx index a8b5b1fbb9..0c4a68dd18 100644 --- a/apps/loopover-ui/content/docs/ams-unattended-scheduling.mdx +++ b/apps/loopover-ui/content/docs/ams-unattended-scheduling.mdx @@ -1,6 +1,7 @@ --- title: Unattended scheduling & failure alerting description: Run the miner's scheduled commands -- manage poll and discover -- unattended on cron or systemd, and alert reliably when a run fails. +eyebrow: Maintainers --- Operational guidance for running the miner's scheduled commands — `manage poll` and `discover` — @@ -9,9 +10,9 @@ commands most likely to run on a schedule; everything they need is local and the interactive prompts. - Scope: scheduling + failure alerting for `manage poll` / `discover`. For local-state recovery - see the [AMS operations runbook](/docs/ams-operations-runbook); for deployment layout see the - [AMS deployment guide](/docs/ams-deployment). + Scope: scheduling + failure alerting for `manage poll` / `discover`. For local-state recovery see + the [AMS operations runbook](/docs/ams-operations-runbook); for deployment layout see the [AMS + deployment guide](/docs/ams-deployment). ## The exit-code contract (what to alert on) @@ -48,12 +49,16 @@ MAILTO=you@example.com LOOPOVER_MINER_NO_UPDATE_CHECK=1 # Poll a tracked PR every 10 minutes. The || branch fires on any non-zero exit: it logs the failing + # code to syslog AND re-raises it with exit "$status", so the failure stays visible to exit-status + # monitoring instead of being masked by logger's own success. -*/10 * * * * /usr/local/bin/loopover-miner manage poll acme/widgets 42 --json || { status=$?; logger -t loopover-miner "manage poll failed (exit $status)"; exit "$status"; } + +_/10 _ \* \* \* /usr/local/bin/loopover-miner manage poll acme/widgets 42 --json || { status=$?; logger -t loopover-miner "manage poll failed (exit $status)"; exit "$status"; } # Discover + enqueue candidate work hourly. -0 * * * * /usr/local/bin/loopover-miner discover --search "label:good-first-issue" --json || { status=$?; logger -t loopover-miner "discover failed (exit $status)"; exit "$status"; }`} + +0 \* \* \* \* /usr/local/bin/loopover-miner discover --search "label:good-first-issue" --json || { status=$?; logger -t loopover-miner "discover failed (exit $status)"; exit "$status"; }`} /> Two cron facts to get right here: @@ -66,8 +71,8 @@ Two cron facts to get right here: `logger` succeeds (exit 0), so `cmd || logger …` makes the whole cron job exit 0 — any - exit-status-based monitoring then sees success. Capture the code first (`status=$?`) and - re-raise it (`exit "$status"`) as shown, so the real failing code survives. + exit-status-based monitoring then sees success. Capture the code first (`status=$?`) and re-raise + it (`exit "$status"`) as shown, so the real failing code survives. ## systemd (service + timer) @@ -86,7 +91,9 @@ OnFailure=loopover-miner-alert@%n.service [Service] Type=oneshot Environment=LOOPOVER_MINER_NO_UPDATE_CHECK=1 + # A non-zero exit (2) marks the unit failed and triggers OnFailure=. + ExecStart=/usr/local/bin/loopover-miner discover --search "label:good-first-issue" --json`} /> diff --git a/apps/loopover-ui/content/docs/backtest-calibration.mdx b/apps/loopover-ui/content/docs/backtest-calibration.mdx index 8d0d5e1ec9..41c7a89eae 100644 --- a/apps/loopover-ui/content/docs/backtest-calibration.mdx +++ b/apps/loopover-ui/content/docs/backtest-calibration.mdx @@ -1,6 +1,7 @@ --- title: Backtest & calibration description: How LoopOver measures whether its own review rules are right — and backtests threshold and logic changes against real history before they ship. +eyebrow: Core concepts --- ## What this is diff --git a/apps/loopover-ui/content/docs/beta-onboarding.mdx b/apps/loopover-ui/content/docs/beta-onboarding.mdx index 57bdd87469..6d947944b7 100644 --- a/apps/loopover-ui/content/docs/beta-onboarding.mdx +++ b/apps/loopover-ui/content/docs/beta-onboarding.mdx @@ -1,6 +1,7 @@ --- title: Beta onboarding by role description: "Pick the lane that matches you. Each path ends in a concrete first win — install, configure, or read a report — without treating LoopOver as an official Gittensor product surface." +eyebrow: Get started --- diff --git a/apps/loopover-ui/content/docs/branch-analysis.mdx b/apps/loopover-ui/content/docs/branch-analysis.mdx index 6d4c33d145..0580d493df 100644 --- a/apps/loopover-ui/content/docs/branch-analysis.mdx +++ b/apps/loopover-ui/content/docs/branch-analysis.mdx @@ -1,6 +1,7 @@ --- title: Branch analysis description: LoopOver analyzes branches using metadata only. Your source code never leaves your machine. +eyebrow: Core concepts --- ## Inputs @@ -33,19 +34,19 @@ Authorization: Bearer •••••••• Content-Type: application/json { - "login": "your-github-login", - "repoFullName": "entrius/gittensor", - "baseRef": "main", - "headRef": "feat/scorer-cleanup", - "changedFiles": [ - { "path": "src/scorer.ts", "additions": 42, "deletions": 8, "status": "modified" } - ], - "labels": ["scorer", "ready-for-review"], - "linkedIssues": [421], - "commitMessages": ["refactor scorer gating", "fix linked-issue projection"], - "validation": [ - { "command": "npm run typecheck", "status": "passed" } - ] +"login": "your-github-login", +"repoFullName": "entrius/gittensor", +"baseRef": "main", +"headRef": "feat/scorer-cleanup", +"changedFiles": [ +{ "path": "src/scorer.ts", "additions": 42, "deletions": 8, "status": "modified" } +], +"labels": ["scorer", "ready-for-review"], +"linkedIssues": [421], +"commitMessages": ["refactor scorer gating", "fix linked-issue projection"], +"validation": [ +{ "command": "npm run typecheck", "status": "passed" } +] }`} /> diff --git a/apps/loopover-ui/content/docs/capacity.mdx b/apps/loopover-ui/content/docs/capacity.mdx index 704ca99128..7c3840b54e 100644 --- a/apps/loopover-ui/content/docs/capacity.mdx +++ b/apps/loopover-ui/content/docs/capacity.mdx @@ -1,6 +1,7 @@ --- title: Capacity and throughput description: Real throughput and concurrency numbers for AMS's iterate-loop and the review-gate's PR-processing queue, so an operator can plan for load instead of guessing. +eyebrow: Maintainers --- Real throughput and concurrency numbers for AMS's iterate-loop and the review-gate's PR-processing @@ -39,15 +40,18 @@ REST GETs): items={[ { title: "SWEEP_MAX_PRS = 3", - description: "The recurring sweep's per-tick cap — sized for a sweep that re-runs every ~2 minutes, so a low steady-state budget compounds safely across ticks.", + description: + "The recurring sweep's per-tick cap — sized for a sweep that re-runs every ~2 minutes, so a low steady-state budget compounds safely across ticks.", }, { title: "ISSUE_WAKE_MAX_PRS = 25", - description: "One-shot budget for an issue-linked wake event — a rarer trigger than a merge, so a larger one-time budget doesn't risk compounding across repeated events in one rate-limit window.", + description: + "One-shot budget for an issue-linked wake event — a rarer trigger than a merge, so a larger one-time budget doesn't risk compounding across repeated events in one rate-limit window.", }, { title: "MERGE_WAKE_MAX_PRS = 15", - description: "One-shot budget for a merge-triggered wake — sized lower than the issue-wake budget because merges are a far more common trigger; a repeated-merge burst inside one rate-limit window must not compound the way the rarer issue-wake trigger safely can.", + description: + "One-shot budget for a merge-triggered wake — sized lower than the issue-wake budget because merges are a far more common trigger; a repeated-merge burst inside one rate-limit window must not compound the way the rarer issue-wake trigger safely can.", }, ]} /> @@ -61,21 +65,24 @@ attributes, read at `wrangler deploy` time (not runtime-configurable via `env.SO items={[ { title: "max_batch_size = 5", - description: "The most jobs one batch can bundle at once — bounds how many heavy sweep/backfill jobs land in a single delivery.", + description: + "The most jobs one batch can bundle at once — bounds how many heavy sweep/backfill jobs land in a single delivery.", }, { title: "max_concurrency = 8", - description: "Global consumer fan-out for multi-tenant volume (#4892) — aligned with the #4913 load-test concurrency=8 band. Per-installation admission (default limit 2) still caps how many background GitHub-budget jobs one tenant may run at once, so raising the global bound cannot let a single installation monopolize every worker slot.", + description: + "Global consumer fan-out for multi-tenant volume (#4892) — aligned with the #4913 load-test concurrency=8 band. Per-installation admission (default limit 2) still caps how many background GitHub-budget jobs one tenant may run at once, so raising the global bound cannot let a single installation monopolize every worker slot.", }, ]} /> - `max_batch_size × max_concurrency` (5 × 8 = 40) bounds Worker in-flight work. Rate-limit safety across - tenants comes from two already-shipped layers: each GitHub App installation has its own REST budget, - and `installation-concurrency-admission.ts` (#2970) caps per-install background GitHub fetches. Further - re-tuning still benefits from live `audit_events` / dashboard volume data when available — these defaults - are the architecture-justified baseline after the #4903–#4913 scaling work, not a production-volume fit. + `max_batch_size × max_concurrency` (5 × 8 = 40) bounds Worker in-flight work. Rate-limit safety + across tenants comes from two already-shipped layers: each GitHub App installation has its own + REST budget, and `installation-concurrency-admission.ts` (#2970) caps per-install background + GitHub fetches. Further re-tuning still benefits from live `audit_events` / dashboard volume data + when available — these defaults are the architecture-justified baseline after the #4903–#4913 + scaling work, not a production-volume fit. **Takeaways:** diff --git a/apps/loopover-ui/content/docs/federated-fleet-intelligence.mdx b/apps/loopover-ui/content/docs/federated-fleet-intelligence.mdx index 6df9453f91..e6d0926935 100644 --- a/apps/loopover-ui/content/docs/federated-fleet-intelligence.mdx +++ b/apps/loopover-ui/content/docs/federated-fleet-intelligence.mdx @@ -1,6 +1,7 @@ --- title: Federated fleet intelligence description: Opt-in sharing of anonymized gate-calibration aggregates between self-hosted LoopOver instances — what is shared, how consent works, and how trust is gated. +eyebrow: Self-hosting --- Federated fleet intelligence is an **opt-in** path for self-hosted LoopOver (ORB) operators who want to compare their own gate calibration against peer medians. Nothing is shared unless you turn it on in config-as-code. There is no default collector and no automatic peer discovery. @@ -15,20 +16,20 @@ Every field in the bundle body is aggregate-only. The export query never selects Fields currently enumerated on the signed body (`FederatedSignalBundleBody`): -| Field | Meaning | -| --- | --- | -| `schemaVersion` | Bundle contract version (receivers can reject unknown versions) | -| `instanceId` | Opaque per-instance handle (no PII) | -| `generatedAt` | ISO timestamp the bundle was built | -| `windowDays` | Calibration window length (so peers only median equal-length windows) | -| `decided` | Resolved PRs in-window that the gate decided | -| `mergePrecision` | P(merged & not reverted \| gate said merge), or null until enough samples | -| `closePrecision` | P(closed & not reopened \| gate said close), or null until enough samples | -| `fpRate` / `fnRate` | False-positive / false-negative rates, or null until eligible | -| `reversalRate` | Share of decided PRs a human reversed | -| `cycleP50Ms` / `cycleP95Ms` | Gate-decision → resolution latency percentiles | -| `slopRate` | Share of decided PRs whose reason bucketed to `slop_advisory` | -| `copycatRate` | Share whose reason bucketed to `duplicate_risk` (aggregate rate only) | +| Field | Meaning | +| --------------------------- | ------------------------------------------------------------------------- | +| `schemaVersion` | Bundle contract version (receivers can reject unknown versions) | +| `instanceId` | Opaque per-instance handle (no PII) | +| `generatedAt` | ISO timestamp the bundle was built | +| `windowDays` | Calibration window length (so peers only median equal-length windows) | +| `decided` | Resolved PRs in-window that the gate decided | +| `mergePrecision` | P(merged & not reverted \| gate said merge), or null until enough samples | +| `closePrecision` | P(closed & not reopened \| gate said close), or null until enough samples | +| `fpRate` / `fnRate` | False-positive / false-negative rates, or null until eligible | +| `reversalRate` | Share of decided PRs a human reversed | +| `cycleP50Ms` / `cycleP95Ms` | Gate-decision → resolution latency percentiles | +| `slopRate` | Share of decided PRs whose reason bucketed to `slop_advisory` | +| `copycatRate` | Share whose reason bucketed to `duplicate_risk` (aggregate rate only) | A detached `signature` accompanies the body. Export today signs with a local HMAC key; the trust design (#6477) calls for **operator-allowlisted peer public keys** on the receive path so verification does not require sharing an anonymization secret. @@ -64,9 +65,9 @@ To also arm the **transport client** (push this instance's bundle and/or pull pe The trust model (#6477) is intentional and checkable — not a reputation system: 1. **Explicit allowlist, not discovery.** Peer trust is operator-configured: you only consider bundles from sources you deliberately configured (a peer public key on your allowlist, or a collector URL you chose). There is no automatic peer discovery and no central PKI. -2. **Collector mode is the same rule.** Pointing at a collector *is* the trust decision. The client does not add a second peer-vetting layer on top of that choice. +2. **Collector mode is the same rule.** Pointing at a collector _is_ the trust decision. The client does not add a second peer-vetting layer on top of that choice. 3. **Median, not mean.** Peer comparisons use a **median** of peer calibration metrics. A median resists a bounded number of outlier/poisoned bundles; a mean does not. -4. **Sybil cost is on the receiver.** Flooding fake peers only matters if *you* added many fake keys to your own allowlist — that is self-limiting by construction. +4. **Sybil cost is on the receiver.** Flooding fake peers only matters if _you_ added many fake keys to your own allowlist — that is self-limiting by construction. 5. **Abuse recovery.** If a peer looks wrong, remove that key (or stop pointing at that collector). v1 does not ship a separate retroactive-detection subsystem. Until #6480's import/validation surface is live in your deployment, do not treat pulled files as automatically trusted calibration input. diff --git a/apps/loopover-ui/content/docs/github-app.mdx b/apps/loopover-ui/content/docs/github-app.mdx index c318da37da..ae9e5821b2 100644 --- a/apps/loopover-ui/content/docs/github-app.mdx +++ b/apps/loopover-ui/content/docs/github-app.mdx @@ -1,6 +1,7 @@ --- title: GitHub App configuration description: Install a LoopOver GitHub App on a repo so it reviews your pull requests, then choose whether it should stay advisory or enforce repo-configured PR quality rules. +eyebrow: Workflows --- Once installed, a **LoopOver GitHub App reviews every pull request** on the repos you select. @@ -155,17 +156,19 @@ settings: publicSurface: comment_only # Friendly gate alias (wins over settings: for gate fields): + gate: - enabled: true # review-agent check on/off - linkedIssue: advisory # block | advisory | off - duplicates: block - readiness: { mode: advisory, minScore: 60 } +enabled: true # review-agent check on/off +linkedIssue: advisory # block | advisory | off +duplicates: block +readiness: { mode: advisory, minScore: 60 } # Public review-panel content: + review: - footer: { text: "Reviewed by our bot." } # custom lead — the Gittensor register link is always appended - note: "Run npm test before requesting review." - fields: { relatedWork: false } # show/hide individual panel rows`} +footer: { text: "Reviewed by our bot." } # custom lead — the Gittensor register link is always appended +note: "Run npm test before requesting review." +fields: { relatedWork: false } # show/hide individual panel rows`} /> Maintainer-supplied footer and note text is dropped if it contains forbidden public language diff --git a/apps/loopover-ui/content/docs/how-reviews-work.mdx b/apps/loopover-ui/content/docs/how-reviews-work.mdx index ba11c2d0c2..0c55855e8c 100644 --- a/apps/loopover-ui/content/docs/how-reviews-work.mdx +++ b/apps/loopover-ui/content/docs/how-reviews-work.mdx @@ -1,6 +1,7 @@ --- title: How reviews work description: What LoopOver does when a pull request opens — the gate, the dual-AI review and consensus, and the single comment that surfaces it. +eyebrow: Reviews --- ## The shape of a review diff --git a/apps/loopover-ui/content/docs/loopover-commands.mdx b/apps/loopover-ui/content/docs/loopover-commands.mdx index a30ff6d566..0097d48a99 100644 --- a/apps/loopover-ui/content/docs/loopover-commands.mdx +++ b/apps/loopover-ui/content/docs/loopover-commands.mdx @@ -1,6 +1,7 @@ --- title: "@loopover command reference" description: The full PR and issue-comment control surface -- Q&A verbs, maintainer digests, and PR action commands. Roles reflect the shipped default policy; maintainers can override per command in .loopover.yml. +eyebrow: Commands --- import { diff --git a/apps/loopover-ui/content/docs/maintainer-install-trust.mdx b/apps/loopover-ui/content/docs/maintainer-install-trust.mdx index e9f2138a8a..8dba6c6297 100644 --- a/apps/loopover-ui/content/docs/maintainer-install-trust.mdx +++ b/apps/loopover-ui/content/docs/maintainer-install-trust.mdx @@ -1,6 +1,7 @@ --- title: Maintainer install and trust guide description: A maintainer-first checklist for self-hosting and installing a GitHub App, keeping public output safe, authorizing commands, using the browser extension, and rejecting weak LoopOver-driven PRs. +eyebrow: Launch guide --- import { MAINTAINER_COMMAND_LIST, PUBLIC_COMMAND_LIST } from "@/lib/command-reference"; diff --git a/apps/loopover-ui/content/docs/maintainer-self-hosting.mdx b/apps/loopover-ui/content/docs/maintainer-self-hosting.mdx index 3c4d7c39fa..7e39389395 100644 --- a/apps/loopover-ui/content/docs/maintainer-self-hosting.mdx +++ b/apps/loopover-ui/content/docs/maintainer-self-hosting.mdx @@ -1,6 +1,7 @@ --- title: Self-hosted reviews description: Run the LoopOver review service on your own infrastructure, with your own data store, GitHub App, AI provider, enrichment service, observability, and private repo policy. +eyebrow: Maintainers --- diff --git a/apps/loopover-ui/content/docs/maintainer-workflow.mdx b/apps/loopover-ui/content/docs/maintainer-workflow.mdx index 482db7f751..244f7a36d4 100644 --- a/apps/loopover-ui/content/docs/maintainer-workflow.mdx +++ b/apps/loopover-ui/content/docs/maintainer-workflow.mdx @@ -1,6 +1,7 @@ --- title: Maintainer workflow description: The default posture is silence. You opt into context. The repo stays calm. +eyebrow: Workflows --- import { MAINTAINER_COMMAND_LIST, PUBLIC_COMMAND_LIST } from "@/lib/command-reference"; diff --git a/apps/loopover-ui/content/docs/mcp-clients.mdx b/apps/loopover-ui/content/docs/mcp-clients.mdx index 8a4b08b28f..7a7259815d 100644 --- a/apps/loopover-ui/content/docs/mcp-clients.mdx +++ b/apps/loopover-ui/content/docs/mcp-clients.mdx @@ -1,6 +1,7 @@ --- title: MCP client setup description: Configure your coding agent to talk to the LoopOver MCP. Pick stdio for local agents, remote for cloud agents. +eyebrow: Get started --- ## Generate config diff --git a/apps/loopover-ui/content/docs/miner-coding-agent.mdx b/apps/loopover-ui/content/docs/miner-coding-agent.mdx index 1f14e9b9b4..8a9fccd434 100644 --- a/apps/loopover-ui/content/docs/miner-coding-agent.mdx +++ b/apps/loopover-ui/content/docs/miner-coding-agent.mdx @@ -1,6 +1,7 @@ --- title: Miner coding-agent driver description: Choose a production provider, override the right model and timeout knobs, and recognize credential failures before you chase the wrong layer. +eyebrow: Configuration --- The miner resolves `MINER_CODING_AGENT_PROVIDER` as a comma-separated preference list. The first @@ -47,6 +48,7 @@ MINER_CODING_AGENT_CLAUDE_MODEL= MINER_CODING_AGENT_TIMEOUT_MS=120000 # Prefer Codex, fall back to Claude. + MINER_CODING_AGENT_PROVIDER=codex-cli,claude-cli MINER_CODING_AGENT_CODEX_MODEL=`} /> diff --git a/apps/loopover-ui/content/docs/miner-quickstart.mdx b/apps/loopover-ui/content/docs/miner-quickstart.mdx index 6cb36e4ef2..acf0e7fcde 100644 --- a/apps/loopover-ui/content/docs/miner-quickstart.mdx +++ b/apps/loopover-ui/content/docs/miner-quickstart.mdx @@ -1,6 +1,7 @@ --- title: Miner quickstart by contribution lane description: Choose how you want to contribute, then follow the same loop — install, sign in, doctor, plan, preflight, packet — with the flags that fit your lane. About five minutes per lane. +eyebrow: Get started --- LoopOver is copilot-only. It ranks and explains your options and drafts public-safe PR packets. It @@ -24,11 +25,13 @@ npx -y @loopover/mcp@latest --help npm i -g @loopover/mcp@latest # sign in, confirm identity, check the session + loopover-mcp login loopover-mcp whoami --json loopover-mcp status --json # verify API, auth, and the local scorer before any analysis + loopover-mcp doctor --json`} /> diff --git a/apps/loopover-ui/content/docs/miner-workflow.mdx b/apps/loopover-ui/content/docs/miner-workflow.mdx index 48ce4e4fca..7733fe1118 100644 --- a/apps/loopover-ui/content/docs/miner-workflow.mdx +++ b/apps/loopover-ui/content/docs/miner-workflow.mdx @@ -1,6 +1,7 @@ --- title: Miner workflow description: A deterministic four-step loop. Each step is pure metadata; each output is structured JSON your agent can consume. +eyebrow: Workflows --- If the workflow will spawn Claude Code or Codex, configure that driver first in [Miner diff --git a/apps/loopover-ui/content/docs/owner-checklist.mdx b/apps/loopover-ui/content/docs/owner-checklist.mdx index 9cb6d157fe..354de7d615 100644 --- a/apps/loopover-ui/content/docs/owner-checklist.mdx +++ b/apps/loopover-ui/content/docs/owner-checklist.mdx @@ -1,6 +1,7 @@ --- title: Repo-owner onboarding checklist description: Work through this before you invite LoopOver contribution traffic. It mirrors the readiness report exactly, so each item is something the platform actually checks -- and each comes with the honest tradeoff you are opting into. +eyebrow: Repo owners --- LoopOver is **quiet by default**: it installs without posting comments or diff --git a/apps/loopover-ui/content/docs/privacy-security.mdx b/apps/loopover-ui/content/docs/privacy-security.mdx index e634948bce..4332364d28 100644 --- a/apps/loopover-ui/content/docs/privacy-security.mdx +++ b/apps/loopover-ui/content/docs/privacy-security.mdx @@ -1,6 +1,7 @@ --- title: Privacy & security description: Privacy is the product. These are hard rules, not best-effort goals. +eyebrow: Operating --- ## Hard rules @@ -61,12 +62,13 @@ LOOPOVER_REVIEW_IMPROVEMENT_SIGNAL="true" # read-only PR quality-delta sign LOOPOVER_REVIEW_AMS_REPUTATION_BRIDGE="true" # upgrade-only AMS track-record bridge (never shown; needs features.amsReputationBridge + LOOPOVER_AMS_TRACK_RECORD_URL) # Global (cron / endpoint) flags, not scoped by LOOPOVER_REVIEW_REPOS. -LOOPOVER_REVIEW_CONTINUOUS="true" # fleet-wide default: re-review on every push (else one-shot) -LOOPOVER_REVIEW_OPS="true" # read-only anomaly scan + outcome stats endpoint -LOOPOVER_REVIEW_SELFTUNE="true" # self-tightening tuning loop, never loosens -LOOPOVER_REVIEW_PARITY_AUDIT="true" # shadow-record gate-decision parity readiness -LOOPOVER_REVIEW_CONTENT_LANE="true" # dedicated content/registry-repo review lane -LOOPOVER_REVIEW_DRAFT="true" # public draft-submission (contributor fork PR) flow`} + +LOOPOVER_REVIEW_CONTINUOUS="true" # fleet-wide default: re-review on every push (else one-shot) +LOOPOVER_REVIEW_OPS="true" # read-only anomaly scan + outcome stats endpoint +LOOPOVER_REVIEW_SELFTUNE="true" # self-tightening tuning loop, never loosens +LOOPOVER_REVIEW_PARITY_AUDIT="true" # shadow-record gate-decision parity readiness +LOOPOVER_REVIEW_CONTENT_LANE="true" # dedicated content/registry-repo review lane +LOOPOVER_REVIEW_DRAFT="true" # public draft-submission (contributor fork PR) flow`} /> The internal-only controls never surface publicly. Submitter reputation, for example, can @@ -102,7 +104,7 @@ anti-abuse defaults that reveal no review direction and are not per-repo tunable ## Orb fleet telemetry (#4893) The always-on fleet-calibration pipeline (`POST /v1/orb/ingest`) aggregates anonymized signals -from every registered *and unregistered* self-host instance into one shared table +from every registered _and unregistered_ self-host instance into one shared table (`orb_signals`) — see [self-hosting: GitHub App](/docs/self-hosting-github-app#telemetry-is-separate-from-token-brokerage) for what's exported today and how to opt out. That table is intentional and not a leak: repo/PR identifiers are HMAC-anonymized with a per-instance secret LoopOver's own collector never holds, @@ -114,24 +116,23 @@ grows a hosted, per-customer model, what would be safe to add to this pipeline, wouldn't?** A clear yes/no per category, so a future PR that touches this table has a rule to check itself against instead of relitigating anonymization from scratch. -| Potential future data | Safe to add? | Why | -| --- | --- | --- | -| Raw repo/org names or logins, even opt-in | **No** | The anonymization promise is fleet-wide, not per-tenant-configurable — one customer opting into raw identifiers doesn't change what every *other* instance was told about this shared table. A hosted customer who wants their own data identifiable should read it back from their own instance/dashboard, never from the shared fleet table. | -| PR/commit content, diffs, or comments | **No** | Never queried into the export today (the collector's own DB query selects only verdict/outcome/reversal/timestamps) — there is no path for this to leave an instance short of a deliberate, separate feature. Adding it to fleet telemetry would be a new capability, not an extension of this one. | -| Full-text gate reasons | **No** | Already bucketed to a fixed, low-cardinality category before export specifically so free text (which can quote file paths, identifiers, or reviewer commentary) never leaves the instance. A hosted model doesn't change the fact that this text wasn't written for cross-tenant eyes. | -| A raw `installation_id` column on `orb_signals` itself | **No** | `orb_signals`/`orb_instances` were built with only an opaque, HMAC-derived `instance_id` — retrofitting a raw installation id onto that table would retroactively de-anonymize every row collected under the existing anonymity promise. If hosted billing/support ever needs to map a fleet instance to a paying installation, that mapping belongs in a **separate** table (mirroring how `orb_github_installations` already keeps raw account identity in its own table, entirely apart from `orb_signals`), not a new column here. | -| Per-instance metrics surfaced back to *that instance's own* operator | **Yes, with new authz** | `GET /v1/internal/fleet/analytics` already computes per-instance breakdowns keyed by the same opaque `instanceId` — the data already exists. A hosted per-customer dashboard could show a customer their own row. What doesn't exist yet is anything that maps "this session belongs to instance X" and enforces that a customer can only ever see their own opaque id, never another tenant's — that authz layer is new work, not a data-model change. | -| Cross-tenant fleet-median/percentile comparisons (e.g. gate precision vs. the peer median) | **Yes, unchanged** | Already shipped, in aggregate form only — a single percentile/median across the whole fleet, with no way to reverse-engineer any other specific tenant's raw value from it. This category is the one place "more data" is already safe, because it was designed to stay aggregate-only from the start. | +| Potential future data | Safe to add? | Why | +| ------------------------------------------------------------------------------------------ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Raw repo/org names or logins, even opt-in | **No** | The anonymization promise is fleet-wide, not per-tenant-configurable — one customer opting into raw identifiers doesn't change what every _other_ instance was told about this shared table. A hosted customer who wants their own data identifiable should read it back from their own instance/dashboard, never from the shared fleet table. | +| PR/commit content, diffs, or comments | **No** | Never queried into the export today (the collector's own DB query selects only verdict/outcome/reversal/timestamps) — there is no path for this to leave an instance short of a deliberate, separate feature. Adding it to fleet telemetry would be a new capability, not an extension of this one. | +| Full-text gate reasons | **No** | Already bucketed to a fixed, low-cardinality category before export specifically so free text (which can quote file paths, identifiers, or reviewer commentary) never leaves the instance. A hosted model doesn't change the fact that this text wasn't written for cross-tenant eyes. | +| A raw `installation_id` column on `orb_signals` itself | **No** | `orb_signals`/`orb_instances` were built with only an opaque, HMAC-derived `instance_id` — retrofitting a raw installation id onto that table would retroactively de-anonymize every row collected under the existing anonymity promise. If hosted billing/support ever needs to map a fleet instance to a paying installation, that mapping belongs in a **separate** table (mirroring how `orb_github_installations` already keeps raw account identity in its own table, entirely apart from `orb_signals`), not a new column here. | +| Per-instance metrics surfaced back to _that instance's own_ operator | **Yes, with new authz** | `GET /v1/internal/fleet/analytics` already computes per-instance breakdowns keyed by the same opaque `instanceId` — the data already exists. A hosted per-customer dashboard could show a customer their own row. What doesn't exist yet is anything that maps "this session belongs to instance X" and enforces that a customer can only ever see their own opaque id, never another tenant's — that authz layer is new work, not a data-model change. | +| Cross-tenant fleet-median/percentile comparisons (e.g. gate precision vs. the peer median) | **Yes, unchanged** | Already shipped, in aggregate form only — a single percentile/median across the whole fleet, with no way to reverse-engineer any other specific tenant's raw value from it. This category is the one place "more data" is already safe, because it was designed to stay aggregate-only from the start. | Anonymization is enforced entirely on the **sender**, not the receiver: `ORB_ANONYMIZE=false` - makes a self-host instance export raw repo/PR names, and `POST /v1/orb/ingest` has no - server-side check that a submitted `repo_hash`/`pr_hash` actually looks like a hash before - storing it in the shared table — it validates only type and length. Today this only affects an - operator who deliberately opted out for their own private collector. It becomes a sharper - problem the moment `/v1/orb/ingest` is fronted by anything resembling a hosted, multi-tenant - path: a misconfigured or malicious sender could land raw identifiers in a table every other - instance was told stays anonymized. Closing this (reject a submission whose `repo_hash`/`pr_hash` - doesn't match the expected HMAC-output shape) is real follow-up work, tracked separately from - this review. + makes a self-host instance export raw repo/PR names, and `POST /v1/orb/ingest` has no server-side + check that a submitted `repo_hash`/`pr_hash` actually looks like a hash before storing it in the + shared table — it validates only type and length. Today this only affects an operator who + deliberately opted out for their own private collector. It becomes a sharper problem the moment + `/v1/orb/ingest` is fronted by anything resembling a hosted, multi-tenant path: a misconfigured or + malicious sender could land raw identifiers in a table every other instance was told stays + anonymized. Closing this (reject a submission whose `repo_hash`/`pr_hash` doesn't match the + expected HMAC-output shape) is real follow-up work, tracked separately from this review. diff --git a/apps/loopover-ui/content/docs/quickstart.mdx b/apps/loopover-ui/content/docs/quickstart.mdx index 5fe46f9c87..e48f3142ef 100644 --- a/apps/loopover-ui/content/docs/quickstart.mdx +++ b/apps/loopover-ui/content/docs/quickstart.mdx @@ -1,6 +1,7 @@ --- title: Quickstart description: Install the MCP, sign in, and run your first analysis. About two minutes. +eyebrow: Get started --- ## 1. Install @@ -13,6 +14,7 @@ install it globally. npx -y @loopover/mcp@latest --help # install + npm i -g @loopover/mcp@latest`} /> diff --git a/apps/loopover-ui/content/docs/scoreability.mdx b/apps/loopover-ui/content/docs/scoreability.mdx index fa0df76df3..ea0499a306 100644 --- a/apps/loopover-ui/content/docs/scoreability.mdx +++ b/apps/loopover-ui/content/docs/scoreability.mdx @@ -1,6 +1,7 @@ --- title: Scoreability description: LoopOver projects how scoreable your branch is under several scenarios. These are estimates, never guarantees. +eyebrow: Core concepts --- ## The seven scenarios diff --git a/apps/loopover-ui/content/docs/self-hosting-ai-providers.mdx b/apps/loopover-ui/content/docs/self-hosting-ai-providers.mdx index f1cecbb722..d1beb75912 100644 --- a/apps/loopover-ui/content/docs/self-hosting-ai-providers.mdx +++ b/apps/loopover-ui/content/docs/self-hosting-ai-providers.mdx @@ -1,6 +1,7 @@ --- title: AI providers description: AI review is optional. The deterministic gate still runs when no provider is configured. +eyebrow: Self-hosting --- ## Provider options diff --git a/apps/loopover-ui/content/docs/self-hosting-backup-scaling.mdx b/apps/loopover-ui/content/docs/self-hosting-backup-scaling.mdx index c943ebaaca..dab078d0a0 100644 --- a/apps/loopover-ui/content/docs/self-hosting-backup-scaling.mdx +++ b/apps/loopover-ui/content/docs/self-hosting-backup-scaling.mdx @@ -1,6 +1,7 @@ --- title: Backup and scaling description: Choose the right data layout for one node or many, and make sure the review state can be restored. +eyebrow: Self-hosting --- ## Default: SQLite single node diff --git a/apps/loopover-ui/content/docs/self-hosting-configuration.mdx b/apps/loopover-ui/content/docs/self-hosting-configuration.mdx index a6eb019e28..6fa9a2ef3b 100644 --- a/apps/loopover-ui/content/docs/self-hosting-configuration.mdx +++ b/apps/loopover-ui/content/docs/self-hosting-configuration.mdx @@ -1,6 +1,7 @@ --- title: Configuration description: "The self-host configuration model: deployment env, private per-repo policy, feature flags, and review modes." +eyebrow: Self-hosting --- import { SELFHOST_ENV_REFERENCE_MARKDOWN } from "@/lib/selfhost-env-reference"; @@ -81,6 +82,7 @@ Start from a template instead of reverse-engineering env flags, private-config p cp config/examples/loopover.minimal.yml .loopover.yml # Self-host private mount (operator-only policy) + mkdir -p loopover-config cp config/examples/global.loopover.yml loopover-config/.loopover.yml`} /> @@ -366,7 +368,7 @@ A cluster of contributor-abuse guardrails, all config-as-code only, all off/unse - **Exemptions and account age** — `autoCloseExemptLogins` is a shared, repo-scoped list of logins never throttled or closed by these deterministic mechanisms, on top of the standing owner/admin/bot exemption. `accountAgeThresholdDays` (default `null`, off) applies `newAccountLabel` (default `new-account`) to a PR from a below-threshold-age account — friction/visibility only, never an automatic close on account age alone, and never for the owner, admins, or bots. - **Command rate limit** — `commandRateLimitPolicy` (`off`/`hold`, default `off`) generalizes the review-nag pattern to every `@loopover` command, not just review-request pings. `commandRateLimitMaxPerWindow` (default `20`) bounds cheap, cache-only commands; `commandRateLimitAiMaxPerWindow` (default `5`) is the tighter limit for AI-cost-bearing commands (ask/blockers/preflight/etc.); `commandRateLimitWindowHours` (default `24`) is the rolling window both limits count against. -### settings.issuePlan* +### settings.issuePlan\* Config for `loopover_plan_repo_issues`, the AI issue-planning MCP tool: given a maintainer-supplied free-form goal, it proposes a small set of GitHub issue drafts (dry-run by default) and, on explicit request, creates them via the same installation-token/Orb-broker path every other GitHub write in this repo uses — never a flat PAT, so it only works on a repo the App/Orb is actually installed on. diff --git a/apps/loopover-ui/content/docs/self-hosting-docs-audit.mdx b/apps/loopover-ui/content/docs/self-hosting-docs-audit.mdx index bab92eef75..feb51302e0 100644 --- a/apps/loopover-ui/content/docs/self-hosting-docs-audit.mdx +++ b/apps/loopover-ui/content/docs/self-hosting-docs-audit.mdx @@ -1,6 +1,7 @@ --- title: Docs accuracy audit description: Before a self-host release, use this checklist to confirm website docs still match runtime defaults. Each row links a docs page to the files and CI drift guards that must stay aligned. +eyebrow: Self-hosting --- import { Link } from "@tanstack/react-router"; diff --git a/apps/loopover-ui/content/docs/self-hosting-github-app.mdx b/apps/loopover-ui/content/docs/self-hosting-github-app.mdx index 0bffb9749c..0a04dc949c 100644 --- a/apps/loopover-ui/content/docs/self-hosting-github-app.mdx +++ b/apps/loopover-ui/content/docs/self-hosting-github-app.mdx @@ -1,6 +1,7 @@ --- title: GitHub App and Orb description: A self-host needs webhook delivery and installation tokens. Direct GitHub App is the default, recommended model -- Orb broker mode is private/managed-beta only. +eyebrow: Self-hosting --- ## Choose a connection mode diff --git a/apps/loopover-ui/content/docs/self-hosting-operations.mdx b/apps/loopover-ui/content/docs/self-hosting-operations.mdx index 5a653282ad..e1fdcb96a9 100644 --- a/apps/loopover-ui/content/docs/self-hosting-operations.mdx +++ b/apps/loopover-ui/content/docs/self-hosting-operations.mdx @@ -1,6 +1,7 @@ --- title: Operations description: "Daily operating checks for the review service: health, queue, logs, metrics, dashboards, and context services." +eyebrow: Self-hosting --- ## Health endpoints @@ -494,31 +495,29 @@ repeat the extension block in your override file: TEMP: /tmp/runner/tmp services: - runner-2-tmp-init: - image: alpine:3.20 - profiles: ["runners"] - volumes: - - runner-work-2:/tmp/runner - command: ["sh", "-c", "mkdir -p /tmp/runner/tmp && chmod 1777 /tmp/runner/tmp"] - - runner-2: - image: myoung34/github-runner:ubuntu-jammy - profiles: ["runners"] - depends_on: - runner-2-tmp-init: - condition: service_completed_successfully - environment: - <<: *runner-tmp-env - RUNNER_NAME: loopover-runner-2 - RUNNER_SCOPE: \${RUNNER_SCOPE:-repo} - REPO_URL: \${RUNNER_REPO_URL:-} - RUNNER_TOKEN: \${RUNNER_TOKEN:-} - RUNNER_WORKDIR: /tmp/runner - volumes: - - runner-work-2:/tmp/runner +runner-2-tmp-init: +image: alpine:3.20 +profiles: ["runners"] +volumes: - runner-work-2:/tmp/runner +command: ["sh", "-c", "mkdir -p /tmp/runner/tmp && chmod 1777 /tmp/runner/tmp"] + +runner-2: +image: myoung34/github-runner:ubuntu-jammy +profiles: ["runners"] +depends_on: +runner-2-tmp-init: +condition: service_completed_successfully +environment: +<<: \*runner-tmp-env +RUNNER_NAME: loopover-runner-2 +RUNNER_SCOPE: \${RUNNER_SCOPE:-repo} +REPO_URL: \${RUNNER_REPO_URL:-} +RUNNER_TOKEN: \${RUNNER_TOKEN:-} +RUNNER_WORKDIR: /tmp/runner +volumes: - runner-work-2:/tmp/runner volumes: - runner-work-2:`} +runner-work-2:`} /> ## Enabling Sentry (your own DSN) @@ -973,6 +972,7 @@ then persists the resolved image reference back to `LOOPOVER_IMAGE` in ./scripts/deploy-selfhost-image.sh # Pin an exact release tag or content digest + ./scripts/deploy-selfhost-image.sh ghcr.io/jsonbored/loopover-selfhost:orb-v0.1.0 LOOPOVER_IMAGE=ghcr.io/jsonbored/loopover-selfhost@sha256:... ./scripts/deploy-selfhost-image.sh`} /> @@ -1017,7 +1017,9 @@ script assumes that checkout already exists and already tracks `origin/main`. SELFHOST_UPDATE_REMOTE=upstream SELFHOST_UPDATE_BRANCH=main ./scripts/selfhost-update.sh # Skip the health probe step (e.g. you'll run it yourself, or right after a schema-changing release + # where you want to inspect logs before curling /ready) + SELFHOST_SKIP_POST_UPDATE_CHECK=1 ./scripts/selfhost-update.sh`} /> @@ -1112,8 +1114,9 @@ observation window by design (3 minutes by default): code={`./scripts/selfhost-post-update-regression-gate.sh # Tune the observation window and/or the dead-job threshold that triggers the pause + SELFHOST_REGRESSION_WINDOW_SECONDS=300 SELFHOST_REGRESSION_JOB_DEAD_THRESHOLD=10 \\ - ./scripts/selfhost-post-update-regression-gate.sh`} +./scripts/selfhost-post-update-regression-gate.sh`} /> If it trips, clear the pause once you've confirmed the regression is fixed: diff --git a/apps/loopover-ui/content/docs/self-hosting-quickstart.mdx b/apps/loopover-ui/content/docs/self-hosting-quickstart.mdx index 7cfc484a23..d9b68f4eec 100644 --- a/apps/loopover-ui/content/docs/self-hosting-quickstart.mdx +++ b/apps/loopover-ui/content/docs/self-hosting-quickstart.mdx @@ -1,6 +1,7 @@ --- title: Quickstart description: A minimal self-host boot path for maintainers -- start the service, verify readiness, and keep the first rollout safe. +eyebrow: Self-hosting --- ## 1. Copy the sample env @@ -240,19 +241,19 @@ Nothing below needs a flag to start; everything past the first row needs an expl loopover app + Redis SQLite database, dry-run-friendly, Orb telemetry (see Callout below) RECOMMENDED FOR PRODUCTION (opt-in) - --profile postgres shared/multi-instance database (pgvector-capable) - --profile pgbouncer connection pooling in front of Postgres - --profile caddy automatic HTTPS via Let's Encrypt - --profile litestream continuous SQLite backup to S3-compatible storage - --profile observability Prometheus + Alertmanager + Loki + Grafana +--profile postgres shared/multi-instance database (pgvector-capable) +--profile pgbouncer connection pooling in front of Postgres +--profile caddy automatic HTTPS via Let's Encrypt +--profile litestream continuous SQLite backup to S3-compatible storage +--profile observability Prometheus + Alertmanager + Loki + Grafana OPT-IN, NOT REQUIRED FOR A TRIAL INSTANCE - --profile qdrant dedicated RAG vector store (else sqlite-vec/pgvector) - --profile ollama local model for AI review or embeddings - --profile tailscale private network sidecar - --profile runners self-hosted GitHub Actions runner - --profile backup scheduled backup + backup-exporter jobs - AI_PROVIDER=... off by default; reviews are deterministic-only until set`} /> +--profile qdrant dedicated RAG vector store (else sqlite-vec/pgvector) +--profile ollama local model for AI review or embeddings +--profile tailscale private network sidecar +--profile runners self-hosted GitHub Actions runner +--profile backup scheduled backup + backup-exporter jobs +AI_PROVIDER=... off by default; reviews are deterministic-only until set`} /> Orb fleet-calibration telemetry (verdict, outcome, cycle time — never repo names, code, or logins) diff --git a/apps/loopover-ui/content/docs/self-hosting-rag.mdx b/apps/loopover-ui/content/docs/self-hosting-rag.mdx index 800dfb131b..b80ab989f2 100644 --- a/apps/loopover-ui/content/docs/self-hosting-rag.mdx +++ b/apps/loopover-ui/content/docs/self-hosting-rag.mdx @@ -1,6 +1,7 @@ --- title: RAG indexing description: RAG adds relevant existing code and docs to the AI reviewer prompt. It is additive and fail-safe. +eyebrow: Self-hosting --- ## Prerequisites diff --git a/apps/loopover-ui/content/docs/self-hosting-rees-analyzers.mdx b/apps/loopover-ui/content/docs/self-hosting-rees-analyzers.mdx index fc08757e92..e6e9bdbda3 100644 --- a/apps/loopover-ui/content/docs/self-hosting-rees-analyzers.mdx +++ b/apps/loopover-ui/content/docs/self-hosting-rees-analyzers.mdx @@ -1,6 +1,7 @@ --- title: REES analyzer reference description: Every analyzer name you can put in REES_ANALYZERS, what it inspects, what it reports, and whether it needs network or GitHub token access. +eyebrow: Self-hosting --- import { REES_ANALYZERS, REES_ANALYZER_NAMES, REES_PROFILES } from "@/lib/rees-analyzers"; @@ -17,9 +18,11 @@ local and low-cost registry checks. REES_ANALYZERS=all # A subset runs only the named analyzers. + REES_ANALYZERS=secret,actionPin,redos # Invalid names are ignored; if none are valid, REES runs no analyzers. + REES_ANALYZERS=unknownName`} /> diff --git a/apps/loopover-ui/content/docs/self-hosting-rees.mdx b/apps/loopover-ui/content/docs/self-hosting-rees.mdx index 9df7cdad0e..52a89b2277 100644 --- a/apps/loopover-ui/content/docs/self-hosting-rees.mdx +++ b/apps/loopover-ui/content/docs/self-hosting-rees.mdx @@ -1,6 +1,7 @@ --- title: REES enrichment description: REES runs external or heavier analyzers and returns a public-safe brief that the AI reviewer can use. +eyebrow: Self-hosting --- import { REES_ANALYZER_NAMES } from "@/lib/rees-analyzers"; @@ -137,6 +138,7 @@ forwarding stays off unless you explicitly set `REES_FORWARD_GITHUB_TOKEN=true`. LOOPOVER_REVIEW_ENRICHMENT=false # Keep REES on and explicitly allow token-aware analyzers: + REES_FORWARD_GITHUB_TOKEN=true`} /> diff --git a/apps/loopover-ui/content/docs/self-hosting-release-checklist.mdx b/apps/loopover-ui/content/docs/self-hosting-release-checklist.mdx index 91e105e25b..e3cae445c6 100644 --- a/apps/loopover-ui/content/docs/self-hosting-release-checklist.mdx +++ b/apps/loopover-ui/content/docs/self-hosting-release-checklist.mdx @@ -1,6 +1,7 @@ --- title: First release checklist description: Everything to confirm before cutting orb-v0.1.0 — the first stable (non-prerelease) self-host image — after two beta cuts already validated the pipeline end to end. Run the smoke matrix against a candidate image before tagging any orb-vX.Y.Z or -rc/-beta prerelease; CI only exercises the plain SQLite + Redis + direct-App default. +eyebrow: Self-hosting --- ## Versioning and release trigger @@ -281,7 +282,9 @@ SELFHOST_SMOKE_FORBID_EVENTS="selfhost_orb_relay_register_failed" \\ ./scripts/smoke-selfhost.sh loopover:rc-candidate # Pull mode -- no PUBLIC_API_ORIGIN needed; the container polls the broker outbound instead of + # exposing an inbound endpoint. The right fit for NAT/tailnet operators with no public ingress. + SELFHOST_SMOKE_EXTRA_ENV="ORB_ENROLLMENT_SECRET=\${TEST_ENROLLMENT_SECRET} ORB_RELAY_MODE=pull" \\ SELFHOST_SMOKE_EXPECT_EVENTS="selfhost_orb_relay_register" \\ @@ -314,6 +317,7 @@ SELFHOST_SMOKE_FORBID_EVENTS="selfhost_ai_cli_missing" \\ ./scripts/smoke-selfhost.sh loopover:rc-candidate # Codex only (requires the fail-closed opt-in) + SELFHOST_SMOKE_EXTRA_ENV="AI_PROVIDER=codex LOOPOVER_ENABLE_UNSAFE_CODEX_REVIEWER=1" \\ SELFHOST_SMOKE_EXPECT_EVENTS="selfhost_ai_provider" \\ @@ -321,6 +325,7 @@ SELFHOST_SMOKE_FORBID_EVENTS="selfhost_ai_cli_missing" \\ ./scripts/smoke-selfhost.sh loopover:rc-candidate # Codex primary, Claude Code fallback + SELFHOST_SMOKE_EXTRA_ENV="AI_PROVIDER=codex,claude-code CODEX_AI_EFFORT=medium CLAUDE_AI_EFFORT=medium @@ -367,6 +372,7 @@ Redis is always-on in every scenario above (the base script already boots it) ./scripts/smoke-selfhost.sh loopover:rc-candidate # With Qdrant RAG: + docker network create gt-rag-smoke docker run -d --name gt-qdrant --network gt-rag-smoke qdrant/qdrant:v1.18.2 SELFHOST_SMOKE_NETWORK=gt-rag-smoke \\ diff --git a/apps/loopover-ui/content/docs/self-hosting-releases.mdx b/apps/loopover-ui/content/docs/self-hosting-releases.mdx index 2b30e49426..c4e6e22765 100644 --- a/apps/loopover-ui/content/docs/self-hosting-releases.mdx +++ b/apps/loopover-ui/content/docs/self-hosting-releases.mdx @@ -1,6 +1,7 @@ --- title: Releases and images description: How to consume official self-host images, pin versions, build custom images, and keep source maps aligned. +eyebrow: Self-hosting --- ## Image tags @@ -70,6 +71,7 @@ A tag like `orb-v0.1.0-rc.1` or `orb-v0.1.0-beta.1` runs the identical build/pro curl http://localhost:8787/ready # Building from the current checkout instead of pulling + ./scripts/deploy-selfhost-prebuilt.sh`} /> diff --git a/apps/loopover-ui/content/docs/self-hosting-security.mdx b/apps/loopover-ui/content/docs/self-hosting-security.mdx index 2cd300a92a..66828537f6 100644 --- a/apps/loopover-ui/content/docs/self-hosting-security.mdx +++ b/apps/loopover-ui/content/docs/self-hosting-security.mdx @@ -1,6 +1,7 @@ --- title: Security description: The self-host stack holds maintainer credentials and policy. Keep those boundaries explicit. +eyebrow: Self-hosting --- ## Secret handling @@ -20,7 +21,7 @@ description: The self-host stack holds maintainer credentials and policy. Keep t { title: "Rotate deliberately", description: - "Cadence, per-secret procedure, and the categories that can't be rotated safely yet -- see \"Secret rotation policy\" below.", + 'Cadence, per-secret procedure, and the categories that can\'t be rotated safely yet -- see "Secret rotation policy" below.', }, ]} /> @@ -71,20 +72,23 @@ rm secrets/loopover_api_token.txt && ./scripts/selfhost-init-secrets.sh # gene docker compose up -d --no-deps loopover # Freely rotatable secret -- hosted Worker path + wrangler secret put LOOPOVER_API_TOKEN + # then redeploy`} + /> `TOKEN_ENCRYPTION_SECRET` (maintainer BYOK AI-provider keys at rest) and `DRAFT_TOKEN_ENCRYPTION_SECRET` (in-flight contributor draft OAuth tokens) are AES-256-GCM keys - every already-encrypted row was written against. The decrypt path always derives the AES key - from whatever value is currently set, with no fallback to a prior value -- there is no - re-encryption tooling in this codebase today. Rotating `TOKEN_ENCRYPTION_SECRET` means every - maintainer who saved a BYOK provider key has to re-enter it; rotating - `DRAFT_TOKEN_ENCRYPTION_SECRET` fails any draft submission mid-flight. Only rotate these two on - suspected compromise, and treat the resulting key/token loss as an accepted, unavoidable cost - until a re-encrypt-in-place migration exists (tracked as separate follow-up work). + every already-encrypted row was written against. The decrypt path always derives the AES key from + whatever value is currently set, with no fallback to a prior value -- there is no re-encryption + tooling in this codebase today. Rotating `TOKEN_ENCRYPTION_SECRET` means every maintainer who + saved a BYOK provider key has to re-enter it; rotating `DRAFT_TOKEN_ENCRYPTION_SECRET` fails any + draft submission mid-flight. Only rotate these two on suspected compromise, and treat the + resulting key/token loss as an accepted, unavoidable cost until a re-encrypt-in-place migration + exists (tracked as separate follow-up work). `ORB_ENROLLMENT_SECRET` has the same gap from the other direction: `orb_enrollments.revoked_at` is @@ -219,20 +223,21 @@ The shipped `caddy/Caddyfile` reverse-proxies to `loopover:8787` on the compose header_up X-Real-IP {remote_host} } - encode zstd gzip + encode zstd gzip - header { - Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" - X-Content-Type-Options "nosniff" - X-Frame-Options "DENY" - Referrer-Policy "strict-origin-when-cross-origin" - -Server - } + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + X-Content-Type-Options "nosniff" + X-Frame-Options "DENY" + Referrer-Policy "strict-origin-when-cross-origin" + -Server + } + + log { + output stderr + format json + } - log { - output stderr - format json - } }`} /> diff --git a/apps/loopover-ui/content/docs/self-hosting-troubleshooting.mdx b/apps/loopover-ui/content/docs/self-hosting-troubleshooting.mdx index 852c9f369a..48c686085e 100644 --- a/apps/loopover-ui/content/docs/self-hosting-troubleshooting.mdx +++ b/apps/loopover-ui/content/docs/self-hosting-troubleshooting.mdx @@ -1,6 +1,7 @@ --- title: Troubleshooting description: Start with readiness and logs, then isolate webhook, queue, AI, REES, RAG, or write-suppression problems. +eyebrow: Self-hosting --- ## First checks @@ -87,9 +88,11 @@ A short burst of deferrals is expected and self-resolving: the queue is delibera sum by (key_scope, job_type) (rate(loopover_jobs_rate_limit_admission_deferred_total[10m])) # Is one key_scope (e.g. a single installation token) the bottleneck? + topk(5, sum by (key_scope) (rate(loopover_jobs_rate_limit_budget_deferred_total[10m]))) # Real rate-limit responses from GitHub itself (not just internal deferrals) + sum(rate(loopover_github_rest_rate_limit_responses_total[10m]))`} /> @@ -107,6 +110,7 @@ sum by (class) (rate(loopover_github_response_cache_total{result="hit"}[15m])) sum by (class) (rate(loopover_github_response_cache_total[15m])) # GraphQL hit rate — same shape, separate metric + sum by (class) (rate(loopover_github_graphql_cache_total{result="hit"}[15m])) / sum by (class) (rate(loopover_github_graphql_cache_total[15m]))`} @@ -126,6 +130,7 @@ sum by (class) (rate(loopover_github_graphql_cache_total[15m]))`} docker compose --profile qdrant ps qdrant # Only after confirming a dimension mismatch is the actual cause: + curl -X DELETE "$QDRANT_URL/collections/loopover"`} /> @@ -160,7 +165,9 @@ The metrics path is the app's own `/metrics` (scraped directly by Prometheus) pl docker compose logs --tail=80 prometheus otel-collector grafana # Send one synthetic OTLP metric through the collector, read it back from its Prometheus exporter, + # and sanity-check the app's own /metrics HELP/TYPE shape. + npm run test:smoke:observability:metrics`} /> @@ -179,6 +186,7 @@ The trace path is app or smoke process → OTEL collector → Tempo → Grafana. docker compose logs --tail=80 tempo otel-collector grafana # Send one synthetic span through the collector and read it back from Tempo. + npm run test:smoke:observability`} /> diff --git a/apps/loopover-ui/content/docs/self-hosting-unified-ams-orb.mdx b/apps/loopover-ui/content/docs/self-hosting-unified-ams-orb.mdx index 868175ba2a..f34c24811e 100644 --- a/apps/loopover-ui/content/docs/self-hosting-unified-ams-orb.mdx +++ b/apps/loopover-ui/content/docs/self-hosting-unified-ams-orb.mdx @@ -1,6 +1,7 @@ --- title: Running ORB and AMS together description: Run ORB (self-hosted PR review) and AMS (the autonomous miner) together on one host -- shared state, aligned .env, and working AMS Grafana panels in fleet mode. +eyebrow: Self-hosting --- ORB (self-hosted PR review) and AMS (the autonomous miner) each run on their own, but there is one @@ -67,7 +68,7 @@ two never line up on their own and the Grafana AMS datasources stay **silently e ## 3. Bridge the state so AMS panels populate The AMS package ships an opt-in override that relocates the fleet miner's `/data/miner` state onto -the *same* host directory the exporter reads — using the same `LOOPOVER_MINER_CONFIG_DIR` +the _same_ host directory the exporter reads — using the same `LOOPOVER_MINER_CONFIG_DIR` variable and default, so there is no `docker volume inspect` archaeology. Copy the example (it is gitignored) and run all three compose files together with both profiles: @@ -77,14 +78,14 @@ gitignored) and run all three compose files together with both profiles: packages/loopover-miner/docker-compose.miner.override.yml # edit the host path only for a non-default location docker compose \\ - -f docker-compose.yml \\ - -f packages/loopover-miner/docker-compose.miner.yml \\ - -f packages/loopover-miner/docker-compose.miner.override.yml \\ - --profile observability --profile ams-observability up -d`} +-f docker-compose.yml \\ +-f packages/loopover-miner/docker-compose.miner.yml \\ +-f packages/loopover-miner/docker-compose.miner.override.yml \\ +--profile observability --profile ams-observability up -d`} /> The override bind-mounts `/data/miner` to `${LOOPOVER_MINER_CONFIG_DIR:-~/.config/loopover-miner}` -— Compose merges by container path, so this *replaces* the base file's `miner-data` named-volume +— Compose merges by container path, so this _replaces_ the base file's `miner-data` named-volume mount for the same target rather than adding a second one. The exporter's own bind is the same source: `${LOOPOVER_MINER_CONFIG_DIR:-~/.config/loopover-miner}:/ams-ledgers:ro`. Both sides now read one location. diff --git a/apps/loopover-ui/content/docs/troubleshooting.mdx b/apps/loopover-ui/content/docs/troubleshooting.mdx index 4966690173..5849ba9c48 100644 --- a/apps/loopover-ui/content/docs/troubleshooting.mdx +++ b/apps/loopover-ui/content/docs/troubleshooting.mdx @@ -1,6 +1,7 @@ --- title: Troubleshooting description: The MCP ships with diagnostics. Start with doctor, then drill in. +eyebrow: Operating --- ## Health checks @@ -28,6 +29,7 @@ work is happening; traces answer _where time went_ inside a review job. docker compose --profile observability up -d # Export app queue-job and AI-provider spans to Tempo. + OTEL_TRACES_EXPORTER=otlp OTEL_TRACES_SAMPLER=parentbased_traceidratio OTEL_TRACES_SAMPLER_ARG=0.05 diff --git a/apps/loopover-ui/content/docs/tuning.mdx b/apps/loopover-ui/content/docs/tuning.mdx index 6bd9f6b058..e1db382204 100644 --- a/apps/loopover-ui/content/docs/tuning.mdx +++ b/apps/loopover-ui/content/docs/tuning.mdx @@ -1,6 +1,7 @@ --- title: Tuning your reviews description: How to configure LoopOver CI and LoopOver review -- gate modes, score thresholds, guardrails, and feature flags -- through .loopover.yml and your repo settings. +eyebrow: Operating --- ## How configuration fits together diff --git a/apps/loopover-ui/content/docs/upstream-drift.mdx b/apps/loopover-ui/content/docs/upstream-drift.mdx index fa78cd78b4..d49504796a 100644 --- a/apps/loopover-ui/content/docs/upstream-drift.mdx +++ b/apps/loopover-ui/content/docs/upstream-drift.mdx @@ -1,6 +1,7 @@ --- title: Upstream drift description: Gittensor moves. LoopOver tracks every meaningful change to scoring, registry, and issue-discovery so your decisions stay grounded. +eyebrow: Core concepts --- ## How drift works diff --git a/apps/loopover-ui/source.config.ts b/apps/loopover-ui/source.config.ts index 128a5c1754..afb1bc5478 100644 --- a/apps/loopover-ui/source.config.ts +++ b/apps/loopover-ui/source.config.ts @@ -1,5 +1,18 @@ import { defineDocs } from "fumadocs-mdx/config"; +import { pageSchema } from "fumadocs-core/source/schema"; +import { z } from "zod"; +// eyebrow (#8151): the one genuinely per-page datum the old per-route docs..tsx files hard-coded +// as a JSX prop (e.g. eyebrow="Configuration") instead of deriving it from anything -- moved into +// frontmatter so the single dynamic docs.$slug.tsx route can read it the same way it reads +// title/description, with no per-page route file left to hold it. Required (not optional): every real +// content/docs/*.mdx page has always rendered one, and a page silently missing its eyebrow badge would +// be a content regression fumadocs-mdx's own build should catch, not something to render as blank. export const docs = defineDocs({ dir: "content/docs", + docs: { + schema: pageSchema.extend({ + eyebrow: z.string(), + }), + }, }); diff --git a/apps/loopover-ui/src/components/site/ams-observability-callout.tsx b/apps/loopover-ui/src/components/site/ams-observability-callout.tsx index 00df10396b..67c747cd77 100644 --- a/apps/loopover-ui/src/components/site/ams-observability-callout.tsx +++ b/apps/loopover-ui/src/components/site/ams-observability-callout.tsx @@ -12,16 +12,18 @@ import { Callout } from "@/components/site/primitives"; // its own in-app /docs/ams-observability route (#6024, ported from the source packages/loopover-miner/docs/observability.md // this constant used to point at directly) -- an in-app Link keeps the reader on the docs site instead of bouncing // to GitHub. -export const AMS_OBSERVABILITY_DOC_URL = "/docs/ams-observability"; +export const AMS_OBSERVABILITY_DOC_SLUG = "ams-observability"; /** A `note` callout pointing a dual-role ORB+AMS operator at the "Observing your miner" observability guide. */ export function AmsObservabilityCallout() { return ( If you also run AMS (the loopover-miner) on this host, see{" "} - Observing your miner to point Grafana at the - redacted AMS ledger datasources and load its Grafana dashboard — separate from the ORB - review-service observability above. + + Observing your miner + {" "} + to point Grafana at the redacted AMS ledger datasources and load its Grafana dashboard — + separate from the ORB review-service observability above. ); } diff --git a/apps/loopover-ui/src/components/site/api-status-banner.tsx b/apps/loopover-ui/src/components/site/api-status-banner.tsx index d7f1ef8a65..9088f31783 100644 --- a/apps/loopover-ui/src/components/site/api-status-banner.tsx +++ b/apps/loopover-ui/src/components/site/api-status-banner.tsx @@ -106,7 +106,8 @@ export function ApiStatusBanner() { )} diff --git a/apps/loopover-ui/src/components/site/app-panels/miner-panel.tsx b/apps/loopover-ui/src/components/site/app-panels/miner-panel.tsx index b51f4a30d1..f0f044451a 100644 --- a/apps/loopover-ui/src/components/site/app-panels/miner-panel.tsx +++ b/apps/loopover-ui/src/components/site/app-panels/miner-panel.tsx @@ -325,7 +325,8 @@ export function MinerPanel() {

Each blocker links to how to clear it.{" "} See scoreability docs → diff --git a/apps/loopover-ui/src/components/site/docs-nav.tsx b/apps/loopover-ui/src/components/site/docs-nav.tsx index bab4b2a82a..d5a5f415dc 100644 --- a/apps/loopover-ui/src/components/site/docs-nav.tsx +++ b/apps/loopover-ui/src/components/site/docs-nav.tsx @@ -1,4 +1,5 @@ import { Link, useRouterState } from "@tanstack/react-router"; +import type { ReactNode } from "react"; import { cn } from "@/lib/utils"; @@ -116,6 +117,34 @@ function groupItems(group: DocsGroup): DocsItem[] { return "items" in group ? group.items : group.subgroups.flatMap((sub) => sub.items); } +// Every DocsItem#to is either the bare docs index ("/docs") or "/docs/" resolved through the +// single dynamic docs.$slug.tsx route (#8151) -- items stay full paths (not bare slugs) so pathname +// comparisons/key/prev-next lookups below don't need a second derived field, and this one helper +// splits into whichever shape each case needs (the index route takes no params; the dynamic +// route requires them). +function DocsNavLink({ + to, + className, + children, +}: { + to: string; + className: string; + children: ReactNode; +}) { + if (to === "/docs") { + return ( + + {children} + + ); + } + return ( + + {children} + + ); +} + function DocsItemList({ items, pathname }: { items: DocsItem[]; pathname: string }) { return (

    @@ -123,8 +152,8 @@ function DocsItemList({ items, pathname }: { items: DocsItem[]; pathname: string const active = pathname === it.to; return (
  • - )} {it.label} - +
  • ); })} @@ -183,8 +212,8 @@ export function DocsPrevNext() { return (
    {prev ? ( - @@ -193,13 +222,13 @@ export function DocsPrevNext() { {prev.label} - + ) : ( )} {next && ( - @@ -208,7 +237,7 @@ export function DocsPrevNext() { {next.label} - + )}
    ); diff --git a/apps/loopover-ui/src/components/site/site-footer.tsx b/apps/loopover-ui/src/components/site/site-footer.tsx index 0b6f8bf8bd..4247976a75 100644 --- a/apps/loopover-ui/src/components/site/site-footer.tsx +++ b/apps/loopover-ui/src/components/site/site-footer.tsx @@ -4,6 +4,10 @@ import { Github } from "lucide-react"; import { LoopOverMark } from "./mark"; import { HealthDot } from "./health-dot"; +// Docs entries carry a bare `docSlug` (resolved through the single dynamic /docs/$slug route, #8151) +// instead of a `to` -- the two are rendered by different branches below since TanStack +// Router's typed `to`/`params` pairing can't be satisfied by one polymorphic call across a static +// route and a dynamic-segment route in the same array. const cols = [ { title: "Product", @@ -17,19 +21,19 @@ const cols = [ { title: "Docs", links: [ - { to: "/docs/quickstart", label: "Quickstart" }, - { to: "/docs/mcp-clients", label: "MCP clients" }, - { to: "/docs/github-app", label: "GitHub App" }, - { to: "/docs/maintainer-self-hosting", label: "Self-host reviews" }, - { to: "/docs/privacy-security", label: "Privacy & security" }, + { docSlug: "quickstart", label: "Quickstart" }, + { docSlug: "mcp-clients", label: "MCP clients" }, + { docSlug: "github-app", label: "GitHub App" }, + { docSlug: "maintainer-self-hosting", label: "Self-host reviews" }, + { docSlug: "privacy-security", label: "Privacy & security" }, ], }, { title: "Reference", links: [ { to: "/api", label: "API reference" }, - { to: "/docs/scoreability", label: "Scoreability" }, - { to: "/docs/upstream-drift", label: "Upstream drift" }, + { docSlug: "scoreability", label: "Scoreability" }, + { docSlug: "upstream-drift", label: "Upstream drift" }, { to: "/changelog", label: "Changelog" }, ], }, @@ -37,7 +41,7 @@ const cols = [ title: "Project", links: [ { to: "/roadmap", label: "Roadmap" }, - { to: "/docs/troubleshooting", label: "Troubleshooting" }, + { docSlug: "troubleshooting", label: "Troubleshooting" }, { to: "/docs", label: "All docs" }, ], }, @@ -80,13 +84,23 @@ export function SiteFooter() {
      {c.links.map((l) => ( -
    • - - {l.label} - +
    • + {"docSlug" in l ? ( + + {l.label} + + ) : ( + + {l.label} + + )}
    • ))}
    diff --git a/apps/loopover-ui/src/components/site/site-header.tsx b/apps/loopover-ui/src/components/site/site-header.tsx index 61a50ef261..22d8ba8dc4 100644 --- a/apps/loopover-ui/src/components/site/site-header.tsx +++ b/apps/loopover-ui/src/components/site/site-header.tsx @@ -16,14 +16,15 @@ const nav = [ { to: "/roadmap", label: "Roadmap" }, ] as const; +// Resolved through the single dynamic /docs/$slug route (#8151). const docsMenu = [ - { to: "/docs/quickstart", label: "Quickstart", hint: "Install MCP + first run" }, - { to: "/docs/mcp-clients", label: "MCP clients", hint: "Codex · Claude · Cursor" }, - { to: "/docs/github-app", label: "GitHub App", hint: "Quiet maintainer setup" }, - { to: "/docs/maintainer-self-hosting", label: "Self-host", hint: "Run the review service" }, - { to: "/docs/scoreability", label: "Scoreability", hint: "How projections work" }, - { to: "/docs/upstream-drift", label: "Upstream drift", hint: "Snapshot freshness" }, - { to: "/docs/troubleshooting", label: "Troubleshooting", hint: "Common errors" }, + { slug: "quickstart", label: "Quickstart", hint: "Install MCP + first run" }, + { slug: "mcp-clients", label: "MCP clients", hint: "Codex · Claude · Cursor" }, + { slug: "github-app", label: "GitHub App", hint: "Quiet maintainer setup" }, + { slug: "maintainer-self-hosting", label: "Self-host", hint: "Run the review service" }, + { slug: "scoreability", label: "Scoreability", hint: "How projections work" }, + { slug: "upstream-drift", label: "Upstream drift", hint: "Snapshot freshness" }, + { slug: "troubleshooting", label: "Troubleshooting", hint: "Common errors" }, ] as const; export function SiteHeader() { @@ -115,8 +116,9 @@ export function SiteHeader() {
    {docsMenu.map((item) => ( setDocsOpen(false)} activeProps={{ className: "bg-muted/60 text-foreground" }} className="group relative flex flex-col gap-0.5 rounded-token px-3 py-2 text-token-sm text-foreground/85 transition-all duration-150 motion-reduce:transition-none hover:bg-muted hover:text-foreground focus-ring" diff --git a/apps/loopover-ui/src/lib/docs-source-server-isolation.test.ts b/apps/loopover-ui/src/lib/docs-source-server-isolation.test.ts index 4cbbc746a8..b4ae26c07b 100644 --- a/apps/loopover-ui/src/lib/docs-source-server-isolation.test.ts +++ b/apps/loopover-ui/src/lib/docs-source-server-isolation.test.ts @@ -11,7 +11,7 @@ import { describe, expect, it } from "vitest"; // fumadocs-mdx lookup behind a createServerFn (docs-source.functions.ts) so the client always // fetches the result over the wire instead of re-executing the server-only module itself. // -// docs-source.server.ts isn't imported here: like docs.miner-coding-agent.test.tsx and +// docs-source.server.ts isn't imported here: like miner-coding-agent-docs-data.test.ts and // docs.ams-observability-callout.test.tsx, actually evaluating fumadocs-mdx's generated // `collections/server` module needs the fumadocs-mdx Vite plugin (registered for the app build, // not this standalone vitest.config.ts), so this is a source-level drift guard instead. @@ -28,8 +28,11 @@ describe("docs.*.tsx routes never import docs-source(.server) directly", () => { ); const inScope = docsRouteFiles.filter((name) => !outOfScope.has(name)); - it("found the expected set of in-scope docs route files", () => { - expect(inScope.length).toBe(49); + // #8151: the 49 per-page docs..tsx files collapsed into one dynamic docs.$slug.tsx + // route, so this is now a shape assertion (exactly the dynamic route, nothing else) rather + // than a per-page count that had to be bumped on every new docs page. + it("found exactly the single dynamic docs route, nothing else, in scope", () => { + expect(inScope).toEqual(["docs.$slug.tsx"]); }); it.each(inScope)( diff --git a/apps/loopover-ui/src/lib/docs-source.functions.ts b/apps/loopover-ui/src/lib/docs-source.functions.ts index abdfe86d8f..ac32ed0723 100644 --- a/apps/loopover-ui/src/lib/docs-source.functions.ts +++ b/apps/loopover-ui/src/lib/docs-source.functions.ts @@ -9,11 +9,12 @@ import { z } from "zod"; // in the browser. // // The import of docs-source.server is dynamic (inside the handler) rather than a static -// top-level import: this file itself is a static import in every docs.*.tsx route, and a +// top-level import: this file itself is a static import in docs.$slug.tsx (and +// docs.miner-coding-agent.tsx before #8151 collapsed it into that one dynamic route), and a // static top-level import here would drag docs-source.server.ts's eager content/docs/*.mdx // glob into any test that merely imports a docs.*.tsx route module without invoking its -// loader (e.g. docs.miner-coding-agent.test.tsx importing named exports for assertions) -- -// vitest.config.ts doesn't register the fumadocs-mdx plugin needed to parse those files. +// loader -- vitest.config.ts doesn't register the fumadocs-mdx plugin needed to parse those +// files. export const getDocPage = createServerFn({ method: "POST" }) .inputValidator(z.object({ slugs: z.array(z.string()) })) .handler(async ({ data }) => { diff --git a/apps/loopover-ui/src/lib/docs-source.server.ts b/apps/loopover-ui/src/lib/docs-source.server.ts index feb4bbd86b..6068991f8c 100644 --- a/apps/loopover-ui/src/lib/docs-source.server.ts +++ b/apps/loopover-ui/src/lib/docs-source.server.ts @@ -17,5 +17,10 @@ export const docsSource = loader({ export function getDocPageMeta(slugs: string[]) { const page = docsSource.getPage(slugs); if (!page) return null; - return { path: page.path, title: page.data.title, description: page.data.description }; + return { + path: page.path, + title: page.data.title, + description: page.data.description, + eyebrow: page.data.eyebrow, + }; } diff --git a/apps/loopover-ui/src/routes/docs.miner-coding-agent.test.tsx b/apps/loopover-ui/src/lib/miner-coding-agent-docs-data.test.ts similarity index 76% rename from apps/loopover-ui/src/routes/docs.miner-coding-agent.test.tsx rename to apps/loopover-ui/src/lib/miner-coding-agent-docs-data.test.ts index 8466d4ea29..8e273492f6 100644 --- a/apps/loopover-ui/src/routes/docs.miner-coding-agent.test.tsx +++ b/apps/loopover-ui/src/lib/miner-coding-agent-docs-data.test.ts @@ -5,16 +5,17 @@ import { CODING_AGENT_DRIVER_NAMES } from "../../../../packages/loopover-engine/ import { MINER_CODING_AGENT_ENV_ROWS, MINER_CODING_AGENT_PROVIDER_ITEMS, - MinerCodingAgentDriverDocs, -} from "./docs.miner-coding-agent"; +} from "./miner-coding-agent-docs-data"; // Renders from content/docs/miner-coding-agent.mdx via the fumadocs client-loader (see // docs-source.server.ts's comment) -- a synchronous component render can't exercise that path -// without a full router context, so this is now a content drift-guard on the .mdx source, -// matching the pattern in docs-selfhost-activation-paths.test.ts. +// without a full router context, so this is a content drift-guard on the .mdx source, matching +// the pattern in docs-selfhost-activation-paths.test.ts. Moved out of src/routes/ (#8151) once +// docs.miner-coding-agent.tsx (its former sibling) was deleted along with the other 48 +// per-page docs..tsx route files -- this file tests content data, not routing. const MDX_PATH = "content/docs/miner-coding-agent.mdx"; -describe("miner coding-agent docs page", () => { +describe("miner coding-agent docs content", () => { it("documents the expected sections", () => { const source = readFileSync(MDX_PATH, "utf8"); expect(source).toContain("title: Miner coding-agent driver"); @@ -38,8 +39,4 @@ describe("miner coding-agent docs page", () => { "MINER_CODING_AGENT_TIMEOUT_MS", ]); }); - - it("exports the route component used by the route definition", () => { - expect(typeof MinerCodingAgentDriverDocs).toBe("function"); - }); }); diff --git a/apps/loopover-ui/src/routes/docs.miner-coding-agent.tsx b/apps/loopover-ui/src/lib/miner-coding-agent-docs-data.ts similarity index 52% rename from apps/loopover-ui/src/routes/docs.miner-coding-agent.tsx rename to apps/loopover-ui/src/lib/miner-coding-agent-docs-data.ts index b67d8dd195..ca7836f5a7 100644 --- a/apps/loopover-ui/src/routes/docs.miner-coding-agent.tsx +++ b/apps/loopover-ui/src/lib/miner-coding-agent-docs-data.ts @@ -1,22 +1,10 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/miner-coding-agent.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -// -// The three arrays below no longer feed this page's own JSX -- their prose was -// hand-transcribed into the migrated .mdx above -- but they stay exported because +// content/docs/miner-coding-agent.mdx's prose was hand-transcribed from these arrays during the +// fumadocs migration (#6037) -- they no longer feed any page's own JSX (the dynamic docs.$slug.tsx +// route just renders the compiled MDX), but stay exported because // docs.miner-coding-agent.test.tsx imports them directly (asserting -// MINER_CODING_AGENT_PROVIDER_ITEMS against packages/loopover-engine's -// CODING_AGENT_DRIVER_NAMES, and MINER_CODING_AGENT_ENV_ROWS's env var names). Keep -// both this data and the corresponding MDX content in sync if either changes. +// MINER_CODING_AGENT_PROVIDER_ITEMS against packages/loopover-engine's CODING_AGENT_DRIVER_NAMES, +// and MINER_CODING_AGENT_ENV_ROWS's env var names). Keep both this data and the corresponding MDX +// content in sync if either changes. export const MINER_CODING_AGENT_PROVIDER_ITEMS: Array<{ title: string; description: string }> = [ { title: "noop", @@ -98,42 +86,3 @@ export const MINER_CODING_AGENT_TRUST_ROWS: Array<{ title: string; description: "The Codex home or auth path is not isolated from operator-owned storage. Remove the unsafe override.", }, ]; - -export const Route = createFileRoute("/docs/miner-coding-agent")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["miner-coding-agent"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Miner coding-agent driver — LoopOver docs" }, - { - name: "description", - content: - "Enable Claude Code or Codex as the miner's coding-agent driver, and document the provider, model, timeout, and credential troubleshooting paths.", - }, - { property: "og:title", content: "Miner coding-agent driver — LoopOver docs" }, - { - property: "og:description", - content: - "Enable Claude Code or Codex as the miner's coding-agent driver, and document the provider, model, timeout, and credential troubleshooting paths.", - }, - { property: "og:url", content: "/docs/miner-coding-agent" }, - ], - links: [{ rel: "canonical", href: "/docs/miner-coding-agent" }], - }), - component: MinerCodingAgentDriverDocs, -}); - -export function MinerCodingAgentDriverDocs() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routeTree.gen.ts b/apps/loopover-ui/src/routeTree.gen.ts index ddf4b1534e..77d7a40ec0 100644 --- a/apps/loopover-ui/src/routeTree.gen.ts +++ b/apps/loopover-ui/src/routeTree.gen.ts @@ -26,56 +26,8 @@ import { Route as DocsIndexRouteImport } from './routes/docs.index' import { Route as AppIndexRouteImport } from './routes/app.index' import { Route as ApiIndexRouteImport } from './routes/api.index' import { Route as InstallPermissionsRouteImport } from './routes/install.permissions' -import { Route as DocsUpstreamDriftRouteImport } from './routes/docs.upstream-drift' -import { Route as DocsTuningRouteImport } from './routes/docs.tuning' -import { Route as DocsTroubleshootingRouteImport } from './routes/docs.troubleshooting' -import { Route as DocsSelfHostingUnifiedAmsOrbRouteImport } from './routes/docs.self-hosting-unified-ams-orb' -import { Route as DocsSelfHostingTroubleshootingRouteImport } from './routes/docs.self-hosting-troubleshooting' -import { Route as DocsSelfHostingSecurityRouteImport } from './routes/docs.self-hosting-security' -import { Route as DocsSelfHostingReleasesRouteImport } from './routes/docs.self-hosting-releases' -import { Route as DocsSelfHostingReleaseChecklistRouteImport } from './routes/docs.self-hosting-release-checklist' -import { Route as DocsSelfHostingReesAnalyzersRouteImport } from './routes/docs.self-hosting-rees-analyzers' -import { Route as DocsSelfHostingReesRouteImport } from './routes/docs.self-hosting-rees' -import { Route as DocsSelfHostingRagRouteImport } from './routes/docs.self-hosting-rag' -import { Route as DocsSelfHostingQuickstartRouteImport } from './routes/docs.self-hosting-quickstart' -import { Route as DocsSelfHostingOperationsRouteImport } from './routes/docs.self-hosting-operations' -import { Route as DocsSelfHostingGithubAppRouteImport } from './routes/docs.self-hosting-github-app' -import { Route as DocsSelfHostingDocsAuditRouteImport } from './routes/docs.self-hosting-docs-audit' -import { Route as DocsSelfHostingConfigurationRouteImport } from './routes/docs.self-hosting-configuration' -import { Route as DocsSelfHostingBackupScalingRouteImport } from './routes/docs.self-hosting-backup-scaling' -import { Route as DocsSelfHostingAiProvidersRouteImport } from './routes/docs.self-hosting-ai-providers' -import { Route as DocsScoreabilityRouteImport } from './routes/docs.scoreability' -import { Route as DocsQuickstartRouteImport } from './routes/docs.quickstart' -import { Route as DocsPrivacySecurityRouteImport } from './routes/docs.privacy-security' -import { Route as DocsOwnerChecklistRouteImport } from './routes/docs.owner-checklist' -import { Route as DocsMinerWorkflowRouteImport } from './routes/docs.miner-workflow' -import { Route as DocsMinerQuickstartRouteImport } from './routes/docs.miner-quickstart' -import { Route as DocsMinerCodingAgentRouteImport } from './routes/docs.miner-coding-agent' -import { Route as DocsMcpClientsRouteImport } from './routes/docs.mcp-clients' -import { Route as DocsMaintainerWorkflowRouteImport } from './routes/docs.maintainer-workflow' -import { Route as DocsMaintainerSelfHostingRouteImport } from './routes/docs.maintainer-self-hosting' -import { Route as DocsMaintainerInstallTrustRouteImport } from './routes/docs.maintainer-install-trust' -import { Route as DocsLoopoverCommandsRouteImport } from './routes/docs.loopover-commands' -import { Route as DocsHowReviewsWorkRouteImport } from './routes/docs.how-reviews-work' -import { Route as DocsGithubAppRouteImport } from './routes/docs.github-app' import { Route as DocsFumadocsSpikeApiReferenceRouteImport } from './routes/docs.fumadocs-spike-api-reference' -import { Route as DocsFederatedFleetIntelligenceRouteImport } from './routes/docs.federated-fleet-intelligence' -import { Route as DocsCapacityRouteImport } from './routes/docs.capacity' -import { Route as DocsBranchAnalysisRouteImport } from './routes/docs.branch-analysis' -import { Route as DocsBetaOnboardingRouteImport } from './routes/docs.beta-onboarding' -import { Route as DocsBacktestCalibrationRouteImport } from './routes/docs.backtest-calibration' -import { Route as DocsAmsUnattendedSchedulingRouteImport } from './routes/docs.ams-unattended-scheduling' -import { Route as DocsAmsSizingRouteImport } from './routes/docs.ams-sizing' -import { Route as DocsAmsOperationsRunbookRouteImport } from './routes/docs.ams-operations-runbook' -import { Route as DocsAmsObservabilityRouteImport } from './routes/docs.ams-observability' -import { Route as DocsAmsKillSwitchIncidentRouteImport } from './routes/docs.ams-kill-switch-incident' -import { Route as DocsAmsGoalSpecRouteImport } from './routes/docs.ams-goal-spec' -import { Route as DocsAmsFleetManifestRouteImport } from './routes/docs.ams-fleet-manifest' -import { Route as DocsAmsEnvReferenceRouteImport } from './routes/docs.ams-env-reference' -import { Route as DocsAmsDiscoveryPlaneRouteImport } from './routes/docs.ams-discovery-plane' -import { Route as DocsAmsDeploymentRouteImport } from './routes/docs.ams-deployment' -import { Route as DocsAmsConfigPrecedenceRouteImport } from './routes/docs.ams-config-precedence' -import { Route as DocsAiSummariesRouteImport } from './routes/docs.ai-summaries' +import { Route as DocsSlugRouteImport } from './routes/docs.$slug' import { Route as AppWorkbenchRouteImport } from './routes/app.workbench' import { Route as AppRunsRouteImport } from './routes/app.runs' import { Route as AppReposRouteImport } from './routes/app.repos' @@ -177,272 +129,15 @@ const InstallPermissionsRoute = InstallPermissionsRouteImport.update({ path: '/permissions', getParentRoute: () => InstallRoute, } as any) -const DocsUpstreamDriftRoute = DocsUpstreamDriftRouteImport.update({ - id: '/upstream-drift', - path: '/upstream-drift', - getParentRoute: () => DocsRoute, -} as any) -const DocsTuningRoute = DocsTuningRouteImport.update({ - id: '/tuning', - path: '/tuning', - getParentRoute: () => DocsRoute, -} as any) -const DocsTroubleshootingRoute = DocsTroubleshootingRouteImport.update({ - id: '/troubleshooting', - path: '/troubleshooting', - getParentRoute: () => DocsRoute, -} as any) -const DocsSelfHostingUnifiedAmsOrbRoute = - DocsSelfHostingUnifiedAmsOrbRouteImport.update({ - id: '/self-hosting-unified-ams-orb', - path: '/self-hosting-unified-ams-orb', - getParentRoute: () => DocsRoute, - } as any) -const DocsSelfHostingTroubleshootingRoute = - DocsSelfHostingTroubleshootingRouteImport.update({ - id: '/self-hosting-troubleshooting', - path: '/self-hosting-troubleshooting', - getParentRoute: () => DocsRoute, - } as any) -const DocsSelfHostingSecurityRoute = DocsSelfHostingSecurityRouteImport.update({ - id: '/self-hosting-security', - path: '/self-hosting-security', - getParentRoute: () => DocsRoute, -} as any) -const DocsSelfHostingReleasesRoute = DocsSelfHostingReleasesRouteImport.update({ - id: '/self-hosting-releases', - path: '/self-hosting-releases', - getParentRoute: () => DocsRoute, -} as any) -const DocsSelfHostingReleaseChecklistRoute = - DocsSelfHostingReleaseChecklistRouteImport.update({ - id: '/self-hosting-release-checklist', - path: '/self-hosting-release-checklist', - getParentRoute: () => DocsRoute, - } as any) -const DocsSelfHostingReesAnalyzersRoute = - DocsSelfHostingReesAnalyzersRouteImport.update({ - id: '/self-hosting-rees-analyzers', - path: '/self-hosting-rees-analyzers', - getParentRoute: () => DocsRoute, - } as any) -const DocsSelfHostingReesRoute = DocsSelfHostingReesRouteImport.update({ - id: '/self-hosting-rees', - path: '/self-hosting-rees', - getParentRoute: () => DocsRoute, -} as any) -const DocsSelfHostingRagRoute = DocsSelfHostingRagRouteImport.update({ - id: '/self-hosting-rag', - path: '/self-hosting-rag', - getParentRoute: () => DocsRoute, -} as any) -const DocsSelfHostingQuickstartRoute = - DocsSelfHostingQuickstartRouteImport.update({ - id: '/self-hosting-quickstart', - path: '/self-hosting-quickstart', - getParentRoute: () => DocsRoute, - } as any) -const DocsSelfHostingOperationsRoute = - DocsSelfHostingOperationsRouteImport.update({ - id: '/self-hosting-operations', - path: '/self-hosting-operations', - getParentRoute: () => DocsRoute, - } as any) -const DocsSelfHostingGithubAppRoute = - DocsSelfHostingGithubAppRouteImport.update({ - id: '/self-hosting-github-app', - path: '/self-hosting-github-app', - getParentRoute: () => DocsRoute, - } as any) -const DocsSelfHostingDocsAuditRoute = - DocsSelfHostingDocsAuditRouteImport.update({ - id: '/self-hosting-docs-audit', - path: '/self-hosting-docs-audit', - getParentRoute: () => DocsRoute, - } as any) -const DocsSelfHostingConfigurationRoute = - DocsSelfHostingConfigurationRouteImport.update({ - id: '/self-hosting-configuration', - path: '/self-hosting-configuration', - getParentRoute: () => DocsRoute, - } as any) -const DocsSelfHostingBackupScalingRoute = - DocsSelfHostingBackupScalingRouteImport.update({ - id: '/self-hosting-backup-scaling', - path: '/self-hosting-backup-scaling', - getParentRoute: () => DocsRoute, - } as any) -const DocsSelfHostingAiProvidersRoute = - DocsSelfHostingAiProvidersRouteImport.update({ - id: '/self-hosting-ai-providers', - path: '/self-hosting-ai-providers', - getParentRoute: () => DocsRoute, - } as any) -const DocsScoreabilityRoute = DocsScoreabilityRouteImport.update({ - id: '/scoreability', - path: '/scoreability', - getParentRoute: () => DocsRoute, -} as any) -const DocsQuickstartRoute = DocsQuickstartRouteImport.update({ - id: '/quickstart', - path: '/quickstart', - getParentRoute: () => DocsRoute, -} as any) -const DocsPrivacySecurityRoute = DocsPrivacySecurityRouteImport.update({ - id: '/privacy-security', - path: '/privacy-security', - getParentRoute: () => DocsRoute, -} as any) -const DocsOwnerChecklistRoute = DocsOwnerChecklistRouteImport.update({ - id: '/owner-checklist', - path: '/owner-checklist', - getParentRoute: () => DocsRoute, -} as any) -const DocsMinerWorkflowRoute = DocsMinerWorkflowRouteImport.update({ - id: '/miner-workflow', - path: '/miner-workflow', - getParentRoute: () => DocsRoute, -} as any) -const DocsMinerQuickstartRoute = DocsMinerQuickstartRouteImport.update({ - id: '/miner-quickstart', - path: '/miner-quickstart', - getParentRoute: () => DocsRoute, -} as any) -const DocsMinerCodingAgentRoute = DocsMinerCodingAgentRouteImport.update({ - id: '/miner-coding-agent', - path: '/miner-coding-agent', - getParentRoute: () => DocsRoute, -} as any) -const DocsMcpClientsRoute = DocsMcpClientsRouteImport.update({ - id: '/mcp-clients', - path: '/mcp-clients', - getParentRoute: () => DocsRoute, -} as any) -const DocsMaintainerWorkflowRoute = DocsMaintainerWorkflowRouteImport.update({ - id: '/maintainer-workflow', - path: '/maintainer-workflow', - getParentRoute: () => DocsRoute, -} as any) -const DocsMaintainerSelfHostingRoute = - DocsMaintainerSelfHostingRouteImport.update({ - id: '/maintainer-self-hosting', - path: '/maintainer-self-hosting', - getParentRoute: () => DocsRoute, - } as any) -const DocsMaintainerInstallTrustRoute = - DocsMaintainerInstallTrustRouteImport.update({ - id: '/maintainer-install-trust', - path: '/maintainer-install-trust', - getParentRoute: () => DocsRoute, - } as any) -const DocsLoopoverCommandsRoute = DocsLoopoverCommandsRouteImport.update({ - id: '/loopover-commands', - path: '/loopover-commands', - getParentRoute: () => DocsRoute, -} as any) -const DocsHowReviewsWorkRoute = DocsHowReviewsWorkRouteImport.update({ - id: '/how-reviews-work', - path: '/how-reviews-work', - getParentRoute: () => DocsRoute, -} as any) -const DocsGithubAppRoute = DocsGithubAppRouteImport.update({ - id: '/github-app', - path: '/github-app', - getParentRoute: () => DocsRoute, -} as any) const DocsFumadocsSpikeApiReferenceRoute = DocsFumadocsSpikeApiReferenceRouteImport.update({ id: '/fumadocs-spike-api-reference', path: '/fumadocs-spike-api-reference', getParentRoute: () => DocsRoute, } as any) -const DocsFederatedFleetIntelligenceRoute = - DocsFederatedFleetIntelligenceRouteImport.update({ - id: '/federated-fleet-intelligence', - path: '/federated-fleet-intelligence', - getParentRoute: () => DocsRoute, - } as any) -const DocsCapacityRoute = DocsCapacityRouteImport.update({ - id: '/capacity', - path: '/capacity', - getParentRoute: () => DocsRoute, -} as any) -const DocsBranchAnalysisRoute = DocsBranchAnalysisRouteImport.update({ - id: '/branch-analysis', - path: '/branch-analysis', - getParentRoute: () => DocsRoute, -} as any) -const DocsBetaOnboardingRoute = DocsBetaOnboardingRouteImport.update({ - id: '/beta-onboarding', - path: '/beta-onboarding', - getParentRoute: () => DocsRoute, -} as any) -const DocsBacktestCalibrationRoute = DocsBacktestCalibrationRouteImport.update({ - id: '/backtest-calibration', - path: '/backtest-calibration', - getParentRoute: () => DocsRoute, -} as any) -const DocsAmsUnattendedSchedulingRoute = - DocsAmsUnattendedSchedulingRouteImport.update({ - id: '/ams-unattended-scheduling', - path: '/ams-unattended-scheduling', - getParentRoute: () => DocsRoute, - } as any) -const DocsAmsSizingRoute = DocsAmsSizingRouteImport.update({ - id: '/ams-sizing', - path: '/ams-sizing', - getParentRoute: () => DocsRoute, -} as any) -const DocsAmsOperationsRunbookRoute = - DocsAmsOperationsRunbookRouteImport.update({ - id: '/ams-operations-runbook', - path: '/ams-operations-runbook', - getParentRoute: () => DocsRoute, - } as any) -const DocsAmsObservabilityRoute = DocsAmsObservabilityRouteImport.update({ - id: '/ams-observability', - path: '/ams-observability', - getParentRoute: () => DocsRoute, -} as any) -const DocsAmsKillSwitchIncidentRoute = - DocsAmsKillSwitchIncidentRouteImport.update({ - id: '/ams-kill-switch-incident', - path: '/ams-kill-switch-incident', - getParentRoute: () => DocsRoute, - } as any) -const DocsAmsGoalSpecRoute = DocsAmsGoalSpecRouteImport.update({ - id: '/ams-goal-spec', - path: '/ams-goal-spec', - getParentRoute: () => DocsRoute, -} as any) -const DocsAmsFleetManifestRoute = DocsAmsFleetManifestRouteImport.update({ - id: '/ams-fleet-manifest', - path: '/ams-fleet-manifest', - getParentRoute: () => DocsRoute, -} as any) -const DocsAmsEnvReferenceRoute = DocsAmsEnvReferenceRouteImport.update({ - id: '/ams-env-reference', - path: '/ams-env-reference', - getParentRoute: () => DocsRoute, -} as any) -const DocsAmsDiscoveryPlaneRoute = DocsAmsDiscoveryPlaneRouteImport.update({ - id: '/ams-discovery-plane', - path: '/ams-discovery-plane', - getParentRoute: () => DocsRoute, -} as any) -const DocsAmsDeploymentRoute = DocsAmsDeploymentRouteImport.update({ - id: '/ams-deployment', - path: '/ams-deployment', - getParentRoute: () => DocsRoute, -} as any) -const DocsAmsConfigPrecedenceRoute = DocsAmsConfigPrecedenceRouteImport.update({ - id: '/ams-config-precedence', - path: '/ams-config-precedence', - getParentRoute: () => DocsRoute, -} as any) -const DocsAiSummariesRoute = DocsAiSummariesRouteImport.update({ - id: '/ai-summaries', - path: '/ai-summaries', +const DocsSlugRoute = DocsSlugRouteImport.update({ + id: '/$slug', + path: '/$slug', getParentRoute: () => DocsRoute, } as any) const AppWorkbenchRoute = AppWorkbenchRouteImport.update({ @@ -548,56 +243,8 @@ export interface FileRoutesByFullPath { '/app/repos': typeof AppReposRoute '/app/runs': typeof AppRunsRoute '/app/workbench': typeof AppWorkbenchRoute - '/docs/ai-summaries': typeof DocsAiSummariesRoute - '/docs/ams-config-precedence': typeof DocsAmsConfigPrecedenceRoute - '/docs/ams-deployment': typeof DocsAmsDeploymentRoute - '/docs/ams-discovery-plane': typeof DocsAmsDiscoveryPlaneRoute - '/docs/ams-env-reference': typeof DocsAmsEnvReferenceRoute - '/docs/ams-fleet-manifest': typeof DocsAmsFleetManifestRoute - '/docs/ams-goal-spec': typeof DocsAmsGoalSpecRoute - '/docs/ams-kill-switch-incident': typeof DocsAmsKillSwitchIncidentRoute - '/docs/ams-observability': typeof DocsAmsObservabilityRoute - '/docs/ams-operations-runbook': typeof DocsAmsOperationsRunbookRoute - '/docs/ams-sizing': typeof DocsAmsSizingRoute - '/docs/ams-unattended-scheduling': typeof DocsAmsUnattendedSchedulingRoute - '/docs/backtest-calibration': typeof DocsBacktestCalibrationRoute - '/docs/beta-onboarding': typeof DocsBetaOnboardingRoute - '/docs/branch-analysis': typeof DocsBranchAnalysisRoute - '/docs/capacity': typeof DocsCapacityRoute - '/docs/federated-fleet-intelligence': typeof DocsFederatedFleetIntelligenceRoute + '/docs/$slug': typeof DocsSlugRoute '/docs/fumadocs-spike-api-reference': typeof DocsFumadocsSpikeApiReferenceRoute - '/docs/github-app': typeof DocsGithubAppRoute - '/docs/how-reviews-work': typeof DocsHowReviewsWorkRoute - '/docs/loopover-commands': typeof DocsLoopoverCommandsRoute - '/docs/maintainer-install-trust': typeof DocsMaintainerInstallTrustRoute - '/docs/maintainer-self-hosting': typeof DocsMaintainerSelfHostingRoute - '/docs/maintainer-workflow': typeof DocsMaintainerWorkflowRoute - '/docs/mcp-clients': typeof DocsMcpClientsRoute - '/docs/miner-coding-agent': typeof DocsMinerCodingAgentRoute - '/docs/miner-quickstart': typeof DocsMinerQuickstartRoute - '/docs/miner-workflow': typeof DocsMinerWorkflowRoute - '/docs/owner-checklist': typeof DocsOwnerChecklistRoute - '/docs/privacy-security': typeof DocsPrivacySecurityRoute - '/docs/quickstart': typeof DocsQuickstartRoute - '/docs/scoreability': typeof DocsScoreabilityRoute - '/docs/self-hosting-ai-providers': typeof DocsSelfHostingAiProvidersRoute - '/docs/self-hosting-backup-scaling': typeof DocsSelfHostingBackupScalingRoute - '/docs/self-hosting-configuration': typeof DocsSelfHostingConfigurationRoute - '/docs/self-hosting-docs-audit': typeof DocsSelfHostingDocsAuditRoute - '/docs/self-hosting-github-app': typeof DocsSelfHostingGithubAppRoute - '/docs/self-hosting-operations': typeof DocsSelfHostingOperationsRoute - '/docs/self-hosting-quickstart': typeof DocsSelfHostingQuickstartRoute - '/docs/self-hosting-rag': typeof DocsSelfHostingRagRoute - '/docs/self-hosting-rees': typeof DocsSelfHostingReesRoute - '/docs/self-hosting-rees-analyzers': typeof DocsSelfHostingReesAnalyzersRoute - '/docs/self-hosting-release-checklist': typeof DocsSelfHostingReleaseChecklistRoute - '/docs/self-hosting-releases': typeof DocsSelfHostingReleasesRoute - '/docs/self-hosting-security': typeof DocsSelfHostingSecurityRoute - '/docs/self-hosting-troubleshooting': typeof DocsSelfHostingTroubleshootingRoute - '/docs/self-hosting-unified-ams-orb': typeof DocsSelfHostingUnifiedAmsOrbRoute - '/docs/troubleshooting': typeof DocsTroubleshootingRoute - '/docs/tuning': typeof DocsTuningRoute - '/docs/upstream-drift': typeof DocsUpstreamDriftRoute '/install/permissions': typeof InstallPermissionsRoute '/api/': typeof ApiIndexRoute '/app/': typeof AppIndexRoute @@ -628,56 +275,8 @@ export interface FileRoutesByTo { '/app/repos': typeof AppReposRoute '/app/runs': typeof AppRunsRoute '/app/workbench': typeof AppWorkbenchRoute - '/docs/ai-summaries': typeof DocsAiSummariesRoute - '/docs/ams-config-precedence': typeof DocsAmsConfigPrecedenceRoute - '/docs/ams-deployment': typeof DocsAmsDeploymentRoute - '/docs/ams-discovery-plane': typeof DocsAmsDiscoveryPlaneRoute - '/docs/ams-env-reference': typeof DocsAmsEnvReferenceRoute - '/docs/ams-fleet-manifest': typeof DocsAmsFleetManifestRoute - '/docs/ams-goal-spec': typeof DocsAmsGoalSpecRoute - '/docs/ams-kill-switch-incident': typeof DocsAmsKillSwitchIncidentRoute - '/docs/ams-observability': typeof DocsAmsObservabilityRoute - '/docs/ams-operations-runbook': typeof DocsAmsOperationsRunbookRoute - '/docs/ams-sizing': typeof DocsAmsSizingRoute - '/docs/ams-unattended-scheduling': typeof DocsAmsUnattendedSchedulingRoute - '/docs/backtest-calibration': typeof DocsBacktestCalibrationRoute - '/docs/beta-onboarding': typeof DocsBetaOnboardingRoute - '/docs/branch-analysis': typeof DocsBranchAnalysisRoute - '/docs/capacity': typeof DocsCapacityRoute - '/docs/federated-fleet-intelligence': typeof DocsFederatedFleetIntelligenceRoute + '/docs/$slug': typeof DocsSlugRoute '/docs/fumadocs-spike-api-reference': typeof DocsFumadocsSpikeApiReferenceRoute - '/docs/github-app': typeof DocsGithubAppRoute - '/docs/how-reviews-work': typeof DocsHowReviewsWorkRoute - '/docs/loopover-commands': typeof DocsLoopoverCommandsRoute - '/docs/maintainer-install-trust': typeof DocsMaintainerInstallTrustRoute - '/docs/maintainer-self-hosting': typeof DocsMaintainerSelfHostingRoute - '/docs/maintainer-workflow': typeof DocsMaintainerWorkflowRoute - '/docs/mcp-clients': typeof DocsMcpClientsRoute - '/docs/miner-coding-agent': typeof DocsMinerCodingAgentRoute - '/docs/miner-quickstart': typeof DocsMinerQuickstartRoute - '/docs/miner-workflow': typeof DocsMinerWorkflowRoute - '/docs/owner-checklist': typeof DocsOwnerChecklistRoute - '/docs/privacy-security': typeof DocsPrivacySecurityRoute - '/docs/quickstart': typeof DocsQuickstartRoute - '/docs/scoreability': typeof DocsScoreabilityRoute - '/docs/self-hosting-ai-providers': typeof DocsSelfHostingAiProvidersRoute - '/docs/self-hosting-backup-scaling': typeof DocsSelfHostingBackupScalingRoute - '/docs/self-hosting-configuration': typeof DocsSelfHostingConfigurationRoute - '/docs/self-hosting-docs-audit': typeof DocsSelfHostingDocsAuditRoute - '/docs/self-hosting-github-app': typeof DocsSelfHostingGithubAppRoute - '/docs/self-hosting-operations': typeof DocsSelfHostingOperationsRoute - '/docs/self-hosting-quickstart': typeof DocsSelfHostingQuickstartRoute - '/docs/self-hosting-rag': typeof DocsSelfHostingRagRoute - '/docs/self-hosting-rees': typeof DocsSelfHostingReesRoute - '/docs/self-hosting-rees-analyzers': typeof DocsSelfHostingReesAnalyzersRoute - '/docs/self-hosting-release-checklist': typeof DocsSelfHostingReleaseChecklistRoute - '/docs/self-hosting-releases': typeof DocsSelfHostingReleasesRoute - '/docs/self-hosting-security': typeof DocsSelfHostingSecurityRoute - '/docs/self-hosting-troubleshooting': typeof DocsSelfHostingTroubleshootingRoute - '/docs/self-hosting-unified-ams-orb': typeof DocsSelfHostingUnifiedAmsOrbRoute - '/docs/troubleshooting': typeof DocsTroubleshootingRoute - '/docs/tuning': typeof DocsTuningRoute - '/docs/upstream-drift': typeof DocsUpstreamDriftRoute '/install/permissions': typeof InstallPermissionsRoute '/api': typeof ApiIndexRoute '/app': typeof AppIndexRoute @@ -713,56 +312,8 @@ export interface FileRoutesById { '/app/repos': typeof AppReposRoute '/app/runs': typeof AppRunsRoute '/app/workbench': typeof AppWorkbenchRoute - '/docs/ai-summaries': typeof DocsAiSummariesRoute - '/docs/ams-config-precedence': typeof DocsAmsConfigPrecedenceRoute - '/docs/ams-deployment': typeof DocsAmsDeploymentRoute - '/docs/ams-discovery-plane': typeof DocsAmsDiscoveryPlaneRoute - '/docs/ams-env-reference': typeof DocsAmsEnvReferenceRoute - '/docs/ams-fleet-manifest': typeof DocsAmsFleetManifestRoute - '/docs/ams-goal-spec': typeof DocsAmsGoalSpecRoute - '/docs/ams-kill-switch-incident': typeof DocsAmsKillSwitchIncidentRoute - '/docs/ams-observability': typeof DocsAmsObservabilityRoute - '/docs/ams-operations-runbook': typeof DocsAmsOperationsRunbookRoute - '/docs/ams-sizing': typeof DocsAmsSizingRoute - '/docs/ams-unattended-scheduling': typeof DocsAmsUnattendedSchedulingRoute - '/docs/backtest-calibration': typeof DocsBacktestCalibrationRoute - '/docs/beta-onboarding': typeof DocsBetaOnboardingRoute - '/docs/branch-analysis': typeof DocsBranchAnalysisRoute - '/docs/capacity': typeof DocsCapacityRoute - '/docs/federated-fleet-intelligence': typeof DocsFederatedFleetIntelligenceRoute + '/docs/$slug': typeof DocsSlugRoute '/docs/fumadocs-spike-api-reference': typeof DocsFumadocsSpikeApiReferenceRoute - '/docs/github-app': typeof DocsGithubAppRoute - '/docs/how-reviews-work': typeof DocsHowReviewsWorkRoute - '/docs/loopover-commands': typeof DocsLoopoverCommandsRoute - '/docs/maintainer-install-trust': typeof DocsMaintainerInstallTrustRoute - '/docs/maintainer-self-hosting': typeof DocsMaintainerSelfHostingRoute - '/docs/maintainer-workflow': typeof DocsMaintainerWorkflowRoute - '/docs/mcp-clients': typeof DocsMcpClientsRoute - '/docs/miner-coding-agent': typeof DocsMinerCodingAgentRoute - '/docs/miner-quickstart': typeof DocsMinerQuickstartRoute - '/docs/miner-workflow': typeof DocsMinerWorkflowRoute - '/docs/owner-checklist': typeof DocsOwnerChecklistRoute - '/docs/privacy-security': typeof DocsPrivacySecurityRoute - '/docs/quickstart': typeof DocsQuickstartRoute - '/docs/scoreability': typeof DocsScoreabilityRoute - '/docs/self-hosting-ai-providers': typeof DocsSelfHostingAiProvidersRoute - '/docs/self-hosting-backup-scaling': typeof DocsSelfHostingBackupScalingRoute - '/docs/self-hosting-configuration': typeof DocsSelfHostingConfigurationRoute - '/docs/self-hosting-docs-audit': typeof DocsSelfHostingDocsAuditRoute - '/docs/self-hosting-github-app': typeof DocsSelfHostingGithubAppRoute - '/docs/self-hosting-operations': typeof DocsSelfHostingOperationsRoute - '/docs/self-hosting-quickstart': typeof DocsSelfHostingQuickstartRoute - '/docs/self-hosting-rag': typeof DocsSelfHostingRagRoute - '/docs/self-hosting-rees': typeof DocsSelfHostingReesRoute - '/docs/self-hosting-rees-analyzers': typeof DocsSelfHostingReesAnalyzersRoute - '/docs/self-hosting-release-checklist': typeof DocsSelfHostingReleaseChecklistRoute - '/docs/self-hosting-releases': typeof DocsSelfHostingReleasesRoute - '/docs/self-hosting-security': typeof DocsSelfHostingSecurityRoute - '/docs/self-hosting-troubleshooting': typeof DocsSelfHostingTroubleshootingRoute - '/docs/self-hosting-unified-ams-orb': typeof DocsSelfHostingUnifiedAmsOrbRoute - '/docs/troubleshooting': typeof DocsTroubleshootingRoute - '/docs/tuning': typeof DocsTuningRoute - '/docs/upstream-drift': typeof DocsUpstreamDriftRoute '/install/permissions': typeof InstallPermissionsRoute '/api/': typeof ApiIndexRoute '/app/': typeof AppIndexRoute @@ -799,56 +350,8 @@ export interface FileRouteTypes { | '/app/repos' | '/app/runs' | '/app/workbench' - | '/docs/ai-summaries' - | '/docs/ams-config-precedence' - | '/docs/ams-deployment' - | '/docs/ams-discovery-plane' - | '/docs/ams-env-reference' - | '/docs/ams-fleet-manifest' - | '/docs/ams-goal-spec' - | '/docs/ams-kill-switch-incident' - | '/docs/ams-observability' - | '/docs/ams-operations-runbook' - | '/docs/ams-sizing' - | '/docs/ams-unattended-scheduling' - | '/docs/backtest-calibration' - | '/docs/beta-onboarding' - | '/docs/branch-analysis' - | '/docs/capacity' - | '/docs/federated-fleet-intelligence' + | '/docs/$slug' | '/docs/fumadocs-spike-api-reference' - | '/docs/github-app' - | '/docs/how-reviews-work' - | '/docs/loopover-commands' - | '/docs/maintainer-install-trust' - | '/docs/maintainer-self-hosting' - | '/docs/maintainer-workflow' - | '/docs/mcp-clients' - | '/docs/miner-coding-agent' - | '/docs/miner-quickstart' - | '/docs/miner-workflow' - | '/docs/owner-checklist' - | '/docs/privacy-security' - | '/docs/quickstart' - | '/docs/scoreability' - | '/docs/self-hosting-ai-providers' - | '/docs/self-hosting-backup-scaling' - | '/docs/self-hosting-configuration' - | '/docs/self-hosting-docs-audit' - | '/docs/self-hosting-github-app' - | '/docs/self-hosting-operations' - | '/docs/self-hosting-quickstart' - | '/docs/self-hosting-rag' - | '/docs/self-hosting-rees' - | '/docs/self-hosting-rees-analyzers' - | '/docs/self-hosting-release-checklist' - | '/docs/self-hosting-releases' - | '/docs/self-hosting-security' - | '/docs/self-hosting-troubleshooting' - | '/docs/self-hosting-unified-ams-orb' - | '/docs/troubleshooting' - | '/docs/tuning' - | '/docs/upstream-drift' | '/install/permissions' | '/api/' | '/app/' @@ -879,56 +382,8 @@ export interface FileRouteTypes { | '/app/repos' | '/app/runs' | '/app/workbench' - | '/docs/ai-summaries' - | '/docs/ams-config-precedence' - | '/docs/ams-deployment' - | '/docs/ams-discovery-plane' - | '/docs/ams-env-reference' - | '/docs/ams-fleet-manifest' - | '/docs/ams-goal-spec' - | '/docs/ams-kill-switch-incident' - | '/docs/ams-observability' - | '/docs/ams-operations-runbook' - | '/docs/ams-sizing' - | '/docs/ams-unattended-scheduling' - | '/docs/backtest-calibration' - | '/docs/beta-onboarding' - | '/docs/branch-analysis' - | '/docs/capacity' - | '/docs/federated-fleet-intelligence' + | '/docs/$slug' | '/docs/fumadocs-spike-api-reference' - | '/docs/github-app' - | '/docs/how-reviews-work' - | '/docs/loopover-commands' - | '/docs/maintainer-install-trust' - | '/docs/maintainer-self-hosting' - | '/docs/maintainer-workflow' - | '/docs/mcp-clients' - | '/docs/miner-coding-agent' - | '/docs/miner-quickstart' - | '/docs/miner-workflow' - | '/docs/owner-checklist' - | '/docs/privacy-security' - | '/docs/quickstart' - | '/docs/scoreability' - | '/docs/self-hosting-ai-providers' - | '/docs/self-hosting-backup-scaling' - | '/docs/self-hosting-configuration' - | '/docs/self-hosting-docs-audit' - | '/docs/self-hosting-github-app' - | '/docs/self-hosting-operations' - | '/docs/self-hosting-quickstart' - | '/docs/self-hosting-rag' - | '/docs/self-hosting-rees' - | '/docs/self-hosting-rees-analyzers' - | '/docs/self-hosting-release-checklist' - | '/docs/self-hosting-releases' - | '/docs/self-hosting-security' - | '/docs/self-hosting-troubleshooting' - | '/docs/self-hosting-unified-ams-orb' - | '/docs/troubleshooting' - | '/docs/tuning' - | '/docs/upstream-drift' | '/install/permissions' | '/api' | '/app' @@ -963,56 +418,8 @@ export interface FileRouteTypes { | '/app/repos' | '/app/runs' | '/app/workbench' - | '/docs/ai-summaries' - | '/docs/ams-config-precedence' - | '/docs/ams-deployment' - | '/docs/ams-discovery-plane' - | '/docs/ams-env-reference' - | '/docs/ams-fleet-manifest' - | '/docs/ams-goal-spec' - | '/docs/ams-kill-switch-incident' - | '/docs/ams-observability' - | '/docs/ams-operations-runbook' - | '/docs/ams-sizing' - | '/docs/ams-unattended-scheduling' - | '/docs/backtest-calibration' - | '/docs/beta-onboarding' - | '/docs/branch-analysis' - | '/docs/capacity' - | '/docs/federated-fleet-intelligence' + | '/docs/$slug' | '/docs/fumadocs-spike-api-reference' - | '/docs/github-app' - | '/docs/how-reviews-work' - | '/docs/loopover-commands' - | '/docs/maintainer-install-trust' - | '/docs/maintainer-self-hosting' - | '/docs/maintainer-workflow' - | '/docs/mcp-clients' - | '/docs/miner-coding-agent' - | '/docs/miner-quickstart' - | '/docs/miner-workflow' - | '/docs/owner-checklist' - | '/docs/privacy-security' - | '/docs/quickstart' - | '/docs/scoreability' - | '/docs/self-hosting-ai-providers' - | '/docs/self-hosting-backup-scaling' - | '/docs/self-hosting-configuration' - | '/docs/self-hosting-docs-audit' - | '/docs/self-hosting-github-app' - | '/docs/self-hosting-operations' - | '/docs/self-hosting-quickstart' - | '/docs/self-hosting-rag' - | '/docs/self-hosting-rees' - | '/docs/self-hosting-rees-analyzers' - | '/docs/self-hosting-release-checklist' - | '/docs/self-hosting-releases' - | '/docs/self-hosting-security' - | '/docs/self-hosting-troubleshooting' - | '/docs/self-hosting-unified-ams-orb' - | '/docs/troubleshooting' - | '/docs/tuning' - | '/docs/upstream-drift' | '/install/permissions' | '/api/' | '/app/' @@ -1158,230 +565,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof InstallPermissionsRouteImport parentRoute: typeof InstallRoute } - '/docs/upstream-drift': { - id: '/docs/upstream-drift' - path: '/upstream-drift' - fullPath: '/docs/upstream-drift' - preLoaderRoute: typeof DocsUpstreamDriftRouteImport - parentRoute: typeof DocsRoute - } - '/docs/tuning': { - id: '/docs/tuning' - path: '/tuning' - fullPath: '/docs/tuning' - preLoaderRoute: typeof DocsTuningRouteImport - parentRoute: typeof DocsRoute - } - '/docs/troubleshooting': { - id: '/docs/troubleshooting' - path: '/troubleshooting' - fullPath: '/docs/troubleshooting' - preLoaderRoute: typeof DocsTroubleshootingRouteImport - parentRoute: typeof DocsRoute - } - '/docs/self-hosting-unified-ams-orb': { - id: '/docs/self-hosting-unified-ams-orb' - path: '/self-hosting-unified-ams-orb' - fullPath: '/docs/self-hosting-unified-ams-orb' - preLoaderRoute: typeof DocsSelfHostingUnifiedAmsOrbRouteImport - parentRoute: typeof DocsRoute - } - '/docs/self-hosting-troubleshooting': { - id: '/docs/self-hosting-troubleshooting' - path: '/self-hosting-troubleshooting' - fullPath: '/docs/self-hosting-troubleshooting' - preLoaderRoute: typeof DocsSelfHostingTroubleshootingRouteImport - parentRoute: typeof DocsRoute - } - '/docs/self-hosting-security': { - id: '/docs/self-hosting-security' - path: '/self-hosting-security' - fullPath: '/docs/self-hosting-security' - preLoaderRoute: typeof DocsSelfHostingSecurityRouteImport - parentRoute: typeof DocsRoute - } - '/docs/self-hosting-releases': { - id: '/docs/self-hosting-releases' - path: '/self-hosting-releases' - fullPath: '/docs/self-hosting-releases' - preLoaderRoute: typeof DocsSelfHostingReleasesRouteImport - parentRoute: typeof DocsRoute - } - '/docs/self-hosting-release-checklist': { - id: '/docs/self-hosting-release-checklist' - path: '/self-hosting-release-checklist' - fullPath: '/docs/self-hosting-release-checklist' - preLoaderRoute: typeof DocsSelfHostingReleaseChecklistRouteImport - parentRoute: typeof DocsRoute - } - '/docs/self-hosting-rees-analyzers': { - id: '/docs/self-hosting-rees-analyzers' - path: '/self-hosting-rees-analyzers' - fullPath: '/docs/self-hosting-rees-analyzers' - preLoaderRoute: typeof DocsSelfHostingReesAnalyzersRouteImport - parentRoute: typeof DocsRoute - } - '/docs/self-hosting-rees': { - id: '/docs/self-hosting-rees' - path: '/self-hosting-rees' - fullPath: '/docs/self-hosting-rees' - preLoaderRoute: typeof DocsSelfHostingReesRouteImport - parentRoute: typeof DocsRoute - } - '/docs/self-hosting-rag': { - id: '/docs/self-hosting-rag' - path: '/self-hosting-rag' - fullPath: '/docs/self-hosting-rag' - preLoaderRoute: typeof DocsSelfHostingRagRouteImport - parentRoute: typeof DocsRoute - } - '/docs/self-hosting-quickstart': { - id: '/docs/self-hosting-quickstart' - path: '/self-hosting-quickstart' - fullPath: '/docs/self-hosting-quickstart' - preLoaderRoute: typeof DocsSelfHostingQuickstartRouteImport - parentRoute: typeof DocsRoute - } - '/docs/self-hosting-operations': { - id: '/docs/self-hosting-operations' - path: '/self-hosting-operations' - fullPath: '/docs/self-hosting-operations' - preLoaderRoute: typeof DocsSelfHostingOperationsRouteImport - parentRoute: typeof DocsRoute - } - '/docs/self-hosting-github-app': { - id: '/docs/self-hosting-github-app' - path: '/self-hosting-github-app' - fullPath: '/docs/self-hosting-github-app' - preLoaderRoute: typeof DocsSelfHostingGithubAppRouteImport - parentRoute: typeof DocsRoute - } - '/docs/self-hosting-docs-audit': { - id: '/docs/self-hosting-docs-audit' - path: '/self-hosting-docs-audit' - fullPath: '/docs/self-hosting-docs-audit' - preLoaderRoute: typeof DocsSelfHostingDocsAuditRouteImport - parentRoute: typeof DocsRoute - } - '/docs/self-hosting-configuration': { - id: '/docs/self-hosting-configuration' - path: '/self-hosting-configuration' - fullPath: '/docs/self-hosting-configuration' - preLoaderRoute: typeof DocsSelfHostingConfigurationRouteImport - parentRoute: typeof DocsRoute - } - '/docs/self-hosting-backup-scaling': { - id: '/docs/self-hosting-backup-scaling' - path: '/self-hosting-backup-scaling' - fullPath: '/docs/self-hosting-backup-scaling' - preLoaderRoute: typeof DocsSelfHostingBackupScalingRouteImport - parentRoute: typeof DocsRoute - } - '/docs/self-hosting-ai-providers': { - id: '/docs/self-hosting-ai-providers' - path: '/self-hosting-ai-providers' - fullPath: '/docs/self-hosting-ai-providers' - preLoaderRoute: typeof DocsSelfHostingAiProvidersRouteImport - parentRoute: typeof DocsRoute - } - '/docs/scoreability': { - id: '/docs/scoreability' - path: '/scoreability' - fullPath: '/docs/scoreability' - preLoaderRoute: typeof DocsScoreabilityRouteImport - parentRoute: typeof DocsRoute - } - '/docs/quickstart': { - id: '/docs/quickstart' - path: '/quickstart' - fullPath: '/docs/quickstart' - preLoaderRoute: typeof DocsQuickstartRouteImport - parentRoute: typeof DocsRoute - } - '/docs/privacy-security': { - id: '/docs/privacy-security' - path: '/privacy-security' - fullPath: '/docs/privacy-security' - preLoaderRoute: typeof DocsPrivacySecurityRouteImport - parentRoute: typeof DocsRoute - } - '/docs/owner-checklist': { - id: '/docs/owner-checklist' - path: '/owner-checklist' - fullPath: '/docs/owner-checklist' - preLoaderRoute: typeof DocsOwnerChecklistRouteImport - parentRoute: typeof DocsRoute - } - '/docs/miner-workflow': { - id: '/docs/miner-workflow' - path: '/miner-workflow' - fullPath: '/docs/miner-workflow' - preLoaderRoute: typeof DocsMinerWorkflowRouteImport - parentRoute: typeof DocsRoute - } - '/docs/miner-quickstart': { - id: '/docs/miner-quickstart' - path: '/miner-quickstart' - fullPath: '/docs/miner-quickstart' - preLoaderRoute: typeof DocsMinerQuickstartRouteImport - parentRoute: typeof DocsRoute - } - '/docs/miner-coding-agent': { - id: '/docs/miner-coding-agent' - path: '/miner-coding-agent' - fullPath: '/docs/miner-coding-agent' - preLoaderRoute: typeof DocsMinerCodingAgentRouteImport - parentRoute: typeof DocsRoute - } - '/docs/mcp-clients': { - id: '/docs/mcp-clients' - path: '/mcp-clients' - fullPath: '/docs/mcp-clients' - preLoaderRoute: typeof DocsMcpClientsRouteImport - parentRoute: typeof DocsRoute - } - '/docs/maintainer-workflow': { - id: '/docs/maintainer-workflow' - path: '/maintainer-workflow' - fullPath: '/docs/maintainer-workflow' - preLoaderRoute: typeof DocsMaintainerWorkflowRouteImport - parentRoute: typeof DocsRoute - } - '/docs/maintainer-self-hosting': { - id: '/docs/maintainer-self-hosting' - path: '/maintainer-self-hosting' - fullPath: '/docs/maintainer-self-hosting' - preLoaderRoute: typeof DocsMaintainerSelfHostingRouteImport - parentRoute: typeof DocsRoute - } - '/docs/maintainer-install-trust': { - id: '/docs/maintainer-install-trust' - path: '/maintainer-install-trust' - fullPath: '/docs/maintainer-install-trust' - preLoaderRoute: typeof DocsMaintainerInstallTrustRouteImport - parentRoute: typeof DocsRoute - } - '/docs/loopover-commands': { - id: '/docs/loopover-commands' - path: '/loopover-commands' - fullPath: '/docs/loopover-commands' - preLoaderRoute: typeof DocsLoopoverCommandsRouteImport - parentRoute: typeof DocsRoute - } - '/docs/how-reviews-work': { - id: '/docs/how-reviews-work' - path: '/how-reviews-work' - fullPath: '/docs/how-reviews-work' - preLoaderRoute: typeof DocsHowReviewsWorkRouteImport - parentRoute: typeof DocsRoute - } - '/docs/github-app': { - id: '/docs/github-app' - path: '/github-app' - fullPath: '/docs/github-app' - preLoaderRoute: typeof DocsGithubAppRouteImport - parentRoute: typeof DocsRoute - } '/docs/fumadocs-spike-api-reference': { id: '/docs/fumadocs-spike-api-reference' path: '/fumadocs-spike-api-reference' @@ -1389,123 +572,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DocsFumadocsSpikeApiReferenceRouteImport parentRoute: typeof DocsRoute } - '/docs/federated-fleet-intelligence': { - id: '/docs/federated-fleet-intelligence' - path: '/federated-fleet-intelligence' - fullPath: '/docs/federated-fleet-intelligence' - preLoaderRoute: typeof DocsFederatedFleetIntelligenceRouteImport - parentRoute: typeof DocsRoute - } - '/docs/capacity': { - id: '/docs/capacity' - path: '/capacity' - fullPath: '/docs/capacity' - preLoaderRoute: typeof DocsCapacityRouteImport - parentRoute: typeof DocsRoute - } - '/docs/branch-analysis': { - id: '/docs/branch-analysis' - path: '/branch-analysis' - fullPath: '/docs/branch-analysis' - preLoaderRoute: typeof DocsBranchAnalysisRouteImport - parentRoute: typeof DocsRoute - } - '/docs/beta-onboarding': { - id: '/docs/beta-onboarding' - path: '/beta-onboarding' - fullPath: '/docs/beta-onboarding' - preLoaderRoute: typeof DocsBetaOnboardingRouteImport - parentRoute: typeof DocsRoute - } - '/docs/backtest-calibration': { - id: '/docs/backtest-calibration' - path: '/backtest-calibration' - fullPath: '/docs/backtest-calibration' - preLoaderRoute: typeof DocsBacktestCalibrationRouteImport - parentRoute: typeof DocsRoute - } - '/docs/ams-unattended-scheduling': { - id: '/docs/ams-unattended-scheduling' - path: '/ams-unattended-scheduling' - fullPath: '/docs/ams-unattended-scheduling' - preLoaderRoute: typeof DocsAmsUnattendedSchedulingRouteImport - parentRoute: typeof DocsRoute - } - '/docs/ams-sizing': { - id: '/docs/ams-sizing' - path: '/ams-sizing' - fullPath: '/docs/ams-sizing' - preLoaderRoute: typeof DocsAmsSizingRouteImport - parentRoute: typeof DocsRoute - } - '/docs/ams-operations-runbook': { - id: '/docs/ams-operations-runbook' - path: '/ams-operations-runbook' - fullPath: '/docs/ams-operations-runbook' - preLoaderRoute: typeof DocsAmsOperationsRunbookRouteImport - parentRoute: typeof DocsRoute - } - '/docs/ams-observability': { - id: '/docs/ams-observability' - path: '/ams-observability' - fullPath: '/docs/ams-observability' - preLoaderRoute: typeof DocsAmsObservabilityRouteImport - parentRoute: typeof DocsRoute - } - '/docs/ams-kill-switch-incident': { - id: '/docs/ams-kill-switch-incident' - path: '/ams-kill-switch-incident' - fullPath: '/docs/ams-kill-switch-incident' - preLoaderRoute: typeof DocsAmsKillSwitchIncidentRouteImport - parentRoute: typeof DocsRoute - } - '/docs/ams-goal-spec': { - id: '/docs/ams-goal-spec' - path: '/ams-goal-spec' - fullPath: '/docs/ams-goal-spec' - preLoaderRoute: typeof DocsAmsGoalSpecRouteImport - parentRoute: typeof DocsRoute - } - '/docs/ams-fleet-manifest': { - id: '/docs/ams-fleet-manifest' - path: '/ams-fleet-manifest' - fullPath: '/docs/ams-fleet-manifest' - preLoaderRoute: typeof DocsAmsFleetManifestRouteImport - parentRoute: typeof DocsRoute - } - '/docs/ams-env-reference': { - id: '/docs/ams-env-reference' - path: '/ams-env-reference' - fullPath: '/docs/ams-env-reference' - preLoaderRoute: typeof DocsAmsEnvReferenceRouteImport - parentRoute: typeof DocsRoute - } - '/docs/ams-discovery-plane': { - id: '/docs/ams-discovery-plane' - path: '/ams-discovery-plane' - fullPath: '/docs/ams-discovery-plane' - preLoaderRoute: typeof DocsAmsDiscoveryPlaneRouteImport - parentRoute: typeof DocsRoute - } - '/docs/ams-deployment': { - id: '/docs/ams-deployment' - path: '/ams-deployment' - fullPath: '/docs/ams-deployment' - preLoaderRoute: typeof DocsAmsDeploymentRouteImport - parentRoute: typeof DocsRoute - } - '/docs/ams-config-precedence': { - id: '/docs/ams-config-precedence' - path: '/ams-config-precedence' - fullPath: '/docs/ams-config-precedence' - preLoaderRoute: typeof DocsAmsConfigPrecedenceRouteImport - parentRoute: typeof DocsRoute - } - '/docs/ai-summaries': { - id: '/docs/ai-summaries' - path: '/ai-summaries' - fullPath: '/docs/ai-summaries' - preLoaderRoute: typeof DocsAiSummariesRouteImport + '/docs/$slug': { + id: '/docs/$slug' + path: '/$slug' + fullPath: '/docs/$slug' + preLoaderRoute: typeof DocsSlugRouteImport parentRoute: typeof DocsRoute } '/app/workbench': { @@ -1665,110 +736,14 @@ const AppRouteChildren: AppRouteChildren = { const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren) interface DocsRouteChildren { - DocsAiSummariesRoute: typeof DocsAiSummariesRoute - DocsAmsConfigPrecedenceRoute: typeof DocsAmsConfigPrecedenceRoute - DocsAmsDeploymentRoute: typeof DocsAmsDeploymentRoute - DocsAmsDiscoveryPlaneRoute: typeof DocsAmsDiscoveryPlaneRoute - DocsAmsEnvReferenceRoute: typeof DocsAmsEnvReferenceRoute - DocsAmsFleetManifestRoute: typeof DocsAmsFleetManifestRoute - DocsAmsGoalSpecRoute: typeof DocsAmsGoalSpecRoute - DocsAmsKillSwitchIncidentRoute: typeof DocsAmsKillSwitchIncidentRoute - DocsAmsObservabilityRoute: typeof DocsAmsObservabilityRoute - DocsAmsOperationsRunbookRoute: typeof DocsAmsOperationsRunbookRoute - DocsAmsSizingRoute: typeof DocsAmsSizingRoute - DocsAmsUnattendedSchedulingRoute: typeof DocsAmsUnattendedSchedulingRoute - DocsBacktestCalibrationRoute: typeof DocsBacktestCalibrationRoute - DocsBetaOnboardingRoute: typeof DocsBetaOnboardingRoute - DocsBranchAnalysisRoute: typeof DocsBranchAnalysisRoute - DocsCapacityRoute: typeof DocsCapacityRoute - DocsFederatedFleetIntelligenceRoute: typeof DocsFederatedFleetIntelligenceRoute + DocsSlugRoute: typeof DocsSlugRoute DocsFumadocsSpikeApiReferenceRoute: typeof DocsFumadocsSpikeApiReferenceRoute - DocsGithubAppRoute: typeof DocsGithubAppRoute - DocsHowReviewsWorkRoute: typeof DocsHowReviewsWorkRoute - DocsLoopoverCommandsRoute: typeof DocsLoopoverCommandsRoute - DocsMaintainerInstallTrustRoute: typeof DocsMaintainerInstallTrustRoute - DocsMaintainerSelfHostingRoute: typeof DocsMaintainerSelfHostingRoute - DocsMaintainerWorkflowRoute: typeof DocsMaintainerWorkflowRoute - DocsMcpClientsRoute: typeof DocsMcpClientsRoute - DocsMinerCodingAgentRoute: typeof DocsMinerCodingAgentRoute - DocsMinerQuickstartRoute: typeof DocsMinerQuickstartRoute - DocsMinerWorkflowRoute: typeof DocsMinerWorkflowRoute - DocsOwnerChecklistRoute: typeof DocsOwnerChecklistRoute - DocsPrivacySecurityRoute: typeof DocsPrivacySecurityRoute - DocsQuickstartRoute: typeof DocsQuickstartRoute - DocsScoreabilityRoute: typeof DocsScoreabilityRoute - DocsSelfHostingAiProvidersRoute: typeof DocsSelfHostingAiProvidersRoute - DocsSelfHostingBackupScalingRoute: typeof DocsSelfHostingBackupScalingRoute - DocsSelfHostingConfigurationRoute: typeof DocsSelfHostingConfigurationRoute - DocsSelfHostingDocsAuditRoute: typeof DocsSelfHostingDocsAuditRoute - DocsSelfHostingGithubAppRoute: typeof DocsSelfHostingGithubAppRoute - DocsSelfHostingOperationsRoute: typeof DocsSelfHostingOperationsRoute - DocsSelfHostingQuickstartRoute: typeof DocsSelfHostingQuickstartRoute - DocsSelfHostingRagRoute: typeof DocsSelfHostingRagRoute - DocsSelfHostingReesRoute: typeof DocsSelfHostingReesRoute - DocsSelfHostingReesAnalyzersRoute: typeof DocsSelfHostingReesAnalyzersRoute - DocsSelfHostingReleaseChecklistRoute: typeof DocsSelfHostingReleaseChecklistRoute - DocsSelfHostingReleasesRoute: typeof DocsSelfHostingReleasesRoute - DocsSelfHostingSecurityRoute: typeof DocsSelfHostingSecurityRoute - DocsSelfHostingTroubleshootingRoute: typeof DocsSelfHostingTroubleshootingRoute - DocsSelfHostingUnifiedAmsOrbRoute: typeof DocsSelfHostingUnifiedAmsOrbRoute - DocsTroubleshootingRoute: typeof DocsTroubleshootingRoute - DocsTuningRoute: typeof DocsTuningRoute - DocsUpstreamDriftRoute: typeof DocsUpstreamDriftRoute DocsIndexRoute: typeof DocsIndexRoute } const DocsRouteChildren: DocsRouteChildren = { - DocsAiSummariesRoute: DocsAiSummariesRoute, - DocsAmsConfigPrecedenceRoute: DocsAmsConfigPrecedenceRoute, - DocsAmsDeploymentRoute: DocsAmsDeploymentRoute, - DocsAmsDiscoveryPlaneRoute: DocsAmsDiscoveryPlaneRoute, - DocsAmsEnvReferenceRoute: DocsAmsEnvReferenceRoute, - DocsAmsFleetManifestRoute: DocsAmsFleetManifestRoute, - DocsAmsGoalSpecRoute: DocsAmsGoalSpecRoute, - DocsAmsKillSwitchIncidentRoute: DocsAmsKillSwitchIncidentRoute, - DocsAmsObservabilityRoute: DocsAmsObservabilityRoute, - DocsAmsOperationsRunbookRoute: DocsAmsOperationsRunbookRoute, - DocsAmsSizingRoute: DocsAmsSizingRoute, - DocsAmsUnattendedSchedulingRoute: DocsAmsUnattendedSchedulingRoute, - DocsBacktestCalibrationRoute: DocsBacktestCalibrationRoute, - DocsBetaOnboardingRoute: DocsBetaOnboardingRoute, - DocsBranchAnalysisRoute: DocsBranchAnalysisRoute, - DocsCapacityRoute: DocsCapacityRoute, - DocsFederatedFleetIntelligenceRoute: DocsFederatedFleetIntelligenceRoute, + DocsSlugRoute: DocsSlugRoute, DocsFumadocsSpikeApiReferenceRoute: DocsFumadocsSpikeApiReferenceRoute, - DocsGithubAppRoute: DocsGithubAppRoute, - DocsHowReviewsWorkRoute: DocsHowReviewsWorkRoute, - DocsLoopoverCommandsRoute: DocsLoopoverCommandsRoute, - DocsMaintainerInstallTrustRoute: DocsMaintainerInstallTrustRoute, - DocsMaintainerSelfHostingRoute: DocsMaintainerSelfHostingRoute, - DocsMaintainerWorkflowRoute: DocsMaintainerWorkflowRoute, - DocsMcpClientsRoute: DocsMcpClientsRoute, - DocsMinerCodingAgentRoute: DocsMinerCodingAgentRoute, - DocsMinerQuickstartRoute: DocsMinerQuickstartRoute, - DocsMinerWorkflowRoute: DocsMinerWorkflowRoute, - DocsOwnerChecklistRoute: DocsOwnerChecklistRoute, - DocsPrivacySecurityRoute: DocsPrivacySecurityRoute, - DocsQuickstartRoute: DocsQuickstartRoute, - DocsScoreabilityRoute: DocsScoreabilityRoute, - DocsSelfHostingAiProvidersRoute: DocsSelfHostingAiProvidersRoute, - DocsSelfHostingBackupScalingRoute: DocsSelfHostingBackupScalingRoute, - DocsSelfHostingConfigurationRoute: DocsSelfHostingConfigurationRoute, - DocsSelfHostingDocsAuditRoute: DocsSelfHostingDocsAuditRoute, - DocsSelfHostingGithubAppRoute: DocsSelfHostingGithubAppRoute, - DocsSelfHostingOperationsRoute: DocsSelfHostingOperationsRoute, - DocsSelfHostingQuickstartRoute: DocsSelfHostingQuickstartRoute, - DocsSelfHostingRagRoute: DocsSelfHostingRagRoute, - DocsSelfHostingReesRoute: DocsSelfHostingReesRoute, - DocsSelfHostingReesAnalyzersRoute: DocsSelfHostingReesAnalyzersRoute, - DocsSelfHostingReleaseChecklistRoute: DocsSelfHostingReleaseChecklistRoute, - DocsSelfHostingReleasesRoute: DocsSelfHostingReleasesRoute, - DocsSelfHostingSecurityRoute: DocsSelfHostingSecurityRoute, - DocsSelfHostingTroubleshootingRoute: DocsSelfHostingTroubleshootingRoute, - DocsSelfHostingUnifiedAmsOrbRoute: DocsSelfHostingUnifiedAmsOrbRoute, - DocsTroubleshootingRoute: DocsTroubleshootingRoute, - DocsTuningRoute: DocsTuningRoute, - DocsUpstreamDriftRoute: DocsUpstreamDriftRoute, DocsIndexRoute: DocsIndexRoute, } diff --git a/apps/loopover-ui/src/routes/agents.tsx b/apps/loopover-ui/src/routes/agents.tsx index 7deef920de..cee62358f9 100644 --- a/apps/loopover-ui/src/routes/agents.tsx +++ b/apps/loopover-ui/src/routes/agents.tsx @@ -41,7 +41,8 @@ function AgentsPage() {

    MCP client setup @@ -188,7 +189,8 @@ loopover-mcp init-client --print cursor`}
    Client setup diff --git a/apps/loopover-ui/src/routes/docs-routes-loading-state.test.tsx b/apps/loopover-ui/src/routes/docs-routes-loading-state.test.tsx index 1d0c07bcda..b002ac1891 100644 --- a/apps/loopover-ui/src/routes/docs-routes-loading-state.test.tsx +++ b/apps/loopover-ui/src/routes/docs-routes-loading-state.test.tsx @@ -29,7 +29,7 @@ describe("docs route Suspense fallback (#6982)", () => { expect(screen.getByText("Loading…")).toBeTruthy(); }); - it("every docs.*.tsx route imports LoadingState and uses it as the sole Suspense fallback, not a hand-rolled

    ", () => { + it("the single dynamic docs.$slug.tsx route imports LoadingState and uses it as the sole Suspense fallback, not a hand-rolled

    ", () => { const routesDir = join(process.cwd(), "src/routes"); const docsRouteFiles = readdirSync(routesDir).filter( (name) => name.startsWith("docs.") && name.endsWith(".tsx") && !name.endsWith(".test.tsx"), @@ -38,14 +38,16 @@ describe("docs route Suspense fallback (#6982)", () => { // fumadocs-spike-api-reference.tsx is a standalone Scalar API-reference spike (#6037) that wraps // ClientOnly, not Suspense; docs.index.tsx is a static landing page with no MDX content and no // Suspense boundary; docs.tsx is the shared parent layout route, not a content page. None were - // part of this issue's 46-file scope. + // part of this issue's original 46-file scope, nor of #8151's dynamic-route collapse. const outOfScope = new Set([ "docs.fumadocs-spike-api-reference.tsx", "docs.index.tsx", "docs.tsx", ]); const inScope = docsRouteFiles.filter((name) => !outOfScope.has(name)); - expect(inScope.length).toBe(49); + // #8151: the 49 per-page docs..tsx files collapsed into one dynamic docs.$slug.tsx + // route -- a shape assertion now, not a per-page count bumped on every new docs page. + expect(inScope).toEqual(["docs.$slug.tsx"]); for (const file of inScope) { const source = readFileSync(join(routesDir, file), "utf8"); diff --git a/apps/loopover-ui/src/routes/docs.$slug.tsx b/apps/loopover-ui/src/routes/docs.$slug.tsx new file mode 100644 index 0000000000..ac613ae80b --- /dev/null +++ b/apps/loopover-ui/src/routes/docs.$slug.tsx @@ -0,0 +1,53 @@ +import { createFileRoute, notFound } from "@tanstack/react-router"; +import { Suspense } from "react"; + +import { DocsPage } from "@/components/site/docs-page"; +import { LoadingState } from "@/components/site/state-views"; +import { docsClientLoader } from "@/lib/docs-client-loader"; +import { getDocPage } from "@/lib/docs-source.functions"; + +// One dynamic route for every docs page (#8151), replacing 49 near-identical per-page +// docs..tsx files that each duplicated this exact loader/head/component shape by hand. +// title/description/eyebrow all come from the .mdx frontmatter (source.config.ts's schema), +// so adding a docs page is now just a new .mdx file + a nav entry -- no route file, no +// routeTree regen beyond what any new file already triggers, no test-count bump. +// +// docs.index.tsx (a real static page, not MDX-backed) and +// docs.fumadocs-spike-api-reference.tsx (a standalone Scalar widget, not DocsPage/MDX at all) +// stay as their own route files -- neither is boilerplate this route replaces. +export const Route = createFileRoute("/docs/$slug")({ + loader: async ({ params }) => { + const page = await getDocPage({ data: { slugs: [params.slug] } }); + if (!page) throw notFound(); + return page; + }, + head: ({ loaderData, params }) => { + if (!loaderData) return { meta: [{ title: "LoopOver docs" }] }; + const { title, description } = loaderData; + const pageTitle = `${title} — LoopOver docs`; + const url = `/docs/${params.slug}`; + return { + meta: [ + { title: pageTitle }, + { name: "description", content: description }, + { property: "og:title", content: pageTitle }, + { property: "og:description", content: description }, + { property: "og:url", content: url }, + ], + links: [{ rel: "canonical", href: url }], + }; + }, + component: DocsSlugPage, +}); + +function DocsSlugPage() { + const { path, title, description, eyebrow } = Route.useLoaderData(); + const Content = docsClientLoader.getComponent(path); + return ( + + }> + + + + ); +} diff --git a/apps/loopover-ui/src/routes/docs.ai-summaries.tsx b/apps/loopover-ui/src/routes/docs.ai-summaries.tsx deleted file mode 100644 index fd1e6f42bb..0000000000 --- a/apps/loopover-ui/src/routes/docs.ai-summaries.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/ai-summaries.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/ai-summaries")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["ai-summaries"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "AI summaries — LoopOver docs" }, - { - name: "description", - content: - "How LoopOver uses AI: only over deterministic signals, never as a source of truth, with strict public/private boundaries.", - }, - { property: "og:title", content: "AI summaries — LoopOver docs" }, - { - property: "og:description", - content: - "How LoopOver uses AI: only over deterministic signals, never as a source of truth, with strict public/private boundaries.", - }, - { property: "og:url", content: "/docs/ai-summaries" }, - ], - links: [{ rel: "canonical", href: "/docs/ai-summaries" }], - }), - component: AiSummariesDoc, -}); - -function AiSummariesDoc() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.ams-config-precedence.tsx b/apps/loopover-ui/src/routes/docs.ams-config-precedence.tsx deleted file mode 100644 index 423ab27d41..0000000000 --- a/apps/loopover-ui/src/routes/docs.ams-config-precedence.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/ams-config-precedence.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/ams-config-precedence")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["ams-config-precedence"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Miner config precedence — LoopOver docs" }, - { - name: "description", - content: - "How AMS layers configuration across per-repo goal spec, operator env, CLI flags, and operator policy files -- the order each concern actually implements today.", - }, - { property: "og:title", content: "Miner config precedence — LoopOver docs" }, - { - property: "og:description", - content: - "How AMS layers configuration across per-repo goal spec, operator env, CLI flags, and operator policy files -- the order each concern actually implements today.", - }, - { property: "og:url", content: "/docs/ams-config-precedence" }, - ], - links: [{ rel: "canonical", href: "/docs/ams-config-precedence" }], - }), - component: AmsConfigPrecedence, -}); - -function AmsConfigPrecedence() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.ams-deployment.tsx b/apps/loopover-ui/src/routes/docs.ams-deployment.tsx deleted file mode 100644 index 3ad1815922..0000000000 --- a/apps/loopover-ui/src/routes/docs.ams-deployment.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/ams-deployment.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/ams-deployment")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["ams-deployment"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "AMS deployment guide — LoopOver docs" }, - { - name: "description", - content: - "Deploy @loopover/miner in laptop mode (single machine, zero Docker) or fleet mode (containerized workers) — both 100% client-side, credentials never baked into images.", - }, - { property: "og:title", content: "AMS deployment guide — LoopOver docs" }, - { - property: "og:description", - content: - "Deploy @loopover/miner in laptop mode (single machine, zero Docker) or fleet mode (containerized workers) — both 100% client-side, credentials never baked into images.", - }, - { property: "og:url", content: "/docs/ams-deployment" }, - ], - links: [{ rel: "canonical", href: "/docs/ams-deployment" }], - }), - component: AmsDeployment, -}); - -function AmsDeployment() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.ams-discovery-plane.tsx b/apps/loopover-ui/src/routes/docs.ams-discovery-plane.tsx deleted file mode 100644 index 892d5c9959..0000000000 --- a/apps/loopover-ui/src/routes/docs.ams-discovery-plane.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/ams-discovery-plane.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/ams-discovery-plane")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["ams-discovery-plane"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Hosted discovery plane — LoopOver docs" }, - { - name: "description", - content: - "How a loopover-miner instance opts into the optional hosted discovery-index plane, what it may send, and what never leaves the operator's machine.", - }, - { property: "og:title", content: "Hosted discovery plane — LoopOver docs" }, - { - property: "og:description", - content: - "How a loopover-miner instance opts into the optional hosted discovery-index plane, what it may send, and what never leaves the operator's machine.", - }, - { property: "og:url", content: "/docs/ams-discovery-plane" }, - ], - links: [{ rel: "canonical", href: "/docs/ams-discovery-plane" }], - }), - component: AmsDiscoveryPlane, -}); - -function AmsDiscoveryPlane() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.ams-env-reference.tsx b/apps/loopover-ui/src/routes/docs.ams-env-reference.tsx deleted file mode 100644 index b7d3d69f92..0000000000 --- a/apps/loopover-ui/src/routes/docs.ams-env-reference.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/ams-env-reference.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock primitives -- -// not fumadocs-ui's bundled components. See docs-source.server.ts's comment for why the -// loader below resolves only a plain, serializable path string. The .mdx content -// itself imports AMS_ENV_REFERENCE_MARKDOWN from @/lib/ams-env-reference (generated by -// packages/loopover-miner/scripts/generate-env-reference.mjs, drift-checked in CI), -// the same pattern docs.self-hosting-configuration.tsx uses for -// SELFHOST_ENV_REFERENCE_MARKDOWN. -export const Route = createFileRoute("/docs/ams-env-reference")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["ams-env-reference"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "AMS environment variable reference — LoopOver docs" }, - { - name: "description", - content: - "Every LOOPOVER_MINER_* / MINER_* env var the miner reads, where it's first referenced in code, and its default -- generated from source, never hand-copied.", - }, - { property: "og:title", content: "AMS environment variable reference — LoopOver docs" }, - { - property: "og:description", - content: - "Every LOOPOVER_MINER_* / MINER_* env var the miner reads, where it's first referenced in code, and its default -- generated from source, never hand-copied.", - }, - { property: "og:url", content: "/docs/ams-env-reference" }, - ], - links: [{ rel: "canonical", href: "/docs/ams-env-reference" }], - }), - component: AmsEnvReference, -}); - -function AmsEnvReference() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.ams-fleet-manifest.tsx b/apps/loopover-ui/src/routes/docs.ams-fleet-manifest.tsx deleted file mode 100644 index 49d5c9c4cd..0000000000 --- a/apps/loopover-ui/src/routes/docs.ams-fleet-manifest.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/ams-fleet-manifest.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/ams-fleet-manifest")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["ams-fleet-manifest"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Fleet run-manifest — LoopOver docs" }, - { - name: "description", - content: - "The top-level config a fleet operator authors to run the miner across many repos at once -- which repos are in scope and how the worktree/concurrency budget is split.", - }, - { property: "og:title", content: "Fleet run-manifest — LoopOver docs" }, - { - property: "og:description", - content: - "The top-level config a fleet operator authors to run the miner across many repos at once -- which repos are in scope and how the worktree/concurrency budget is split.", - }, - { property: "og:url", content: "/docs/ams-fleet-manifest" }, - ], - links: [{ rel: "canonical", href: "/docs/ams-fleet-manifest" }], - }), - component: AmsFleetManifest, -}); - -function AmsFleetManifest() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.ams-goal-spec.tsx b/apps/loopover-ui/src/routes/docs.ams-goal-spec.tsx deleted file mode 100644 index 1a8ecfbec9..0000000000 --- a/apps/loopover-ui/src/routes/docs.ams-goal-spec.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/ams-goal-spec.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/ams-goal-spec")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["ams-goal-spec"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "MinerGoalSpec (.loopover-miner.yml) — LoopOver docs" }, - { - name: "description", - content: - "Per-repo configuration telling an autonomous LoopOver miner what to look for and how to behave when targeting a repo -- the full field reference.", - }, - { property: "og:title", content: "MinerGoalSpec (.loopover-miner.yml) — LoopOver docs" }, - { - property: "og:description", - content: - "Per-repo configuration telling an autonomous LoopOver miner what to look for and how to behave when targeting a repo -- the full field reference.", - }, - { property: "og:url", content: "/docs/ams-goal-spec" }, - ], - links: [{ rel: "canonical", href: "/docs/ams-goal-spec" }], - }), - component: AmsGoalSpec, -}); - -function AmsGoalSpec() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.ams-kill-switch-incident.tsx b/apps/loopover-ui/src/routes/docs.ams-kill-switch-incident.tsx deleted file mode 100644 index eebc018cae..0000000000 --- a/apps/loopover-ui/src/routes/docs.ams-kill-switch-incident.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/ams-kill-switch-incident.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/ams-kill-switch-incident")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["ams-kill-switch-incident"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Kill-switch incident runbook — LoopOver docs" }, - { - name: "description", - content: - "Detect, activate, and audit a misbehaving Rent-a-Loop miner after the kill-switch stops it.", - }, - { property: "og:title", content: "Kill-switch incident runbook — LoopOver docs" }, - { - property: "og:description", - content: - "Detect, activate, and audit a misbehaving Rent-a-Loop miner after the kill-switch stops it.", - }, - { property: "og:url", content: "/docs/ams-kill-switch-incident" }, - ], - links: [{ rel: "canonical", href: "/docs/ams-kill-switch-incident" }], - }), - component: AmsKillSwitchIncident, -}); - -function AmsKillSwitchIncident() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.ams-observability-callout.test.tsx b/apps/loopover-ui/src/routes/docs.ams-observability-callout.test.tsx index 37d0803de0..18b9a9c94c 100644 --- a/apps/loopover-ui/src/routes/docs.ams-observability-callout.test.tsx +++ b/apps/loopover-ui/src/routes/docs.ams-observability-callout.test.tsx @@ -3,12 +3,28 @@ import { render, screen } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; import type { ReactNode } from "react"; +// Interpolates $-params the same way the real router resolves `to`/`params` into a concrete href, +// so this stub's rendered link stays a faithful stand-in after #8151 moved the callout's Link from a +// plain string `to` to the typed `to="/docs/$slug" params={{ slug }}` form every docs link now uses. vi.mock("@tanstack/react-router", () => ({ - Link: ({ to, children }: { to: string; children: ReactNode }) => {children}, + Link: ({ + to, + params, + children, + }: { + to: string; + params?: Record; + children: ReactNode; + }) => { + const href = params + ? to.replace(/\$([a-zA-Z0-9_]+)/g, (_match, name: string) => params[name] ?? `$${name}`) + : to; + return {children}; + }, })); import { - AMS_OBSERVABILITY_DOC_URL, + AMS_OBSERVABILITY_DOC_SLUG, AmsObservabilityCallout, } from "../components/site/ams-observability-callout"; @@ -27,12 +43,12 @@ describe("AMS observability cross-reference callout", () => { it("renders a link to the Observing your miner guide", () => { render(); const link = screen.getByRole("link", { name: "Observing your miner" }); - expect(link.getAttribute("href")).toBe(AMS_OBSERVABILITY_DOC_URL); + expect(link.getAttribute("href")).toBe(`/docs/${AMS_OBSERVABILITY_DOC_SLUG}`); }); - it("targets a well-formed, non-empty in-app docs path (guards against a blank/copy-paste link)", () => { - expect(AMS_OBSERVABILITY_DOC_URL).toBeTruthy(); - expect(AMS_OBSERVABILITY_DOC_URL.startsWith("/docs/")).toBe(true); + it("targets a well-formed, non-empty in-app docs slug (guards against a blank/copy-paste link)", () => { + expect(AMS_OBSERVABILITY_DOC_SLUG).toBeTruthy(); + expect(AMS_OBSERVABILITY_DOC_SLUG.startsWith("/")).toBe(false); }); it.each(ROUTES_WITH_CALLOUT)("wires the callout into %s", (_path, docPath) => { diff --git a/apps/loopover-ui/src/routes/docs.ams-observability.tsx b/apps/loopover-ui/src/routes/docs.ams-observability.tsx deleted file mode 100644 index 708610118a..0000000000 --- a/apps/loopover-ui/src/routes/docs.ams-observability.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/ams-observability.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/ams-observability")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["ams-observability"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Observing your miner — LoopOver docs" }, - { - name: "description", - content: - "Point Grafana at redacted AMS reporting exports to see attempt and prediction history without exposing the miner's live local ledgers.", - }, - { property: "og:title", content: "Observing your miner — LoopOver docs" }, - { - property: "og:description", - content: - "Point Grafana at redacted AMS reporting exports to see attempt and prediction history without exposing the miner's live local ledgers.", - }, - { property: "og:url", content: "/docs/ams-observability" }, - ], - links: [{ rel: "canonical", href: "/docs/ams-observability" }], - }), - component: AmsObservability, -}); - -function AmsObservability() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.ams-operations-runbook.tsx b/apps/loopover-ui/src/routes/docs.ams-operations-runbook.tsx deleted file mode 100644 index afc981a334..0000000000 --- a/apps/loopover-ui/src/routes/docs.ams-operations-runbook.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/ams-operations-runbook.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/ams-operations-runbook")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["ams-operations-runbook"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "AMS operations runbook — LoopOver docs" }, - { - name: "description", - content: - "Recover from SQLite lock contention, ledger corruption, and post-upgrade schema migrations in loopover-miner's local state.", - }, - { property: "og:title", content: "AMS operations runbook — LoopOver docs" }, - { - property: "og:description", - content: - "Recover from SQLite lock contention, ledger corruption, and post-upgrade schema migrations in loopover-miner's local state.", - }, - { property: "og:url", content: "/docs/ams-operations-runbook" }, - ], - links: [{ rel: "canonical", href: "/docs/ams-operations-runbook" }], - }), - component: AmsOperationsRunbook, -}); - -function AmsOperationsRunbook() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.ams-sizing.tsx b/apps/loopover-ui/src/routes/docs.ams-sizing.tsx deleted file mode 100644 index 650863582a..0000000000 --- a/apps/loopover-ui/src/routes/docs.ams-sizing.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/ams-sizing.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/ams-sizing")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["ams-sizing"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Resource sizing — LoopOver docs" }, - { - name: "description", - content: - "Real, measured CPU/RAM/disk numbers for laptop mode and fleet mode, so an operator can size a host or cluster from data instead of guessing.", - }, - { property: "og:title", content: "Resource sizing — LoopOver docs" }, - { - property: "og:description", - content: - "Real, measured CPU/RAM/disk numbers for laptop mode and fleet mode, so an operator can size a host or cluster from data instead of guessing.", - }, - { property: "og:url", content: "/docs/ams-sizing" }, - ], - links: [{ rel: "canonical", href: "/docs/ams-sizing" }], - }), - component: AmsSizing, -}); - -function AmsSizing() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.ams-unattended-scheduling.tsx b/apps/loopover-ui/src/routes/docs.ams-unattended-scheduling.tsx deleted file mode 100644 index 0fdabeb76f..0000000000 --- a/apps/loopover-ui/src/routes/docs.ams-unattended-scheduling.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/ams-unattended-scheduling.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/ams-unattended-scheduling")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["ams-unattended-scheduling"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Unattended scheduling & failure alerting — LoopOver docs" }, - { - name: "description", - content: - "Run the miner's scheduled commands -- manage poll and discover -- unattended on cron or systemd, and alert reliably when a run fails.", - }, - { property: "og:title", content: "Unattended scheduling & failure alerting — LoopOver docs" }, - { - property: "og:description", - content: - "Run the miner's scheduled commands -- manage poll and discover -- unattended on cron or systemd, and alert reliably when a run fails.", - }, - { property: "og:url", content: "/docs/ams-unattended-scheduling" }, - ], - links: [{ rel: "canonical", href: "/docs/ams-unattended-scheduling" }], - }), - component: AmsUnattendedScheduling, -}); - -function AmsUnattendedScheduling() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.backtest-calibration.tsx b/apps/loopover-ui/src/routes/docs.backtest-calibration.tsx deleted file mode 100644 index 5092800d44..0000000000 --- a/apps/loopover-ui/src/routes/docs.backtest-calibration.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/backtest-calibration.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock primitives -- not fumadocs-ui's -// bundled components. See docs-source.server.ts's comment for why the loader below resolves only a -// plain, serializable path string. -export const Route = createFileRoute("/docs/backtest-calibration")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["backtest-calibration"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Backtest & calibration — LoopOver docs" }, - { - name: "description", - content: - "How LoopOver measures whether its own review rules are right, and backtests threshold and logic changes against real recorded history before they ship.", - }, - { property: "og:title", content: "Backtest & calibration — LoopOver docs" }, - { - property: "og:description", - content: - "How LoopOver measures whether its own review rules are right, and backtests threshold and logic changes against real recorded history before they ship.", - }, - { property: "og:url", content: "/docs/backtest-calibration" }, - ], - links: [{ rel: "canonical", href: "/docs/backtest-calibration" }], - }), - component: BacktestCalibrationDoc, -}); - -function BacktestCalibrationDoc() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.beta-onboarding.tsx b/apps/loopover-ui/src/routes/docs.beta-onboarding.tsx deleted file mode 100644 index 050ba07e17..0000000000 --- a/apps/loopover-ui/src/routes/docs.beta-onboarding.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/beta-onboarding.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/beta-onboarding")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["beta-onboarding"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Beta onboarding — LoopOver docs" }, - { - name: "description", - content: - "Role-based beta paths for miners, maintainers, repo owners, and operators — first useful action, not just API reference.", - }, - { property: "og:title", content: "Beta onboarding — LoopOver docs" }, - { - property: "og:description", - content: - "Role-based beta paths for miners, maintainers, repo owners, and operators — first useful action, not just API reference.", - }, - { property: "og:url", content: "/docs/beta-onboarding" }, - ], - links: [{ rel: "canonical", href: "/docs/beta-onboarding" }], - }), - component: BetaOnboarding, -}); - -function BetaOnboarding() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.branch-analysis.tsx b/apps/loopover-ui/src/routes/docs.branch-analysis.tsx deleted file mode 100644 index bfbdd4dfb5..0000000000 --- a/apps/loopover-ui/src/routes/docs.branch-analysis.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/branch-analysis.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/branch-analysis")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["branch-analysis"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Branch analysis — LoopOver docs" }, - { - name: "description", - content: - "Metadata-only analysis of a branch. Inputs, outputs, and the privacy boundary explained.", - }, - { property: "og:title", content: "Branch analysis — LoopOver docs" }, - { - property: "og:description", - content: - "Metadata-only analysis of a branch. Inputs, outputs, and the privacy boundary explained.", - }, - { property: "og:url", content: "/docs/branch-analysis" }, - ], - links: [{ rel: "canonical", href: "/docs/branch-analysis" }], - }), - component: BranchAnalysis, -}); - -function BranchAnalysis() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.capacity.tsx b/apps/loopover-ui/src/routes/docs.capacity.tsx deleted file mode 100644 index 89d5e6986c..0000000000 --- a/apps/loopover-ui/src/routes/docs.capacity.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/capacity.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/capacity")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["capacity"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Capacity and throughput — LoopOver docs" }, - { - name: "description", - content: - "Real throughput and concurrency numbers for AMS's iterate-loop and the review-gate's PR-processing queue, so an operator can plan for load instead of guessing.", - }, - { property: "og:title", content: "Capacity and throughput — LoopOver docs" }, - { - property: "og:description", - content: - "Real throughput and concurrency numbers for AMS's iterate-loop and the review-gate's PR-processing queue, so an operator can plan for load instead of guessing.", - }, - { property: "og:url", content: "/docs/capacity" }, - ], - links: [{ rel: "canonical", href: "/docs/capacity" }], - }), - component: Capacity, -}); - -function Capacity() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.federated-fleet-intelligence.tsx b/apps/loopover-ui/src/routes/docs.federated-fleet-intelligence.tsx deleted file mode 100644 index 7460840cc8..0000000000 --- a/apps/loopover-ui/src/routes/docs.federated-fleet-intelligence.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/federated-fleet-intelligence.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/federated-fleet-intelligence")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["federated-fleet-intelligence"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Federated fleet intelligence — LoopOver docs" }, - { - name: "description", - content: - "Opt-in sharing of anonymized gate-calibration aggregates between self-hosted LoopOver instances: what is shared, how consent works, and how trust is gated.", - }, - { property: "og:title", content: "Federated fleet intelligence — LoopOver docs" }, - { - property: "og:description", - content: - "Opt-in sharing of anonymized gate-calibration aggregates between self-hosted LoopOver instances: what is shared, how consent works, and how trust is gated.", - }, - { property: "og:url", content: "/docs/federated-fleet-intelligence" }, - ], - links: [{ rel: "canonical", href: "/docs/federated-fleet-intelligence" }], - }), - component: FederatedFleetIntelligence, -}); - -function FederatedFleetIntelligence() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.github-app.tsx b/apps/loopover-ui/src/routes/docs.github-app.tsx deleted file mode 100644 index b33b55a742..0000000000 --- a/apps/loopover-ui/src/routes/docs.github-app.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/github-app.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/github-app")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["github-app"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "GitHub App configuration — LoopOver docs" }, - { - name: "description", - content: - "How the LoopOver GitHub App reviews pull requests once installed. Self-hosting is the only currently available path; a shared, centrally hosted App is planned as a future offering. The LoopOver Orb Review Agent check plus a review comment posted as loopover[bot]. Choose repos, configure sticky PR panels, advisory checks, and optional review-agent enforcement.", - }, - { property: "og:title", content: "GitHub App configuration — LoopOver docs" }, - { - property: "og:description", - content: - "How the LoopOver GitHub App reviews pull requests once installed. Self-hosting is the only currently available path; a shared, centrally hosted App is planned as a future offering. Choose repos, configure sticky PR panels, advisory checks, and optional review-agent enforcement.", - }, - { property: "og:url", content: "/docs/github-app" }, - ], - links: [{ rel: "canonical", href: "/docs/github-app" }], - }), - component: GithubApp, -}); - -function GithubApp() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.how-reviews-work.tsx b/apps/loopover-ui/src/routes/docs.how-reviews-work.tsx deleted file mode 100644 index 8e060ed183..0000000000 --- a/apps/loopover-ui/src/routes/docs.how-reviews-work.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/how-reviews-work.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock primitives -- -// not fumadocs-ui's bundled components. See docs-source.server.ts's comment for why the -// loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/how-reviews-work")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["how-reviews-work"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "How reviews work — LoopOver docs" }, - { - name: "description", - content: - "How LoopOver reviews a pull request: the deterministic gate, the dual-AI review and consensus, the unified review comment, and the signals behind a verdict.", - }, - { property: "og:title", content: "How reviews work — LoopOver docs" }, - { - property: "og:description", - content: - "How LoopOver reviews a pull request: the deterministic gate, the dual-AI review and consensus, the unified review comment, and the signals behind a verdict.", - }, - { property: "og:url", content: "/docs/how-reviews-work" }, - ], - links: [{ rel: "canonical", href: "/docs/how-reviews-work" }], - }), - component: HowReviewsWork, -}); - -function HowReviewsWork() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.index.tsx b/apps/loopover-ui/src/routes/docs.index.tsx index 26b8985dbd..7faddfce4d 100644 --- a/apps/loopover-ui/src/routes/docs.index.tsx +++ b/apps/loopover-ui/src/routes/docs.index.tsx @@ -141,7 +141,8 @@ function AudienceCard({ icon, title, description, primary, links }: Audience) {

    {title}

    {description}

    {primary.label} @@ -151,7 +152,8 @@ function AudienceCard({ icon, title, description, primary, links }: Audience) { {links.map((l) => (
  • diff --git a/apps/loopover-ui/src/routes/docs.loopover-commands.tsx b/apps/loopover-ui/src/routes/docs.loopover-commands.tsx deleted file mode 100644 index fb5d40fde0..0000000000 --- a/apps/loopover-ui/src/routes/docs.loopover-commands.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/loopover-commands.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/CommandTable -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/loopover-commands")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["loopover-commands"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "@loopover command reference — LoopOver docs" }, - { - name: "description", - content: - "Every @loopover PR and issue comment command: syntax, default authorization roles, and the hard boundary between auto-review and the one-shot gate.", - }, - { property: "og:title", content: "@loopover command reference — LoopOver docs" }, - { - property: "og:description", - content: - "Every @loopover PR and issue comment command: syntax, default authorization roles, and the hard boundary between auto-review and the one-shot gate.", - }, - { property: "og:url", content: "/docs/loopover-commands" }, - ], - links: [{ rel: "canonical", href: "/docs/loopover-commands" }], - }), - component: LoopOverCommandsReference, -}); - -function LoopOverCommandsReference() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.maintainer-install-trust.tsx b/apps/loopover-ui/src/routes/docs.maintainer-install-trust.tsx deleted file mode 100644 index 32295e3173..0000000000 --- a/apps/loopover-ui/src/routes/docs.maintainer-install-trust.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/maintainer-install-trust.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow primitives -- -// not fumadocs-ui's bundled components. See docs-source.server.ts's comment for why the loader below -// resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/maintainer-install-trust")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["maintainer-install-trust"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Maintainer install and trust guide — LoopOver docs" }, - { - name: "description", - content: - "Self-host and install a LoopOver GitHub App as a maintainer, verify trust boundaries, preview public output, and decide when GitHub App checks are safe to enable. Self-hosting is the only currently available path.", - }, - { - property: "og:title", - content: "Maintainer install and trust guide — LoopOver docs", - }, - { - property: "og:description", - content: - "Self-host and install a LoopOver GitHub App as a maintainer, verify trust boundaries, preview public output, and decide when GitHub App checks are safe to enable. Self-hosting is the only currently available path.", - }, - { property: "og:url", content: "/docs/maintainer-install-trust" }, - ], - links: [{ rel: "canonical", href: "/docs/maintainer-install-trust" }], - }), - component: MaintainerInstallTrust, -}); - -function MaintainerInstallTrust() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.maintainer-self-hosting.tsx b/apps/loopover-ui/src/routes/docs.maintainer-self-hosting.tsx deleted file mode 100644 index 3c2884a048..0000000000 --- a/apps/loopover-ui/src/routes/docs.maintainer-self-hosting.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/maintainer-self-hosting.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/FeatureRow primitives -- not -// fumadocs-ui's bundled components. See docs-source.server.ts's comment for why the loader below -// resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/maintainer-self-hosting")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["maintainer-self-hosting"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Self-hosted reviews — LoopOver docs" }, - { - name: "description", - content: - "A maintainer guide to self-hosting the LoopOver review service, with dedicated pages for setup, configuration, AI, REES, RAG, operations, releases, security, and troubleshooting.", - }, - { property: "og:title", content: "Self-hosted reviews — LoopOver docs" }, - { - property: "og:description", - content: - "A maintainer guide to self-hosting the LoopOver review service, with dedicated pages for setup, configuration, AI, REES, RAG, operations, releases, security, and troubleshooting.", - }, - { property: "og:url", content: "/docs/maintainer-self-hosting" }, - ], - links: [{ rel: "canonical", href: "/docs/maintainer-self-hosting" }], - }), - component: MaintainerSelfHosting, -}); - -function MaintainerSelfHosting() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.maintainer-workflow.tsx b/apps/loopover-ui/src/routes/docs.maintainer-workflow.tsx deleted file mode 100644 index 4e1ed6c2df..0000000000 --- a/apps/loopover-ui/src/routes/docs.maintainer-workflow.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/maintainer-workflow.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/WorkflowMirror -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment for -// why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/maintainer-workflow")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["maintainer-workflow"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Maintainer workflow — LoopOver docs" }, - { - name: "description", - content: - "How to use LoopOver in a repo: confirmed-miner labels, sticky sanitized comments, on-demand @loopover commands.", - }, - { property: "og:title", content: "Maintainer workflow — LoopOver docs" }, - { - property: "og:description", - content: - "How to use LoopOver in a repo: confirmed-miner labels, sticky sanitized comments, on-demand @loopover commands.", - }, - { property: "og:url", content: "/docs/maintainer-workflow" }, - ], - links: [{ rel: "canonical", href: "/docs/maintainer-workflow" }], - }), - component: MaintainerWorkflow, -}); - -function MaintainerWorkflow() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.mcp-clients.tsx b/apps/loopover-ui/src/routes/docs.mcp-clients.tsx deleted file mode 100644 index 735618f4af..0000000000 --- a/apps/loopover-ui/src/routes/docs.mcp-clients.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/mcp-clients.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/mcp-clients")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["mcp-clients"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "MCP client setup — LoopOver docs" }, - { - name: "description", - content: - "Wire the LoopOver MCP into Codex, Claude Desktop, Cursor, or any MCP-aware client over stdio or remote.", - }, - { property: "og:title", content: "MCP client setup — LoopOver docs" }, - { - property: "og:description", - content: - "Wire the LoopOver MCP into Codex, Claude Desktop, Cursor, or any MCP-aware client over stdio or remote.", - }, - { property: "og:url", content: "/docs/mcp-clients" }, - ], - links: [{ rel: "canonical", href: "/docs/mcp-clients" }], - }), - component: McpClients, -}); - -function McpClients() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.miner-quickstart.tsx b/apps/loopover-ui/src/routes/docs.miner-quickstart.tsx deleted file mode 100644 index b07beb178e..0000000000 --- a/apps/loopover-ui/src/routes/docs.miner-quickstart.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/miner-quickstart.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/AmsObservabilityCallout -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/miner-quickstart")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["miner-quickstart"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Miner quickstart by lane — LoopOver docs" }, - { - name: "description", - content: - "Pick a contribution lane, install @loopover/mcp, sign in, and run plan → preflight → packet. Lane-by-lane commands with JSON output and redaction notes.", - }, - { property: "og:title", content: "Miner quickstart by lane — LoopOver docs" }, - { - property: "og:description", - content: - "Pick a contribution lane, install @loopover/mcp, sign in, and run plan → preflight → packet. Lane-by-lane commands with JSON output and redaction notes.", - }, - { property: "og:url", content: "/docs/miner-quickstart" }, - ], - links: [{ rel: "canonical", href: "/docs/miner-quickstart" }], - }), - component: MinerQuickstart, -}); - -export function MinerQuickstart() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.miner-workflow.tsx b/apps/loopover-ui/src/routes/docs.miner-workflow.tsx deleted file mode 100644 index 9efb6c42df..0000000000 --- a/apps/loopover-ui/src/routes/docs.miner-workflow.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/miner-workflow.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/WorkflowMirror/ -// AmsObservabilityCallout primitives -- not fumadocs-ui's bundled components. See -// docs-source.server.ts's comment for why the loader below resolves only a plain, -// serializable path string. -export const Route = createFileRoute("/docs/miner-workflow")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["miner-workflow"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Miner workflow — LoopOver docs" }, - { - name: "description", - content: "Plan → analyze → preflight → packet. The four-step miner loop with the MCP CLI.", - }, - { property: "og:title", content: "Miner workflow — LoopOver docs" }, - { - property: "og:description", - content: "Plan → analyze → preflight → packet. The four-step miner loop with the MCP CLI.", - }, - { property: "og:url", content: "/docs/miner-workflow" }, - ], - links: [{ rel: "canonical", href: "/docs/miner-workflow" }], - }), - component: MinerWorkflow, -}); - -export function MinerWorkflow() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.owner-checklist.tsx b/apps/loopover-ui/src/routes/docs.owner-checklist.tsx deleted file mode 100644 index 83899c77e9..0000000000 --- a/apps/loopover-ui/src/routes/docs.owner-checklist.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/owner-checklist.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock primitives -- -// not fumadocs-ui's bundled components. See docs-source.server.ts's comment for why the -// loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/owner-checklist")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["owner-checklist"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Repo-owner onboarding checklist — LoopOver docs" }, - { - name: "description", - content: - "A pre-flight checklist for repo owners: registration, config quality, labels, issue quality, contribution lanes, validation, maintainer capacity, and the public/private boundary — with honest tradeoffs.", - }, - { property: "og:title", content: "Repo-owner onboarding checklist — LoopOver docs" }, - { - property: "og:description", - content: - "A pre-flight checklist for repo owners: registration, config quality, labels, issue quality, contribution lanes, validation, maintainer capacity, and the public/private boundary — with honest tradeoffs.", - }, - { property: "og:url", content: "/docs/owner-checklist" }, - ], - links: [{ rel: "canonical", href: "/docs/owner-checklist" }], - }), - component: OwnerChecklist, -}); - -function OwnerChecklist() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.privacy-security.tsx b/apps/loopover-ui/src/routes/docs.privacy-security.tsx deleted file mode 100644 index 8f5ae53556..0000000000 --- a/apps/loopover-ui/src/routes/docs.privacy-security.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/privacy-security.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock primitives -- -// not fumadocs-ui's bundled components. See docs-source.server.ts's comment for why the -// loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/privacy-security")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["privacy-security"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Privacy & security — LoopOver docs" }, - { - name: "description", - content: - "LoopOver's privacy posture: metadata-only MCP, no PATs, no wallet, no source upload, sanitized public output.", - }, - { property: "og:title", content: "Privacy & security — LoopOver docs" }, - { - property: "og:description", - content: - "LoopOver's privacy posture: metadata-only MCP, no PATs, no wallet, no source upload, sanitized public output.", - }, - { property: "og:url", content: "/docs/privacy-security" }, - ], - links: [{ rel: "canonical", href: "/docs/privacy-security" }], - }), - component: PrivacySecurity, -}); - -function PrivacySecurity() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.quickstart.tsx b/apps/loopover-ui/src/routes/docs.quickstart.tsx deleted file mode 100644 index 5bec72642a..0000000000 --- a/apps/loopover-ui/src/routes/docs.quickstart.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/quickstart.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock primitives -- -// not fumadocs-ui's bundled components. See docs-source.server.ts's comment for why the -// loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/quickstart")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["quickstart"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Quickstart — LoopOver docs" }, - { - name: "description", - content: - "Install @loopover/mcp, sign in with GitHub Device Flow, and analyze your branch in two commands.", - }, - { property: "og:title", content: "Quickstart — LoopOver docs" }, - { - property: "og:description", - content: - "Install @loopover/mcp, sign in with GitHub Device Flow, and analyze your branch in two commands.", - }, - { property: "og:url", content: "/docs/quickstart" }, - ], - links: [{ rel: "canonical", href: "/docs/quickstart" }], - }), - component: Quickstart, -}); - -function Quickstart() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.scoreability.tsx b/apps/loopover-ui/src/routes/docs.scoreability.tsx deleted file mode 100644 index 602703a2d6..0000000000 --- a/apps/loopover-ui/src/routes/docs.scoreability.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/scoreability.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock primitives -- -// not fumadocs-ui's bundled components. See docs-source.server.ts's comment for why the -// loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/scoreability")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["scoreability"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Scoreability — LoopOver docs" }, - { - name: "description", - content: - "Scoreability scenarios explained: current gated, underlying potential, clean-gate, after-pending-merges, linked-issue-fixed, best-reasonable. Estimates only.", - }, - { property: "og:title", content: "Scoreability — LoopOver docs" }, - { - property: "og:description", - content: - "Scoreability scenarios explained: current gated, underlying potential, clean-gate, after-pending-merges, linked-issue-fixed, best-reasonable. Estimates only.", - }, - { property: "og:url", content: "/docs/scoreability" }, - ], - links: [{ rel: "canonical", href: "/docs/scoreability" }], - }), - component: Scoreability, -}); - -function Scoreability() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.self-hosting-ai-providers.tsx b/apps/loopover-ui/src/routes/docs.self-hosting-ai-providers.tsx deleted file mode 100644 index 2fe389ae10..0000000000 --- a/apps/loopover-ui/src/routes/docs.self-hosting-ai-providers.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/self-hosting-ai-providers.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock primitives -- -// not fumadocs-ui's bundled components. See docs-source.server.ts's comment for why the -// loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/self-hosting-ai-providers")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["self-hosting-ai-providers"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Self-host AI providers — LoopOver docs" }, - { - name: "description", - content: - "Configure AI providers for self-hosted LoopOver reviews, including Anthropic, OpenAI-compatible endpoints, Ollama, Claude Code, and Codex.", - }, - { property: "og:title", content: "Self-host AI providers — LoopOver docs" }, - { - property: "og:description", - content: - "Configure AI providers for self-hosted LoopOver reviews, including Anthropic, OpenAI-compatible endpoints, Ollama, Claude Code, and Codex.", - }, - { property: "og:url", content: "/docs/self-hosting-ai-providers" }, - ], - links: [{ rel: "canonical", href: "/docs/self-hosting-ai-providers" }], - }), - component: SelfHostingAiProviders, -}); - -function SelfHostingAiProviders() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.self-hosting-backup-scaling.tsx b/apps/loopover-ui/src/routes/docs.self-hosting-backup-scaling.tsx deleted file mode 100644 index 082aa018ce..0000000000 --- a/apps/loopover-ui/src/routes/docs.self-hosting-backup-scaling.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/self-hosting-backup-scaling.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock primitives -- -// not fumadocs-ui's bundled components. See docs-source.server.ts's comment for why the -// loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/self-hosting-backup-scaling")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["self-hosting-backup-scaling"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Self-host backup and scaling — LoopOver docs" }, - { - name: "description", - content: - "Back up and scale the self-hosted LoopOver review service with SQLite, Litestream, Postgres, Redis, and restore checks.", - }, - { property: "og:title", content: "Self-host backup and scaling — LoopOver docs" }, - { - property: "og:description", - content: - "Back up and scale the self-hosted LoopOver review service with SQLite, Litestream, Postgres, Redis, and restore checks.", - }, - { property: "og:url", content: "/docs/self-hosting-backup-scaling" }, - ], - links: [{ rel: "canonical", href: "/docs/self-hosting-backup-scaling" }], - }), - component: SelfHostingBackupScaling, -}); - -function SelfHostingBackupScaling() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.self-hosting-configuration.tsx b/apps/loopover-ui/src/routes/docs.self-hosting-configuration.tsx deleted file mode 100644 index 39ef1102a3..0000000000 --- a/apps/loopover-ui/src/routes/docs.self-hosting-configuration.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/self-hosting-configuration.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock primitives -- -// not fumadocs-ui's bundled components. See docs-source.server.ts's comment for why the -// loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/self-hosting-configuration")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["self-hosting-configuration"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Self-host configuration — LoopOver docs" }, - { - name: "description", - content: - "Configure the self-host review service: env vars, private repo config, feature flags, review modes, and safe defaults.", - }, - { property: "og:title", content: "Self-host configuration — LoopOver docs" }, - { - property: "og:description", - content: - "Configure the self-host review service: env vars, private repo config, feature flags, review modes, and safe defaults.", - }, - { property: "og:url", content: "/docs/self-hosting-configuration" }, - ], - links: [{ rel: "canonical", href: "/docs/self-hosting-configuration" }], - }), - component: SelfHostingConfiguration, -}); - -function SelfHostingConfiguration() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.self-hosting-docs-audit.tsx b/apps/loopover-ui/src/routes/docs.self-hosting-docs-audit.tsx deleted file mode 100644 index 2ac40c26ac..0000000000 --- a/apps/loopover-ui/src/routes/docs.self-hosting-docs-audit.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/self-hosting-docs-audit.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock primitives -- -// not fumadocs-ui's bundled components. See docs-source.server.ts's comment for why the -// loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/self-hosting-docs-audit")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["self-hosting-docs-audit"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Self-host docs accuracy audit — LoopOver docs" }, - { - name: "description", - content: - "Checklist mapping self-host website docs to runtime sources of truth — compose defaults, env vars, releases, observability, backup, and drift guards.", - }, - { property: "og:title", content: "Self-host docs accuracy audit — LoopOver docs" }, - { - property: "og:description", - content: - "Checklist mapping self-host website docs to runtime sources of truth — compose defaults, env vars, releases, observability, backup, and drift guards.", - }, - { property: "og:url", content: "/docs/self-hosting-docs-audit" }, - ], - links: [{ rel: "canonical", href: "/docs/self-hosting-docs-audit" }], - }), - component: SelfHostingDocsAudit, -}); - -function SelfHostingDocsAudit() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.self-hosting-github-app.tsx b/apps/loopover-ui/src/routes/docs.self-hosting-github-app.tsx deleted file mode 100644 index c7b91f7323..0000000000 --- a/apps/loopover-ui/src/routes/docs.self-hosting-github-app.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/self-hosting-github-app.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/self-hosting-github-app")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["self-hosting-github-app"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Self-host GitHub App and Orb — LoopOver docs" }, - { - name: "description", - content: - "Connect a self-hosted LoopOver review service to GitHub with your own direct GitHub App (the default, recommended path) or private managed-beta brokered Orb enrollment.", - }, - { property: "og:title", content: "Self-host GitHub App and Orb — LoopOver docs" }, - { - property: "og:description", - content: - "Connect a self-hosted LoopOver review service to GitHub with your own direct GitHub App (the default, recommended path) or private managed-beta brokered Orb enrollment.", - }, - { property: "og:url", content: "/docs/self-hosting-github-app" }, - ], - links: [{ rel: "canonical", href: "/docs/self-hosting-github-app" }], - }), - component: SelfHostingGithubApp, -}); - -function SelfHostingGithubApp() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.self-hosting-operations.tsx b/apps/loopover-ui/src/routes/docs.self-hosting-operations.tsx deleted file mode 100644 index d67ed14a9d..0000000000 --- a/apps/loopover-ui/src/routes/docs.self-hosting-operations.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/self-hosting-operations.mdx via fumadocs-mdx's browser -// entry (docsClientLoader), through the existing DocsPage/Callout/CodeBlock -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/self-hosting-operations")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["self-hosting-operations"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Self-host operations — LoopOver docs" }, - { - name: "description", - content: - "Operate the self-hosted LoopOver review service: readiness, metrics, logs, dashboards, jobs, queues, routine checks, safe updates/rollback, and clean uninstall/decommissioning.", - }, - { property: "og:title", content: "Self-host operations — LoopOver docs" }, - { - property: "og:description", - content: - "Operate the self-hosted LoopOver review service: readiness, metrics, logs, dashboards, jobs, queues, routine checks, safe updates/rollback, and clean uninstall/decommissioning.", - }, - { property: "og:url", content: "/docs/self-hosting-operations" }, - ], - links: [{ rel: "canonical", href: "/docs/self-hosting-operations" }], - }), - component: SelfHostingOperations, -}); - -export function SelfHostingOperations() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.self-hosting-quickstart.tsx b/apps/loopover-ui/src/routes/docs.self-hosting-quickstart.tsx deleted file mode 100644 index 1659969e32..0000000000 --- a/apps/loopover-ui/src/routes/docs.self-hosting-quickstart.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// SPIKE (#6037): rendered from content/docs/self-hosting-quickstart.mdx via fumadocs-mdx's -// browser entry (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. The `loader` below runs server-only and -// resolves nothing but a plain, serializable path string via docs-source.ts's collections/server -// -- never the live MDX component -- which is what avoids the client-bundle crash that a direct -// collections/server import from route-module scope hits (see docs-source.server.ts's comment). Kept at -// the same route/URL as the pre-migration hand-built page to prove visual output is unchanged. -export const Route = createFileRoute("/docs/self-hosting-quickstart")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["self-hosting-quickstart"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Self-hosting quickstart — LoopOver docs" }, - { - name: "description", - content: - "Bring up the LoopOver self-host review service, run readiness checks, and choose the first safe rollout mode.", - }, - { property: "og:title", content: "Self-hosting quickstart — LoopOver docs" }, - { - property: "og:description", - content: - "Bring up the LoopOver self-host review service, run readiness checks, and choose the first safe rollout mode.", - }, - { property: "og:url", content: "/docs/self-hosting-quickstart" }, - ], - links: [{ rel: "canonical", href: "/docs/self-hosting-quickstart" }], - }), - component: SelfHostingQuickstart, -}); - -function SelfHostingQuickstart() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.self-hosting-rag.tsx b/apps/loopover-ui/src/routes/docs.self-hosting-rag.tsx deleted file mode 100644 index fcb1871989..0000000000 --- a/apps/loopover-ui/src/routes/docs.self-hosting-rag.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/self-hosting-rag.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/self-hosting-rag")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["self-hosting-rag"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Self-host RAG indexing — LoopOver docs" }, - { - name: "description", - content: - "Configure retrieval-augmented review context for self-hosted LoopOver with embeddings, Qdrant, indexing jobs, and cold-index behavior.", - }, - { property: "og:title", content: "Self-host RAG indexing — LoopOver docs" }, - { - property: "og:description", - content: - "Configure retrieval-augmented review context for self-hosted LoopOver with embeddings, Qdrant, indexing jobs, and cold-index behavior.", - }, - { property: "og:url", content: "/docs/self-hosting-rag" }, - ], - links: [{ rel: "canonical", href: "/docs/self-hosting-rag" }], - }), - component: SelfHostingRag, -}); - -function SelfHostingRag() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.self-hosting-rees-analyzers.tsx b/apps/loopover-ui/src/routes/docs.self-hosting-rees-analyzers.tsx deleted file mode 100644 index 2399cb6559..0000000000 --- a/apps/loopover-ui/src/routes/docs.self-hosting-rees-analyzers.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/self-hosting-rees-analyzers.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/self-hosting-rees-analyzers")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["self-hosting-rees-analyzers"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "REES analyzer reference — LoopOver docs" }, - { - name: "description", - content: - "Reference for every REES analyzer available to self-hosted LoopOver review engines, including analyzer names, inputs, network behavior, and findings.", - }, - { property: "og:title", content: "REES analyzer reference — LoopOver docs" }, - { - property: "og:description", - content: - "Reference for every REES analyzer available to self-hosted LoopOver review engines, including analyzer names, inputs, network behavior, and findings.", - }, - { property: "og:url", content: "/docs/self-hosting-rees-analyzers" }, - ], - links: [{ rel: "canonical", href: "/docs/self-hosting-rees-analyzers" }], - }), - component: SelfHostingReesAnalyzers, -}); - -function SelfHostingReesAnalyzers() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.self-hosting-rees.tsx b/apps/loopover-ui/src/routes/docs.self-hosting-rees.tsx deleted file mode 100644 index 9169d7e264..0000000000 --- a/apps/loopover-ui/src/routes/docs.self-hosting-rees.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/self-hosting-rees.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/self-hosting-rees")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["self-hosting-rees"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "REES enrichment — LoopOver docs" }, - { - name: "description", - content: - "Configure REES for self-hosted LoopOver reviews, including service auth, analyzer selection, result visibility, and troubleshooting.", - }, - { property: "og:title", content: "REES enrichment — LoopOver docs" }, - { - property: "og:description", - content: - "Configure REES for self-hosted LoopOver reviews, including service auth, analyzer selection, result visibility, and troubleshooting.", - }, - { property: "og:url", content: "/docs/self-hosting-rees" }, - ], - links: [{ rel: "canonical", href: "/docs/self-hosting-rees" }], - }), - component: SelfHostingRees, -}); - -function SelfHostingRees() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.self-hosting-release-checklist.tsx b/apps/loopover-ui/src/routes/docs.self-hosting-release-checklist.tsx deleted file mode 100644 index b39a55c7b6..0000000000 --- a/apps/loopover-ui/src/routes/docs.self-hosting-release-checklist.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/self-hosting-release-checklist.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/self-hosting-release-checklist")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["self-hosting-release-checklist"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "First release checklist — LoopOver docs" }, - { - name: "description", - content: - "Versioning and trigger for the first stable self-host image, the smoke-test matrix (direct App, brokered, air-gapped, each AI provider, SQLite/Postgres, Redis/Qdrant), an image-contents audit, the full-vs-minimal variant decision, and the GitHub Release notes template.", - }, - { property: "og:title", content: "First release checklist — LoopOver docs" }, - { - property: "og:description", - content: - "Versioning, the smoke-test matrix, an image-contents audit, the image-variant decision, and the GitHub Release notes template for the first stable self-host image.", - }, - { property: "og:url", content: "/docs/self-hosting-release-checklist" }, - ], - links: [{ rel: "canonical", href: "/docs/self-hosting-release-checklist" }], - }), - component: SelfHostingReleaseChecklist, -}); - -function SelfHostingReleaseChecklist() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.self-hosting-releases.tsx b/apps/loopover-ui/src/routes/docs.self-hosting-releases.tsx deleted file mode 100644 index 772f6e3b24..0000000000 --- a/apps/loopover-ui/src/routes/docs.self-hosting-releases.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/self-hosting-releases.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/self-hosting-releases")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["self-hosting-releases"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Self-host releases and images — LoopOver docs" }, - { - name: "description", - content: - "Use official LoopOver self-host images, tags, source maps, custom builds, release notes, and upgrade checks.", - }, - { property: "og:title", content: "Self-host releases and images — LoopOver docs" }, - { - property: "og:description", - content: - "Use official LoopOver self-host images, tags, source maps, custom builds, release notes, and upgrade checks.", - }, - { property: "og:url", content: "/docs/self-hosting-releases" }, - ], - links: [{ rel: "canonical", href: "/docs/self-hosting-releases" }], - }), - component: SelfHostingReleases, -}); - -function SelfHostingReleases() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.self-hosting-security.tsx b/apps/loopover-ui/src/routes/docs.self-hosting-security.tsx deleted file mode 100644 index 37d641105f..0000000000 --- a/apps/loopover-ui/src/routes/docs.self-hosting-security.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/self-hosting-security.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/self-hosting-security")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["self-hosting-security"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Self-host security — LoopOver docs" }, - { - name: "description", - content: - "Secure the self-hosted LoopOver review service: secrets, private rules, network exposure, public output boundaries, REES, AI credentials, and observability.", - }, - { property: "og:title", content: "Self-host security — LoopOver docs" }, - { - property: "og:description", - content: - "Secure the self-hosted LoopOver review service: secrets, private rules, network exposure, public output boundaries, REES, AI credentials, and observability.", - }, - { property: "og:url", content: "/docs/self-hosting-security" }, - ], - links: [{ rel: "canonical", href: "/docs/self-hosting-security" }], - }), - component: SelfHostingSecurity, -}); - -function SelfHostingSecurity() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.self-hosting-troubleshooting.tsx b/apps/loopover-ui/src/routes/docs.self-hosting-troubleshooting.tsx deleted file mode 100644 index 87da44cadf..0000000000 --- a/apps/loopover-ui/src/routes/docs.self-hosting-troubleshooting.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/self-hosting-troubleshooting.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/self-hosting-troubleshooting")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["self-hosting-troubleshooting"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Self-host troubleshooting — LoopOver docs" }, - { - name: "description", - content: - "Troubleshoot self-hosted LoopOver reviews: webhook delivery, AI unavailable, REES silent, RAG empty, queue stuck, GitHub rate limits, Qdrant, Orb, AI provider circuit breakers, and readiness failures.", - }, - { property: "og:title", content: "Self-host troubleshooting — LoopOver docs" }, - { - property: "og:description", - content: - "Troubleshoot self-hosted LoopOver reviews: webhook delivery, AI unavailable, REES silent, RAG empty, queue stuck, GitHub rate limits, Qdrant, Orb, AI provider circuit breakers, and readiness failures.", - }, - { property: "og:url", content: "/docs/self-hosting-troubleshooting" }, - ], - links: [{ rel: "canonical", href: "/docs/self-hosting-troubleshooting" }], - }), - component: SelfHostingTroubleshooting, -}); - -function SelfHostingTroubleshooting() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.self-hosting-unified-ams-orb.tsx b/apps/loopover-ui/src/routes/docs.self-hosting-unified-ams-orb.tsx deleted file mode 100644 index b9d1c4497d..0000000000 --- a/apps/loopover-ui/src/routes/docs.self-hosting-unified-ams-orb.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/self-hosting-unified-ams-orb.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock/FeatureRow -// primitives -- not fumadocs-ui's bundled components. See docs-source.server.ts's comment -// for why the loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/self-hosting-unified-ams-orb")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["self-hosting-unified-ams-orb"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Unified ORB + AMS self-host — LoopOver docs" }, - { - name: "description", - content: - "Run ORB (self-hosted PR review) and AMS (the autonomous miner) together on one host — shared state, aligned .env, and working AMS Grafana panels in fleet mode.", - }, - { property: "og:title", content: "Unified ORB + AMS self-host — LoopOver docs" }, - { - property: "og:description", - content: - "Run ORB (self-hosted PR review) and AMS (the autonomous miner) together on one host — shared state, aligned .env, and working AMS Grafana panels in fleet mode.", - }, - { property: "og:url", content: "/docs/self-hosting-unified-ams-orb" }, - ], - links: [{ rel: "canonical", href: "/docs/self-hosting-unified-ams-orb" }], - }), - component: UnifiedAmsOrb, -}); - -function UnifiedAmsOrb() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.troubleshooting.tsx b/apps/loopover-ui/src/routes/docs.troubleshooting.tsx deleted file mode 100644 index 646630ed4b..0000000000 --- a/apps/loopover-ui/src/routes/docs.troubleshooting.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/troubleshooting.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock primitives -- -// not fumadocs-ui's bundled components. See docs-source.server.ts's comment for why the -// loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/troubleshooting")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["troubleshooting"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Troubleshooting — LoopOver docs" }, - { - name: "description", - content: - "Diagnose MCP/CLI issues with doctor, status, and whoami. Common errors and fixes.", - }, - { property: "og:title", content: "Troubleshooting — LoopOver docs" }, - { - property: "og:description", - content: - "Diagnose MCP/CLI issues with doctor, status, and whoami. Common errors and fixes.", - }, - { property: "og:url", content: "/docs/troubleshooting" }, - ], - links: [{ rel: "canonical", href: "/docs/troubleshooting" }], - }), - component: Troubleshooting, -}); - -function Troubleshooting() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.tuning.tsx b/apps/loopover-ui/src/routes/docs.tuning.tsx deleted file mode 100644 index 50c536b5bc..0000000000 --- a/apps/loopover-ui/src/routes/docs.tuning.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/tuning.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock primitives -- -// not fumadocs-ui's bundled components. See docs-source.server.ts's comment for why the -// loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/tuning")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["tuning"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Tuning your reviews — LoopOver docs" }, - { - name: "description", - content: - "Configure LoopOver CI and LoopOver review: gate modes, score thresholds, guardrails, and feature flags via .loopover.yml and repo settings.", - }, - { property: "og:title", content: "Tuning your reviews — LoopOver docs" }, - { - property: "og:description", - content: - "Configure LoopOver CI and LoopOver review: gate modes, score thresholds, guardrails, and feature flags via .loopover.yml and repo settings.", - }, - { property: "og:url", content: "/docs/tuning" }, - ], - links: [{ rel: "canonical", href: "/docs/tuning" }], - }), - component: Tuning, -}); - -function Tuning() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/docs.upstream-drift.tsx b/apps/loopover-ui/src/routes/docs.upstream-drift.tsx deleted file mode 100644 index ad726005d5..0000000000 --- a/apps/loopover-ui/src/routes/docs.upstream-drift.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { createFileRoute, notFound } from "@tanstack/react-router"; -import { Suspense } from "react"; - -import { DocsPage } from "@/components/site/docs-page"; -import { LoadingState } from "@/components/site/state-views"; -import { docsClientLoader } from "@/lib/docs-client-loader"; -import { getDocPage } from "@/lib/docs-source.functions"; - -// Rendered from content/docs/upstream-drift.mdx via fumadocs-mdx's browser entry -// (docsClientLoader), through the existing DocsPage/Callout/CodeBlock primitives -- -// not fumadocs-ui's bundled components. See docs-source.server.ts's comment for why the -// loader below resolves only a plain, serializable path string. -export const Route = createFileRoute("/docs/upstream-drift")({ - loader: async () => { - const page = await getDocPage({ data: { slugs: ["upstream-drift"] } }); - if (!page) throw notFound(); - return page; - }, - head: () => ({ - meta: [ - { title: "Upstream drift — LoopOver docs" }, - { - name: "description", - content: - "LoopOver tracks versioned upstream Gittensor source/ruleset snapshots, hashes semantic payloads, and warns when assumptions drift.", - }, - { property: "og:title", content: "Upstream drift — LoopOver docs" }, - { - property: "og:description", - content: - "LoopOver tracks versioned upstream Gittensor source/ruleset snapshots, hashes semantic payloads, and warns when assumptions drift.", - }, - { property: "og:url", content: "/docs/upstream-drift" }, - ], - links: [{ rel: "canonical", href: "/docs/upstream-drift" }], - }), - component: UpstreamDrift, -}); - -function UpstreamDrift() { - const { path, title, description } = Route.useLoaderData(); - const Content = docsClientLoader.getComponent(path); - return ( - - }> - - - - ); -} diff --git a/apps/loopover-ui/src/routes/index.tsx b/apps/loopover-ui/src/routes/index.tsx index 849f9dcd46..adb00bebd6 100644 --- a/apps/loopover-ui/src/routes/index.tsx +++ b/apps/loopover-ui/src/routes/index.tsx @@ -79,13 +79,15 @@ function Hero() {

    Install MCP → Self-host reviews → @@ -351,7 +353,8 @@ function Boundary() { ))}
Read the privacy posture → @@ -391,29 +394,30 @@ loopover-mcp analyze-branch --login your-login --json`}
    {[ { - to: "/docs/quickstart" as const, + slug: "quickstart", label: "Quickstart", hint: "Install + first analysis", }, { - to: "/docs/mcp-clients" as const, + slug: "mcp-clients", label: "MCP clients", hint: "Codex · Claude · Cursor", }, { - to: "/docs/scoreability" as const, + slug: "scoreability", label: "Scoreability", hint: "How projections work", }, { - to: "/docs/privacy-security" as const, + slug: "privacy-security", label: "Privacy & security", hint: "Public / private boundary", }, ].map((d) => ( -
  • +
  • {d.label} @@ -427,13 +431,21 @@ loopover-mcp analyze-branch --login your-login --json`}
    - + Quickstart → API reference - + Configure Claude / Cursor / Codex
    @@ -642,7 +654,8 @@ function ClosingCta() {
    Install MCP → diff --git a/apps/loopover-ui/src/routes/install.index.tsx b/apps/loopover-ui/src/routes/install.index.tsx index a2c3fabd63..5788f76829 100644 --- a/apps/loopover-ui/src/routes/install.index.tsx +++ b/apps/loopover-ui/src/routes/install.index.tsx @@ -68,14 +68,16 @@ export function InstallPage() {

    Install on GitHub Read the onboarding guide @@ -125,7 +127,8 @@ export function InstallPage() { choose. You confirm the exact scopes on GitHub before the install completes, and you can review or revoke them at any time from your repository's installed-Apps settings. See{" "} privacy & security diff --git a/apps/loopover-ui/src/routes/install.permissions.test.tsx b/apps/loopover-ui/src/routes/install.permissions.test.tsx index 03bbfb8918..59d960282d 100644 --- a/apps/loopover-ui/src/routes/install.permissions.test.tsx +++ b/apps/loopover-ui/src/routes/install.permissions.test.tsx @@ -2,9 +2,25 @@ import { render, screen } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; import type { ReactNode } from "react"; +// Interpolates $-params the same way the real router resolves `to`/`params` into a concrete href +// (#8151 moved every docs link from a plain string `to` to the typed `to="/docs/$slug" params={{ slug }}` +// form), so this stub's rendered link stays a faithful stand-in. vi.mock("@tanstack/react-router", () => ({ createFileRoute: () => () => ({}), - Link: ({ to, children }: { to: string; children: ReactNode }) => {children}, + Link: ({ + to, + params, + children, + }: { + to: string; + params?: Record; + children: ReactNode; + }) => { + const href = params + ? to.replace(/\$([a-zA-Z0-9_]+)/g, (_match, name: string) => params[name] ?? `$${name}`) + : to; + return {children}; + }, })); import { InstallPermissionsPage } from "./install.permissions"; diff --git a/apps/loopover-ui/src/routes/install.permissions.tsx b/apps/loopover-ui/src/routes/install.permissions.tsx index e984fde7bf..750d9dd3af 100644 --- a/apps/loopover-ui/src/routes/install.permissions.tsx +++ b/apps/loopover-ui/src/routes/install.permissions.tsx @@ -204,7 +204,8 @@ export function InstallPermissionsPage() { You grant these on only the repositories you choose, and you can review or revoke them at any time from your repository's installed-Apps settings on GitHub. See{" "} privacy & security diff --git a/apps/loopover-ui/src/routes/install.test.tsx b/apps/loopover-ui/src/routes/install.test.tsx index 187de1a86e..8005de35b5 100644 --- a/apps/loopover-ui/src/routes/install.test.tsx +++ b/apps/loopover-ui/src/routes/install.test.tsx @@ -2,9 +2,25 @@ import { render, screen, within } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; import type { ReactNode } from "react"; +// Interpolates $-params the same way the real router resolves `to`/`params` into a concrete href +// (#8151 moved every docs link from a plain string `to` to the typed `to="/docs/$slug" params={{ slug }}` +// form), so this stub's rendered link stays a faithful stand-in. vi.mock("@tanstack/react-router", () => ({ createFileRoute: () => () => ({}), - Link: ({ to, children }: { to: string; children: ReactNode }) => {children}, + Link: ({ + to, + params, + children, + }: { + to: string; + params?: Record; + children: ReactNode; + }) => { + const href = params + ? to.replace(/\$([a-zA-Z0-9_]+)/g, (_match, name: string) => params[name] ?? `$${name}`) + : to; + return {children}; + }, })); import { InstallPage } from "./install.index"; diff --git a/apps/loopover-ui/src/routes/maintainers.tsx b/apps/loopover-ui/src/routes/maintainers.tsx index bb9be81399..6ee735ec84 100644 --- a/apps/loopover-ui/src/routes/maintainers.tsx +++ b/apps/loopover-ui/src/routes/maintainers.tsx @@ -56,13 +56,15 @@ function MaintainersPage() {

    Self-host reviews Maintainer workflow @@ -230,7 +232,8 @@ GET /v1/repos/:owner/:repo/registration-readiness`} Open the preview Self-host reviews diff --git a/apps/loopover-ui/src/routes/miners.tsx b/apps/loopover-ui/src/routes/miners.tsx index 02c21b4baf..3c47eae047 100644 --- a/apps/loopover-ui/src/routes/miners.tsx +++ b/apps/loopover-ui/src/routes/miners.tsx @@ -55,13 +55,15 @@ function MinersPage() {

    Install MCP Read the workflow @@ -215,7 +217,8 @@ function MinersPage() {
    Install