From e11518708a8eaecb3893965ae9e8004086a6a5f3 Mon Sep 17 00:00:00 2001 From: Yaacov Date: Fri, 28 Aug 2026 17:13:47 +0300 Subject: [PATCH 1/5] feat: add Scient Docs publishing pilot --- .gitignore | 3 + README.md | 10 + docs/architecture/scient-docs-publishing.md | 84 +++++ docs/scient-docs-manifest.json | 126 +++++++ package.json | 4 + public/_headers | 7 + scripts/sync-scient-docs.mjs | 266 +++++++++++++ scripts/sync-scient-docs.test.mjs | 151 ++++++++ src/components/ScientDocsNavigation.astro | 98 +++++ src/components/ScientDocsShell.astro | 391 ++++++++++++++++++++ src/pages/docs.astro | 335 ++++++++++++----- src/pages/docs/[slug].astro | 34 ++ src/pages/docs/index.json.ts | 13 + src/pages/docs/raw/[slug].md.ts | 23 ++ 14 files changed, 1447 insertions(+), 98 deletions(-) create mode 100644 docs/architecture/scient-docs-publishing.md create mode 100644 docs/scient-docs-manifest.json create mode 100644 public/_headers create mode 100644 scripts/sync-scient-docs.mjs create mode 100644 scripts/sync-scient-docs.test.mjs create mode 100644 src/components/ScientDocsNavigation.astro create mode 100644 src/components/ScientDocsShell.astro create mode 100644 src/pages/docs/[slug].astro create mode 100644 src/pages/docs/index.json.ts create mode 100644 src/pages/docs/raw/[slug].md.ts diff --git a/.gitignore b/.gitignore index eb5ec1d..d908265 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ node_modules/ dist/ .astro/ .tmp/ +.generated/ +.playwright-cli/ +src/generated/scient-docs/ .wrangler/ .DS_Store *.log diff --git a/README.md b/README.md index 18e9cb1..3ae260d 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,16 @@ Do not deploy production from a feature branch or store Cloudflare credentials i See [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution workflow. The cross-repository operating model is maintained in [`ScientFactory/Scient`](https://github.com/ScientFactory/Scient). +## Scient Docs publishing + +The public `/docs` experience is generated from reviewed Markdown in +`scient-desktop/docs/user/` at an exact commit. The source manifest, hashes, +preview/stable qualification, correction path, and rollback contract live in +[`docs/scient-docs-manifest.json`](docs/scient-docs-manifest.json) and +[`docs/architecture/scient-docs-publishing.md`](docs/architecture/scient-docs-publishing.md). +Generated website files are intentionally ignored; `bun run docs:sync` rebuilds +them and fails closed if source content no longer matches the reviewed hashes. + ## First-party event measurement Cloudflare D1 stores four website event types: diff --git a/docs/architecture/scient-docs-publishing.md b/docs/architecture/scient-docs-publishing.md new file mode 100644 index 0000000..a5b3e3a --- /dev/null +++ b/docs/architecture/scient-docs-publishing.md @@ -0,0 +1,84 @@ +# Scient Docs Publishing Architecture + +Status: Active +Owner: Yaacov +Created: 2026-08-28 +Last updated: 2026-08-28 +Purpose: Defines the website-owned transport, provenance, failure, correction, and rollback contract for publishing canonical Scient Desktop Help. +Doc type: Architecture decision + +## Authority + +`scient-desktop/docs/user/` owns Help prose. The website owns selection, +transport, rendering, navigation, search, accessibility, source/version +display, and deployment. `docs/scient-docs-manifest.json` is the reviewed +publication selection; it is not a second prose source. `scient-agent` is +outside this publishing pass. + +## Selected Transport + +The website build reads a committed manifest that pins: + +- a full immutable `ScientFactory/scient-desktop` commit; +- every selected `docs/user/` source path; +- a SHA-256 digest for each source page; +- stable or preview channel and truthful applicability wording; and +- corpus defaults plus page-level topic, title, summary, and surface metadata. + +CI and ordinary builds fetch each page from GitHub's immutable raw-commit URL. +Local development may set `SCIENT_DOCS_SOURCE_ROOT` to an exact checkout at the +same commit; the generator rejects a different head, and page hashes still +apply. Generated files are ignored build products. The website never commits a +hand-maintained prose copy. + +This transport was selected for the pilot because it is small, independently +deployable, exact, auditable, and reversible without introducing a package +registry or release artifact before one is justified. Re-evaluate it if corpus +size, availability, rate limits, private sources, or release engineering make +raw-commit transport materially unreliable. + +## Build And Safety Contract + +`scripts/sync-scient-docs.mjs` validates the manifest, fetches exact content, +checks every digest and H1, rejects executable/unsafe raw Markdown patterns, +rewrites selected relative Help links to stable public routes, and sends +unselected relative Help links to the exact GitHub source. It generates: + +- Astro Markdown pages under stable `/docs//` routes; +- exact raw Markdown under `/docs/raw/.md`; +- `/docs/index.json` with title, summary, topic, channel, applicability, page + URL, raw URL, source path/revision/URL, and search text; and +- the metadata used by client-side topic/text search and source banners. + +Missing content, a changed hash, unsafe content, a mutable/non-full revision, +duplicate routing, an unqualified stable manifest, or a wrong local checkout +fails the build. There is no stale cache fallback that could silently publish +the wrong content. The previously deployed website remains live while CI or a +preview exposes the failure. + +## Preview, Stable, And Corrections + +A preview manifest may pin an exact documentation or application PR head. The +website labels every page as preview, shows what it applies to, and links to the +exact source. It must not be presented as stable released behavior. + +A stable manifest must name at least one reviewed desktop release tag and pin +the exact Help revision that truthfully describes it. A release triggers a +reviewed website manifest PR; publication should complete within 24 hours. A +Help-only correction uses a desktop documentation PR followed by a dependent +website manifest PR and deployment, without requiring a new app binary. Its +applicability text continues to name the releases the corrected prose +describes. + +Rollback reverts the manifest pin or website publishing change. It never +rewrites canonical desktop Help. A reverted or failed deployment leaves the +last successful public corpus in place and visible through ordinary website +deployment history. + +## Deferred Scope + +The pilot deliberately does not publish every Help page, accept a final +site-wide information architecture, add a documentation MCP, or require a +larger metadata schema. Expand the manifest only after a page is verified for +its released surface. Evaluate MCP only after stable HTML, raw Markdown, the +index, and search reveal a concrete agent-retrieval gap. diff --git a/docs/scient-docs-manifest.json b/docs/scient-docs-manifest.json new file mode 100644 index 0000000..93dab7a --- /dev/null +++ b/docs/scient-docs-manifest.json @@ -0,0 +1,126 @@ +{ + "schemaVersion": 1, + "channel": "preview", + "source": { + "repository": "ScientFactory/scient-desktop", + "revision": "53170890ef9a6e5e53afce10d91c6d607fc91638", + "pullRequest": "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/ScientFactory/scient-desktop/pull/196", + "reviewedAt": "2026-08-28" + }, + "version": { + "label": "Scient Docs pilot preview", + "appliesTo": "Unreleased documentation candidate based on Scient Desktop main through c0baaab2cda29a84682b77b8384bb7542e59a8bd", + "stableReleaseTags": [] + }, + "corpusDefaults": { + "product": "Scient Desktop", + "surface": "desktop", + "publicationStatus": "preview-only", + "audience": "users and support agents" + }, + "publication": { + "releaseTrigger": "A reviewed desktop release plus a website manifest PR selecting its exact Help source revision", + "correctionTrigger": "A merged desktop Help correction plus a dependent website manifest PR; no desktop binary release is required", + "maximumLagHours": 24, + "failureMode": "The build fails closed and the previously deployed Docs remain live; CI and preview checks expose the failed source pin", + "rollback": "Revert the website manifest pin or the website publishing change; canonical desktop Help is never rewritten by rollback" + }, + "pages": [ + { + "slug": "getting-started", + "title": "Getting started", + "summary": "Connect a provider, choose optional local preferences, and hand off to your first project.", + "topic": "Start", + "sourcePath": "docs/user/getting-started.md", + "sha256": "29f8b7be9deb280f02f338ab75f64375231390e18d439453acce606785160987" + }, + { + "slug": "projects", + "title": "Projects", + "summary": "Open or create folders and understand Scient's optional, bounded project setup.", + "topic": "Start", + "sourcePath": "docs/user/projects.md", + "sha256": "c0e891234981007e18fb06dd019597ee5f8035e7910074978f7ee48bb75959a4" + }, + { + "slug": "file-previews", + "title": "File previews", + "summary": "Open project and environment files, refresh previews, and understand current file limits.", + "topic": "Files and documents", + "sourcePath": "docs/user/file-previews.md", + "sha256": "d608b0def0b5103c95e982286589369c8ed2b127dc2749aaca86d2e9329db62e" + }, + { + "slug": "pdf-reader", + "title": "PDF reader", + "summary": "Read, search, navigate, and save copies of PDFs while preserving reading state.", + "topic": "Files and documents", + "sourcePath": "docs/user/pdf-reader.md", + "sha256": "cb565e77f934796ce06937cc5d0624b6cc088e599b9fcc85f39db64c4cd1c63f" + }, + { + "slug": "latex", + "title": "LaTeX", + "summary": "Build LaTeX projects, manage toolchains, inspect diagnostics, and navigate with SyncTeX.", + "topic": "Files and documents", + "sourcePath": "docs/user/latex.md", + "sha256": "184ab2ef560c16d8347afaef160b506f4959252ca134d76949784ab9396a1f32" + }, + { + "slug": "providers", + "title": "Providers", + "summary": "Understand provider readiness, accounts, installations, remote authority, and recovery.", + "topic": "Providers", + "sourcePath": "docs/user/providers.md", + "sha256": "7af7778920ca42dd3e740ff309c1e1e31f09280ed0001e9e9053cb08ce313136" + }, + { + "slug": "providers-codex", + "title": "Codex", + "summary": "Connect Codex, choose a runtime, and manage one or more compatible account homes.", + "topic": "Providers", + "sourcePath": "docs/user/providers-codex.md", + "sha256": "0dd51ebb5a790382a121a0aeca0f2403deff677ad2e0ecd271245a8548771653" + }, + { + "slug": "providers-claude", + "title": "Claude", + "summary": "Connect Claude Code and keep account, config-directory, runtime, and advanced routes distinct.", + "topic": "Providers", + "sourcePath": "docs/user/providers-claude.md", + "sha256": "625aa8cd12247d997555d775208e2a447eef67df428e25e19c854ad9672a8c68" + }, + { + "slug": "providers-antigravity", + "title": "Antigravity", + "summary": "Use Google's account flow, managed runtime, dynamic models, and native session behavior.", + "topic": "Providers", + "sourcePath": "docs/user/providers-antigravity.md", + "sha256": "d924595744d67c926d75d12d4996bb7b6de5f4edde1c0d2a0a1419941a225204" + }, + { + "slug": "providers-grok", + "title": "Grok", + "summary": "Enable the Early Access Grok provider and distinguish subscription, API-key, and runtime state.", + "topic": "Providers", + "sourcePath": "docs/user/providers-grok.md", + "sha256": "f9c4c2f77501f3ddedf7d4e29d6fba9b1e315320787db6ef102b2f8cf2990381" + }, + { + "slug": "providers-droid", + "title": "Droid (Factory)", + "summary": "Connect Factory's Droid and understand ACP models, autonomy, steering, and limitations.", + "topic": "Providers", + "sourcePath": "docs/user/providers-droid.md", + "sha256": "85d9ac038edaf54e58100128dfbb9b5097b4741c801162546ae4a44b464b1d9c" + }, + { + "slug": "providers-cursor", + "title": "Cursor", + "summary": "Connect Cursor Agent and keep browser authentication, runtime ownership, and recovery clear.", + "topic": "Providers", + "sourcePath": "docs/user/providers-cursor.md", + "sha256": "7be3cbcad434648a0c0aba8977fb3df948c53c0a7f21c514ce3a074952eaa5b6" + } + ] +} diff --git a/package.json b/package.json index 084ac00..20bcece 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,14 @@ "private": true, "type": "module", "scripts": { + "docs:sync": "node scripts/sync-scient-docs.mjs", + "predev": "bun run docs:sync", "dev": "astro dev", + "prebuild": "bun run docs:sync", "build": "astro build", "build:edge": "wrangler pages functions build --outdir .tmp/scientfactory-pages-worker", "preview": "astro preview", + "pretypecheck": "bun run docs:sync", "typecheck": "astro check && tsc --noEmit --project functions/tsconfig.json", "test": "vitest run", "db:migrate": "wrangler d1 migrations apply scientfactory-downloads --remote", diff --git a/public/_headers b/public/_headers new file mode 100644 index 0000000..fc2d773 --- /dev/null +++ b/public/_headers @@ -0,0 +1,7 @@ +/docs/raw/* + Content-Type: text/markdown; charset=utf-8 + X-Content-Type-Options: nosniff + +/docs/index.json + Content-Type: application/json; charset=utf-8 + X-Content-Type-Options: nosniff diff --git a/scripts/sync-scient-docs.mjs b/scripts/sync-scient-docs.mjs new file mode 100644 index 0000000..72c72ef --- /dev/null +++ b/scripts/sync-scient-docs.mjs @@ -0,0 +1,266 @@ +#!/usr/bin/env node + +import { execFileSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import { mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs"; +import { dirname, posix, resolve } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const defaultManifestPath = resolve(repositoryRoot, "docs/scient-docs-manifest.json"); +const defaultOutputRoot = resolve(repositoryRoot, "src/generated/scient-docs"); +const dangerousMarkdown = /<(script|iframe|object|embed|style)\b|\bon[a-z]+\s*=|javascript\s*:/i; + +function requiredString(value, label) { + if (typeof value !== "string" || value.trim() === "") { + throw new Error(`${label} must be a non-empty string`); + } + return value; +} + +export function validateManifest(value) { + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw new Error("Scient Docs manifest must be an object"); + } + if (value.schemaVersion !== 1) throw new Error("Unsupported Scient Docs manifest schema"); + if (value.channel !== "preview" && value.channel !== "stable") { + throw new Error("Scient Docs channel must be preview or stable"); + } + if ( + !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test( + requiredString(value.source?.repository, "source.repository"), + ) + ) { + throw new Error("source.repository must be a GitHub owner/repository pair"); + } + if (!/^[0-9a-f]{40}$/.test(requiredString(value.source?.revision, "source.revision"))) { + throw new Error("source.revision must be a full 40-character Git SHA"); + } + if (!/^\d{4}-\d{2}-\d{2}$/.test(requiredString(value.source?.reviewedAt, "source.reviewedAt"))) { + throw new Error("source.reviewedAt must be an ISO date"); + } + requiredString(value.version?.label, "version.label"); + requiredString(value.version?.appliesTo, "version.appliesTo"); + if (!Array.isArray(value.version?.stableReleaseTags)) { + throw new Error("version.stableReleaseTags must be an array"); + } + if (value.channel === "stable" && value.version.stableReleaseTags.length === 0) { + throw new Error("Stable Scient Docs require at least one stable release tag"); + } + for (const [index, tag] of value.version.stableReleaseTags.entries()) { + requiredString(tag, `version.stableReleaseTags[${index}]`); + } + if ( + !Number.isInteger(value.publication?.maximumLagHours) || + value.publication.maximumLagHours < 1 + ) { + throw new Error("publication.maximumLagHours must be a positive integer"); + } + for (const key of ["releaseTrigger", "correctionTrigger", "failureMode", "rollback"]) { + requiredString(value.publication?.[key], `publication.${key}`); + } + if (!Array.isArray(value.pages) || value.pages.length === 0) { + throw new Error("Scient Docs manifest must select at least one page"); + } + + const slugs = new Set(); + const sourcePaths = new Set(); + for (const [index, page] of value.pages.entries()) { + const prefix = `pages[${index}]`; + const slug = requiredString(page.slug, `${prefix}.slug`); + if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)) { + throw new Error(`${prefix}.slug must be a lowercase URL slug`); + } + if (slugs.has(slug)) throw new Error(`Duplicate Scient Docs slug: ${slug}`); + slugs.add(slug); + const sourcePath = requiredString(page.sourcePath, `${prefix}.sourcePath`); + if ( + !sourcePath.startsWith("docs/user/") || + !sourcePath.endsWith(".md") || + sourcePath.includes("\\") || + posix.normalize(sourcePath) !== sourcePath + ) { + throw new Error(`${prefix}.sourcePath must be a docs/user Markdown path`); + } + if (sourcePaths.has(sourcePath)) throw new Error(`Duplicate Help source path: ${sourcePath}`); + sourcePaths.add(sourcePath); + requiredString(page.title, `${prefix}.title`); + requiredString(page.summary, `${prefix}.summary`); + requiredString(page.topic, `${prefix}.topic`); + if (!/^[0-9a-f]{64}$/.test(requiredString(page.sha256, `${prefix}.sha256`))) { + throw new Error(`${prefix}.sha256 must be a SHA-256 digest`); + } + } + return value; +} + +export function hashMarkdown(markdown) { + return createHash("sha256").update(markdown, "utf8").digest("hex"); +} + +function sourceWebUrl(manifest, sourcePath) { + return `https://github.com/${manifest.source.repository}/blob/${manifest.source.revision}/${sourcePath}`; +} + +export function rewriteHelpLinks(markdown, page, manifest) { + const selectedByPath = new Map( + manifest.pages.map((candidate) => [candidate.sourcePath, candidate]), + ); + return markdown.replace(/(\]\()([^)]+)(\))/g, (whole, open, rawTarget, close) => { + const target = rawTarget.trim(); + if (target.startsWith("#") || target.startsWith("/") || /^[a-z][a-z0-9+.-]*:/i.test(target)) { + return whole; + } + const hashIndex = target.indexOf("#"); + const targetPath = hashIndex === -1 ? target : target.slice(0, hashIndex); + const fragment = hashIndex === -1 ? "" : target.slice(hashIndex); + if (!targetPath.endsWith(".md")) return whole; + const normalized = posix.normalize(posix.join(posix.dirname(page.sourcePath), targetPath)); + const selected = selectedByPath.get(normalized); + const rewritten = selected + ? `/docs/${selected.slug}/${fragment}` + : `${sourceWebUrl(manifest, normalized)}${fragment}`; + return `${open}${rewritten}${close}`; + }); +} + +function searchableText(markdown) { + return markdown + .replace(/```[\s\S]*?```/g, " ") + .replace(/`([^`]+)`/g, "$1") + .replace(/!\[[^\]]*\]\([^)]*\)/g, " ") + .replace(/\[([^\]]+)\]\([^)]*\)/g, "$1") + .replace(/[#>*_|~-]/g, " ") + .replace(/\s+/g, " ") + .trim(); +} + +function frontmatter(page, manifest) { + const values = { + title: page.title, + summary: page.summary, + topic: page.topic, + slug: page.slug, + sourcePath: page.sourcePath, + sourceRevision: manifest.source.revision, + channel: manifest.channel, + versionLabel: manifest.version.label, + appliesTo: manifest.version.appliesTo, + }; + return [ + "---", + ...Object.entries(values).map(([key, value]) => `${key}: ${JSON.stringify(value)}`), + "---", + "", + ].join("\n"); +} + +export async function generateDocs({ manifest, loadSource, outputRoot = defaultOutputRoot }) { + validateManifest(manifest); + const rawBySlug = {}; + const pages = []; + + for (const page of manifest.pages) { + const markdown = await loadSource(page.sourcePath); + if (typeof markdown !== "string") + throw new Error(`Source loader returned no text for ${page.sourcePath}`); + const actualHash = hashMarkdown(markdown); + if (actualHash !== page.sha256) { + throw new Error( + `${page.sourcePath} SHA-256 mismatch: expected ${page.sha256}, received ${actualHash}`, + ); + } + if (dangerousMarkdown.test(markdown)) { + throw new Error(`${page.sourcePath} contains executable or unsafe raw Markdown content`); + } + if (!/^#\s+\S.+$/m.test(markdown)) { + throw new Error(`${page.sourcePath} must contain an H1 heading`); + } + + rawBySlug[page.slug] = markdown; + pages.push({ + ...page, + channel: manifest.channel, + versionLabel: manifest.version.label, + appliesTo: manifest.version.appliesTo, + sourceRepository: manifest.source.repository, + sourceRevision: manifest.source.revision, + sourceUrl: sourceWebUrl(manifest, page.sourcePath), + rawUrl: `/docs/raw/${page.slug}.md`, + url: `/docs/${page.slug}/`, + searchText: searchableText(markdown), + }); + } + + rmSync(outputRoot, { recursive: true, force: true }); + const pagesRoot = resolve(outputRoot, "pages"); + mkdirSync(pagesRoot, { recursive: true }); + for (const page of manifest.pages) { + const rendered = rewriteHelpLinks(rawBySlug[page.slug], page, manifest); + writeFileSync( + resolve(pagesRoot, `${page.slug}.md`), + `${frontmatter(page, manifest)}${rendered}`, + "utf8", + ); + } + + const index = { + schemaVersion: 1, + channel: manifest.channel, + source: manifest.source, + version: manifest.version, + corpusDefaults: manifest.corpusDefaults, + publication: manifest.publication, + pages, + }; + writeFileSync(resolve(outputRoot, "index.json"), `${JSON.stringify(index, null, 2)}\n`, "utf8"); + writeFileSync(resolve(outputRoot, "raw.json"), `${JSON.stringify(rawBySlug, null, 2)}\n`, "utf8"); + return index; +} + +function localSourceLoader(sourceRoot, expectedRevision) { + const root = realpathSync(sourceRoot); + const actualRevision = execFileSync("git", ["-C", root, "rev-parse", "HEAD"], { + encoding: "utf8", + }).trim(); + if (actualRevision !== expectedRevision) { + throw new Error( + `SCIENT_DOCS_SOURCE_ROOT is at ${actualRevision}, expected ${expectedRevision}`, + ); + } + return async (sourcePath) => readFileSync(resolve(root, sourcePath), "utf8"); +} + +function remoteSourceLoader(manifest) { + return async (sourcePath) => { + const url = `https://raw.githubusercontent.com/${manifest.source.repository}/${manifest.source.revision}/${sourcePath}`; + const response = await fetch(url, { redirect: "error" }); + if (!response.ok) throw new Error(`Could not fetch ${sourcePath}: HTTP ${response.status}`); + return response.text(); + }; +} + +export async function syncFromManifest({ + manifestPath = defaultManifestPath, + outputRoot = defaultOutputRoot, + sourceRoot = process.env.SCIENT_DOCS_SOURCE_ROOT, +} = {}) { + const manifest = validateManifest(JSON.parse(readFileSync(manifestPath, "utf8"))); + const loadSource = sourceRoot + ? localSourceLoader(sourceRoot, manifest.source.revision) + : remoteSourceLoader(manifest); + const index = await generateDocs({ manifest, loadSource, outputRoot }); + return { outputRoot, pageCount: index.pages.length, revision: manifest.source.revision }; +} + +if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) { + try { + const result = await syncFromManifest(); + process.stdout.write( + `Synced ${result.pageCount} Scient Docs pages from ${result.revision} into ${result.outputRoot}\n`, + ); + } catch (error) { + process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); + process.exitCode = 1; + } +} diff --git a/scripts/sync-scient-docs.test.mjs b/scripts/sync-scient-docs.test.mjs new file mode 100644 index 0000000..38ee339 --- /dev/null +++ b/scripts/sync-scient-docs.test.mjs @@ -0,0 +1,151 @@ +import assert from "node:assert/strict"; +import { mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, it } from "vitest"; + +import { + generateDocs, + hashMarkdown, + rewriteHelpLinks, + validateManifest, +} from "./sync-scient-docs.mjs"; + +const repositoryRoot = dirname(dirname(fileURLToPath(import.meta.url))); + +function fixtureManifest(markdown = "# Getting started\n\nSee [Providers](./providers.md).\n") { + return { + schemaVersion: 1, + channel: "preview", + source: { + repository: "ScientFactory/scient-desktop", + revision: "a".repeat(40), + reviewedAt: "2026-08-28", + }, + version: { + label: "Pilot preview", + appliesTo: "Unreleased candidate", + stableReleaseTags: [], + }, + corpusDefaults: { product: "Scient Desktop", surface: "desktop" }, + publication: { + releaseTrigger: "Reviewed release manifest update", + correctionTrigger: "Reviewed Help correction manifest update", + maximumLagHours: 24, + failureMode: "Fail closed", + rollback: "Revert the pin", + }, + pages: [ + { + slug: "getting-started", + title: "Getting started", + summary: "Start using Scient.", + topic: "Start", + sourcePath: "docs/user/getting-started.md", + sha256: hashMarkdown(markdown), + }, + { + slug: "providers", + title: "Providers", + summary: "Connect a provider.", + topic: "Providers", + sourcePath: "docs/user/providers.md", + sha256: hashMarkdown("# Providers\n"), + }, + ], + }; +} + +describe("Scient Docs manifest", () => { + it("serves raw Markdown and the machine index with explicit UTF-8 content types", () => { + const headers = readFileSync(join(repositoryRoot, "public/_headers"), "utf8"); + assert.match(headers, /\/docs\/raw\/\*\s+Content-Type: text\/markdown; charset=utf-8/); + assert.match(headers, /\/docs\/index\.json\s+Content-Type: application\/json; charset=utf-8/); + }); + + it("requires immutable revisions, unique pages, and stable release qualification", () => { + const manifest = fixtureManifest(); + assert.equal(validateManifest(manifest), manifest); + assert.throws(() => validateManifest({ ...manifest, channel: "stable" }), /release tag/); + assert.throws( + () => validateManifest({ ...manifest, source: { ...manifest.source, revision: "main" } }), + /40-character Git SHA/, + ); + assert.throws( + () => validateManifest({ ...manifest, pages: [manifest.pages[0], manifest.pages[0]] }), + /Duplicate Scient Docs slug/, + ); + assert.throws( + () => + validateManifest({ + ...manifest, + pages: [{ ...manifest.pages[0], sourcePath: "docs/user/../../private.md" }], + }), + /docs\/user Markdown path/, + ); + }); + + it("rewrites selected Help links to public routes and unselected Help to exact source", () => { + const manifest = fixtureManifest(); + const page = manifest.pages[0]; + const markdown = [ + "[Providers](./providers.md#setup)", + "[Other](./other.md)", + "[External](https://example.com)", + ].join("\n"); + const rewritten = rewriteHelpLinks(markdown, page, manifest); + assert.match(rewritten, /\/docs\/providers\/#setup/); + assert.match(rewritten, /blob\/a{40}\/docs\/user\/other\.md/); + assert.match(rewritten, /https:\/\/example\.com/); + }); + + it("generates deterministic page, raw, and index artifacts and rejects unsafe or changed source", async () => { + const markdown = "# Getting started\n\nSee [Providers](./providers.md).\n"; + const manifest = fixtureManifest(markdown); + const outputRoot = mkdtempSync(join(tmpdir(), "scient-docs-test-")); + try { + const sources = new Map([ + ["docs/user/getting-started.md", markdown], + ["docs/user/providers.md", "# Providers\n"], + ]); + const result = await generateDocs({ + manifest, + loadSource: async (path) => sources.get(path), + outputRoot, + }); + assert.equal(result.pages.length, 2); + assert.match( + readFileSync(join(outputRoot, "pages/getting-started.md"), "utf8"), + /\/docs\/providers\//, + ); + assert.equal( + JSON.parse(readFileSync(join(outputRoot, "raw.json"), "utf8"))["getting-started"], + markdown, + ); + + await assert.rejects( + generateDocs({ + manifest, + loadSource: async (path) => + path === "docs/user/getting-started.md" ? `${markdown}changed` : sources.get(path), + outputRoot, + }), + /SHA-256 mismatch/, + ); + const unsafe = "# Getting started\n\n\n"; + const unsafeManifest = fixtureManifest(unsafe); + await assert.rejects( + generateDocs({ + manifest: unsafeManifest, + loadSource: async (path) => + path === "docs/user/getting-started.md" ? unsafe : sources.get(path), + outputRoot, + }), + /unsafe raw Markdown content/, + ); + } finally { + rmSync(outputRoot, { recursive: true, force: true }); + } + }); +}); diff --git a/src/components/ScientDocsNavigation.astro b/src/components/ScientDocsNavigation.astro new file mode 100644 index 0000000..eb86d32 --- /dev/null +++ b/src/components/ScientDocsNavigation.astro @@ -0,0 +1,98 @@ +--- +interface DocsPage { + slug: string; + title: string; + topic: string; + url: string; +} + +interface Props { + pages: DocsPage[]; + currentSlug?: string; + compact?: boolean; +} + +const { pages, currentSlug, compact = false } = Astro.props; +const topics = [...new Set(pages.map((page) => page.topic))]; +--- + + + + diff --git a/src/components/ScientDocsShell.astro b/src/components/ScientDocsShell.astro new file mode 100644 index 0000000..f73147a --- /dev/null +++ b/src/components/ScientDocsShell.astro @@ -0,0 +1,391 @@ +--- +import Layout from "../layouts/Layout.astro"; +import ScientDocsNavigation from "./ScientDocsNavigation.astro"; + +interface DocsPage { + slug: string; + title: string; + summary: string; + topic: string; + url: string; + rawUrl: string; + sourceUrl: string; +} + +interface Props { + index: { + channel: string; + source: { revision: string; reviewedAt: string }; + version: { label: string; appliesTo: string; stableReleaseTags: string[] }; + pages: DocsPage[]; + }; + title: string; + description: string; + currentSlug?: string; + currentPage?: DocsPage; +} + +const { index, title, description, currentSlug, currentPage } = Astro.props; +const isPreview = index.channel === "preview"; +const shortRevision = index.source.revision.slice(0, 10); +--- + + +
+ + +
+ + Browse Scient Docs + {currentPage?.title ?? "All pages"} + + +
+ +
+
+
+ {isPreview ? "Preview documentation" : "Versioned documentation"} +

{index.version.appliesTo}

+
+
+ Reviewed {index.source.reviewedAt} + { + currentPage ? ( + <> + Source {shortRevision} + Raw Markdown + + ) : ( + Source {shortRevision} + ) + } +
+
+ +
+ +
+
+
+
+ + diff --git a/src/pages/docs.astro b/src/pages/docs.astro index c03f0d5..5106c2c 100644 --- a/src/pages/docs.astro +++ b/src/pages/docs.astro @@ -1,103 +1,242 @@ --- -import Prose from "../components/Prose.astro"; -import Layout from "../layouts/Layout.astro"; +import ScientDocsShell from "../components/ScientDocsShell.astro"; +import docsIndex from "../generated/scient-docs/index.json"; + +const topics = [...new Set(docsIndex.pages.map((page) => page.topic))]; --- - - -
- -
-

Download the right build for your computer.

-

- The download page provides the current installers for macOS, Windows, and Linux directly from ScientFactory's official GitHub release. -

-

- It also explains how to choose between Apple Silicon and Intel on macOS and how to handle the first-launch security message on each platform. -

- -
-
- -
- -
-

Begin with a project you already understand.

-
    -
  1. -
    -

    Open a project folder

    -

    Add a local folder containing the files or work you want Scient and its agents to help with.

    -
    -
  2. -
  3. -
    -

    Choose an available provider

    -

    Scient works with supported AI tools installed and authorized on your computer. Choose the provider you want for the new thread.

    -
    -
  4. -
  5. -
    -

    Start a focused thread

    -

    Describe one concrete task, provide the necessary context, and review the agent's proposed work as it progresses.

    -
    -
  6. -
  7. -
    -

    Keep the work visible

    -

    Use Scient's chat, terminal, browser, and review surfaces to understand what changed before accepting the result.

    +
    +
    +

    Documentation

    +

    Learn Scient from the source.

    +

    + These pilot guides are generated from reviewed Scient Desktop Help at an exact commit. Search + the current preview, browse by topic, or open any page's raw Markdown and source evidence. +

    +
    + + + +
    + { + topics.map((topic) => ( +
    +

    {topic}

    +
    + {docsIndex.pages + .filter((page) => page.topic === topic) + .map((page) => ( + + ))}
    -
  8. -
-
-
- -
- -
-

A few concepts are enough to find your way around.

-
-
-

Projects

-

A project connects Scient to a local folder and keeps related work together.

-
-
-

Threads

-

Each thread is a focused conversation with an agent about a particular task.

-
-
-

Providers

-

You choose which supported AI provider and model powers a session.

-
-
-

Workspace tools

-

Chats, terminals, browser previews, and reviews remain accessible in one workspace.

-
-
-
-
- -
- -
-

Expect change, and tell us when something breaks.

-

- Scient is under active development. Features may change between releases, and you may encounter unfinished behavior or platform-specific issues. -

-
-

When reporting a problem, include your operating system, Scient version, the provider you were using, and the shortest sequence that reproduces the issue.

-
-
-
-
-
+ + )) + } + + + + + + + diff --git a/src/pages/docs/[slug].astro b/src/pages/docs/[slug].astro new file mode 100644 index 0000000..7ee610f --- /dev/null +++ b/src/pages/docs/[slug].astro @@ -0,0 +1,34 @@ +--- +import ScientDocsShell from "../../components/ScientDocsShell.astro"; +import docsIndex from "../../generated/scient-docs/index.json"; + +interface MarkdownModule { + Content: any; +} + +const pageModules = import.meta.glob("../../generated/scient-docs/pages/*.md", { + eager: true, +}); + +export function getStaticPaths() { + return docsIndex.pages.map((page) => ({ params: { slug: page.slug } })); +} + +const page = docsIndex.pages.find((candidate) => candidate.slug === Astro.params.slug); +if (!page) throw new Error(`Unknown Scient Docs page: ${Astro.params.slug}`); +const pageModule = pageModules[`../../generated/scient-docs/pages/${page.slug}.md`]; +if (!pageModule) throw new Error(`Missing generated Scient Docs module: ${page.slug}`); +const Content = pageModule.Content; +--- + + +
+ +
+
diff --git a/src/pages/docs/index.json.ts b/src/pages/docs/index.json.ts new file mode 100644 index 0000000..33b9e1a --- /dev/null +++ b/src/pages/docs/index.json.ts @@ -0,0 +1,13 @@ +import type { APIRoute } from "astro"; + +import docsIndex from "../../generated/scient-docs/index.json"; + +export const prerender = true; + +export const GET: APIRoute = () => + new Response(JSON.stringify(docsIndex, null, 2), { + headers: { + "Content-Type": "application/json; charset=utf-8", + "Cache-Control": "public, max-age=300", + }, + }); diff --git a/src/pages/docs/raw/[slug].md.ts b/src/pages/docs/raw/[slug].md.ts new file mode 100644 index 0000000..fc6c6f5 --- /dev/null +++ b/src/pages/docs/raw/[slug].md.ts @@ -0,0 +1,23 @@ +import type { APIRoute } from "astro"; + +import docsIndex from "../../../generated/scient-docs/index.json"; +import rawPages from "../../../generated/scient-docs/raw.json"; + +export const prerender = true; + +export function getStaticPaths() { + return docsIndex.pages.map((page) => ({ params: { slug: page.slug } })); +} + +export const GET: APIRoute = ({ params }) => { + const slug = params.slug ?? ""; + const markdown = rawPages[slug as keyof typeof rawPages]; + if (typeof markdown !== "string") return new Response("Not found\n", { status: 404 }); + return new Response(markdown, { + headers: { + "Content-Type": "text/markdown; charset=utf-8", + "Cache-Control": "public, max-age=300", + "X-Scient-Docs-Source": docsIndex.source.revision, + }, + }); +}; From 575f4cfd2f707134ef25fb260c266e56c9875ba2 Mon Sep 17 00:00:00 2001 From: Yaacov Date: Fri, 28 Aug 2026 18:10:24 +0300 Subject: [PATCH 2/5] docs: publish complete desktop Help corpus preview --- AGENTS.md | 5 +- docs/architecture/scient-docs-publishing.md | 27 +-- docs/scient-docs-manifest.json | 178 +++++++++++++++++++- src/components/ScientDocsShell.astro | 8 + src/pages/docs.astro | 8 +- 5 files changed, 200 insertions(+), 26 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 64945b6..0105b79 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,8 +15,9 @@ must render an approved exact source/version instead of maintaining a second copy. Website Docs work owns rendering, navigation, search, accessibility, deployment, and truthful version/source selection. A change to app behavior or source prose uses a separate desktop pull request with explicit landing order. -Do not invent the final source transport before the publishing pilot selects -and proves it. +Use the exact-source manifest transport recorded in +`docs/architecture/scient-docs-publishing.md`; replace it only through a +reviewed architecture change with concrete evidence. Every pull request includes one `Documentation impact` declaration: `None — reason`, `Updated — paths`, or `Dependent PR — repository and link`. For Docs diff --git a/docs/architecture/scient-docs-publishing.md b/docs/architecture/scient-docs-publishing.md index a5b3e3a..ee51e54 100644 --- a/docs/architecture/scient-docs-publishing.md +++ b/docs/architecture/scient-docs-publishing.md @@ -31,11 +31,12 @@ same commit; the generator rejects a different head, and page hashes still apply. Generated files are ignored build products. The website never commits a hand-maintained prose copy. -This transport was selected for the pilot because it is small, independently -deployable, exact, auditable, and reversible without introducing a package -registry or release artifact before one is justified. Re-evaluate it if corpus -size, availability, rate limits, private sources, or release engineering make -raw-commit transport materially unreliable. +This transport was selected in the pilot and retained for the complete +desktop-first corpus because it remains small, independently deployable, exact, +auditable, and reversible without introducing a package registry or release +artifact before one is justified. Re-evaluate it if corpus size, availability, +rate limits, private sources, or release engineering make raw-commit transport +materially unreliable. ## Build And Safety Contract @@ -75,10 +76,14 @@ rewrites canonical desktop Help. A reverted or failed deployment leaves the last successful public corpus in place and visible through ordinary website deployment history. -## Deferred Scope +## Current corpus and deferred scope -The pilot deliberately does not publish every Help page, accept a final -site-wide information architecture, add a documentation MCP, or require a -larger metadata schema. Expand the manifest only after a page is verified for -its released surface. Evaluate MCP only after stable HTML, raw Markdown, the -index, and search reveal a concrete agent-retrieval gap. +The preview manifest selects all 32 desktop-first Help pages qualified at the +exact desktop source revision. It deliberately excludes the retained mobile +page because Scient has no public mobile release. Directory membership still +does not imply publication, and future Help owners require factual +qualification before entering the manifest. + +Stable release publication, a documentation MCP, and any larger metadata +schema remain separate decisions. Evaluate MCP only after stable HTML, raw +Markdown, the index, and search reveal a concrete agent-retrieval gap. diff --git a/docs/scient-docs-manifest.json b/docs/scient-docs-manifest.json index 93dab7a..5cb7844 100644 --- a/docs/scient-docs-manifest.json +++ b/docs/scient-docs-manifest.json @@ -3,13 +3,13 @@ "channel": "preview", "source": { "repository": "ScientFactory/scient-desktop", - "revision": "53170890ef9a6e5e53afce10d91c6d607fc91638", - "pullRequest": "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/ScientFactory/scient-desktop/pull/196", + "revision": "1e24ed64800fa0bb20e978742d911faa5e093f6f", + "pullRequest": "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/ScientFactory/scient-desktop/pull/197", "reviewedAt": "2026-08-28" }, "version": { - "label": "Scient Docs pilot preview", - "appliesTo": "Unreleased documentation candidate based on Scient Desktop main through c0baaab2cda29a84682b77b8384bb7542e59a8bd", + "label": "Scient Docs complete corpus preview", + "appliesTo": "Unreleased Help candidate based on current Scient Desktop main plus documentation PR #197; not a stable release", "stableReleaseTags": [] }, "corpusDefaults": { @@ -34,6 +34,22 @@ "sourcePath": "docs/user/getting-started.md", "sha256": "29f8b7be9deb280f02f338ab75f64375231390e18d439453acce606785160987" }, + { + "slug": "install", + "title": "Install Scient", + "summary": "Choose the official installer for your platform, complete first launch, and understand existing data boundaries.", + "topic": "Start", + "sourcePath": "docs/user/install.md", + "sha256": "162f6499090767632c56d7e52ecf14fcceab84b74a13322e1f39c8448091fb71" + }, + { + "slug": "updating", + "title": "Update Scient", + "summary": "Update the desktop app, keep connected servers aligned, and understand rollback outcomes.", + "topic": "Start", + "sourcePath": "docs/user/updating.md", + "sha256": "3cc12251807afce1034ad6dd1f981c0533f620e255a695f2382031a903597581" + }, { "slug": "projects", "title": "Projects", @@ -42,11 +58,75 @@ "sourcePath": "docs/user/projects.md", "sha256": "c0e891234981007e18fb06dd019597ee5f8035e7910074978f7ee48bb75959a4" }, + { + "slug": "composer", + "title": "Message composer", + "summary": "Send messages, queue work, steer an active turn, attach files, and recover failed drafts.", + "topic": "Work in Scient", + "sourcePath": "docs/user/composer.md", + "sha256": "1529b13b8c353301985c51c9838d51eddf111a536838d10a5a9d949199ebe7c1" + }, + { + "slug": "thread-sidebar", + "title": "Organize threads", + "summary": "Group, sort, move, settle, archive, and restore conversations in the project sidebar.", + "topic": "Work in Scient", + "sourcePath": "docs/user/thread-sidebar.md", + "sha256": "ca3d67408ce06ec421bccafd4fb10854809854b8e89924ef8031504e5a375045" + }, + { + "slug": "conversation-forks", + "title": "Fork a conversation", + "summary": "Continue from an exact message boundary in the same workspace or an isolated worktree.", + "topic": "Work in Scient", + "sourcePath": "docs/user/conversation-forks.md", + "sha256": "d680bd2ea14e13599a0c2e94194829ab52cdcf6162b96598e49d329ec63a3a02" + }, + { + "slug": "permission-modes", + "title": "Permission modes", + "summary": "Choose provider-specific approval behavior and understand what each mode can authorize.", + "topic": "Work in Scient", + "sourcePath": "docs/user/permission-modes.md", + "sha256": "9c6fb87d74a44642a759fd33fbe34bf01d9fda19ae006088b1a049bad8466c3e" + }, + { + "slug": "keybindings", + "title": "Keybindings", + "summary": "Use current shortcuts, remap commands, and resolve conflicts in the authoritative keybindings file.", + "topic": "Work in Scient", + "sourcePath": "docs/user/keybindings.md", + "sha256": "3fb66a3bce86548259a86d30abf8a26e62650ce40635ed821a5c0b8ecb2923dc" + }, + { + "slug": "project-settings", + "title": "Project settings", + "summary": "Customize project identity and understand which settings are local, persisted, or environment-owned.", + "topic": "Work in Scient", + "sourcePath": "docs/user/project-settings.md", + "sha256": "366d510d1c54964333f978fbacb6276ecf57f87fdad608258438d696babbf659" + }, + { + "slug": "voice-dictation", + "title": "Voice dictation", + "summary": "Transcribe locally, choose a Whisper model, and optionally correct transcript text with a provider.", + "topic": "Work in Scient", + "sourcePath": "docs/user/voice-dictation.md", + "sha256": "05f0eab0640a3be550d250509c27b0a893af9cd5dd56172177a1d6e7e6c7c0b3" + }, + { + "slug": "usage", + "title": "Review usage", + "summary": "Review local provider transcript usage, token totals, cost provenance, and source diagnostics.", + "topic": "Work in Scient", + "sourcePath": "docs/user/usage.md", + "sha256": "47822d730dbf51ed8cddda54b6c1b6b3bce25c63005b063bc855c286ae04c98f" + }, { "slug": "file-previews", "title": "File previews", "summary": "Open project and environment files, refresh previews, and understand current file limits.", - "topic": "Files and documents", + "topic": "Files and research", "sourcePath": "docs/user/file-previews.md", "sha256": "d608b0def0b5103c95e982286589369c8ed2b127dc2749aaca86d2e9329db62e" }, @@ -54,25 +134,89 @@ "slug": "pdf-reader", "title": "PDF reader", "summary": "Read, search, navigate, and save copies of PDFs while preserving reading state.", - "topic": "Files and documents", + "topic": "Files and research", "sourcePath": "docs/user/pdf-reader.md", "sha256": "cb565e77f934796ce06937cc5d0624b6cc088e599b9fcc85f39db64c4cd1c63f" }, + { + "slug": "sources", + "title": "Sources and PDF import", + "summary": "Import PDFs or Zotero records, review evidence and citations, and work with bounded agent tools.", + "topic": "Files and research", + "sourcePath": "docs/user/sources.md", + "sha256": "0d5ce584b2684eccda6021da5d6d8911299e46667cbbff1af5e4cbf398953ff6" + }, { "slug": "latex", "title": "LaTeX", "summary": "Build LaTeX projects, manage toolchains, inspect diagnostics, and navigate with SyncTeX.", - "topic": "Files and documents", + "topic": "Files and research", "sourcePath": "docs/user/latex.md", "sha256": "184ab2ef560c16d8347afaef160b506f4959252ca134d76949784ab9396a1f32" }, + { + "slug": "matlab-run-file", + "title": "Run a MATLAB file", + "summary": "Verify an installed MATLAB runtime, run a saved file, inspect history, and promote results.", + "topic": "Files and research", + "sourcePath": "docs/user/matlab-run-file.md", + "sha256": "43f3173d7e110120099f8db3072b6a93195f4b041dc105bb41c78555ef8a00ef" + }, + { + "slug": "source-control", + "title": "Source control", + "summary": "Connect supported Git forges, authenticate in the execution environment, and manage pull requests.", + "topic": "Files and research", + "sourcePath": "docs/user/source-control.md", + "sha256": "600d938724d7d3c5dffa9f51ed839e3569835889f1d3f18627e366489df6b4db" + }, + { + "slug": "content-direction", + "title": "Conversation text direction", + "summary": "Control automatic, RTL, or LTR conversation layout while preserving technical content.", + "topic": "Rich chat", + "sourcePath": "docs/user/content-direction.md", + "sha256": "c700225b3cdfc45d1994ca3b3f6d7ba95d130b0ed0425910773ef08dfdc432c4" + }, + { + "slug": "math-in-chat", + "title": "Math in chat", + "summary": "Write and inspect inline or display math with safe rendering, copy, and fallback behavior.", + "topic": "Rich chat", + "sourcePath": "docs/user/math-in-chat.md", + "sha256": "52cebb454c1c275d97d62a56b16f75e6e2488ecd3bc5af05ca1b75db14ec9e53" + }, + { + "slug": "diagrams-in-chat", + "title": "Diagrams in chat", + "summary": "Render, inspect, copy, and export secure Mermaid diagrams from settled messages.", + "topic": "Rich chat", + "sourcePath": "docs/user/diagrams-in-chat.md", + "sha256": "481bad02569517e3d825c15de229dc3f5663903fdc3cab3f819107e7c54348f2" + }, + { + "slug": "images-in-chat", + "title": "Images in chat", + "summary": "Display workspace-relative and remote images without crossing workspace authority boundaries.", + "topic": "Rich chat", + "sourcePath": "docs/user/images-in-chat.md", + "sha256": "16b17875835a0934a537188dd3f26893b7e72c35d49cac986cd5a0ec2d4929bf" + }, + { + "slug": "charts-in-chat", + "title": "Interactive charts", + "summary": "Use Vega-Lite and Plotly charts, preserve source fidelity, and export current views.", + "topic": "Rich chat", + "sourcePath": "docs/user/charts-in-chat.md", + "sha256": "93d908404894f1a45f26491e1b9e47023fe595ad46c35f657779d2d0c104a147" + }, { "slug": "providers", "title": "Providers", "summary": "Understand provider readiness, accounts, installations, remote authority, and recovery.", "topic": "Providers", "sourcePath": "docs/user/providers.md", - "sha256": "7af7778920ca42dd3e740ff309c1e1e31f09280ed0001e9e9053cb08ce313136" + "sha256": "8563b8b25326290fc4820eb385ca0eb233b69f0a065ce062f8d65799eac3ba38" }, { "slug": "providers-codex", @@ -80,7 +224,7 @@ "summary": "Connect Codex, choose a runtime, and manage one or more compatible account homes.", "topic": "Providers", "sourcePath": "docs/user/providers-codex.md", - "sha256": "0dd51ebb5a790382a121a0aeca0f2403deff677ad2e0ecd271245a8548771653" + "sha256": "ba910e44aa65df5a47bd5178ad191a6f8409c4e36fc2dbe6eab1dbac9f426191" }, { "slug": "providers-claude", @@ -121,6 +265,22 @@ "topic": "Providers", "sourcePath": "docs/user/providers-cursor.md", "sha256": "7be3cbcad434648a0c0aba8977fb3df948c53c0a7f21c514ce3a074952eaa5b6" + }, + { + "slug": "remote-access", + "title": "Remote environments", + "summary": "Connect over desktop-managed SSH or an advanced direct/private-network path without crossing environment authority.", + "topic": "Remote environments", + "sourcePath": "docs/user/remote-access.md", + "sha256": "c3029be49c6cdd99ff7c0bcac574ac471688e8f90e8ea40df96d9b40b4db465f" + }, + { + "slug": "background-service", + "title": "Background service", + "summary": "Install, inspect, update, or remove a user-scoped Scient server service on Linux or macOS.", + "topic": "Remote environments", + "sourcePath": "docs/user/background-service.md", + "sha256": "478d9df277a35dbf12c488117aaac4a98c8c465129395280adf9ba9ffbd49921" } ] } diff --git a/src/components/ScientDocsShell.astro b/src/components/ScientDocsShell.astro index f73147a..ef16a03 100644 --- a/src/components/ScientDocsShell.astro +++ b/src/components/ScientDocsShell.astro @@ -101,7 +101,11 @@ const shortRevision = index.source.revision.slice(0, 10); .docs-sidebar { position: sticky; top: 1.5rem; + max-height: calc(100svh - 3rem); padding: 1.25rem 0 2rem; + overflow-y: auto; + overscroll-behavior: contain; + scrollbar-gutter: stable; } .docs-mobile-navigation { @@ -367,9 +371,13 @@ const shortRevision = index.source.revision.slice(0, 10); } .docs-mobile-navigation > :global(nav) { + max-height: min(70svh, 36rem); margin: 0 1rem 1rem; padding-top: 1rem; border-top: 1px solid var(--line); + overflow-y: auto; + overscroll-behavior: contain; + scrollbar-gutter: stable; } .docs-provenance { diff --git a/src/pages/docs.astro b/src/pages/docs.astro index 5106c2c..b6e98ac 100644 --- a/src/pages/docs.astro +++ b/src/pages/docs.astro @@ -15,17 +15,17 @@ const topics = [...new Set(docsIndex.pages.map((page) => page.topic))];

Documentation

Learn Scient from the source.

- These pilot guides are generated from reviewed Scient Desktop Help at an exact commit. Search - the current preview, browse by topic, or open any page's raw Markdown and source evidence. + These guides are generated from reviewed Scient Desktop Help at an exact commit. Search the + current preview, browse by topic, or open any page's raw Markdown and source evidence.