53b804b9db21
serve /docs with the self-hosting guide and /support from markdown in the repo, rendered once at startup, and record phase 6 in continuity (v3.0.32)
- time
- 2026-09-26 20:04 UTC · 2 weeks ago
- machine
- Junes-MacBook-Pro.local
- agent
- claude-opus-5-5
- session
- kepr 3 implementation
- parent
- 9a87fc577759
↓ kepr-53b804b9.tar.gzatlas at this save
files changed6 files
package.json+1 −1public/css/kepr.css+15 −0src/pages/docs.ts+23 −0src/pages/markdown.ts+7 −0src/server/app.ts+2 −0tests/front.test.ts+11 −0package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "kepr",3- "version": "3.0.31",3+ "version": "3.0.32",44 "private": true,55 "type": "module",66 "scripts": {public/css/kepr.css+15 −0
@@ -1881,6 +1881,21 @@18811881 border-top: 1px solid var(--bg-surface);18821882 }18831883 1884+/* ----- docs and support --------------------------------------------------- */1885+1886+.site-doc {1887+ max-width: 72ch;1888+ margin-top: 16px;1889+}1890+.site-doc h1 {1891+ font-size: clamp(2rem, 5vw, 3rem);1892+ font-weight: 400;1893+ color: var(--text-heading);1894+}1895+.site-doc h2 {1896+ margin-top: 2.4em;1897+}1898+18841899 @media (max-width: 860px) {18851900 .headline,18861901 .profile-portrait-section {src/pages/docs.ts+23 −0
@@ -0,0 +1,23 @@1+import { readFileSync } from "node:fs";2+import { join } from "node:path";3+import type { Hono } from "hono";4+import { describeFile } from "../describe/describe.ts";5+import { html } from "./html.ts";6+import { cacheFor, type PageContext, page } from "./layout.ts";7+import { renderParagraph, renderSiteMarkdown } from "./markdown.ts";8+9+const SITE_DIR = join(import.meta.dir, "../../docs/site");10+11+/** §21.10: `/docs` and `/support` are Markdown files in Kepr's repo, rendered once at startup. */12+export function registerDocsPages(app: Hono, ctx: PageContext): void {13+ for (const name of ["docs", "support"]) {14+ const source = readFileSync(join(SITE_DIR, `${name}.md`), "utf8");15+ const intro = describeFile(`${name}.md`, source);16+ const description = intro ? renderParagraph(intro, { repo: "", dir: "", at: null }).text : "";17+ const body = html`<article class="site-doc markdown">${renderSiteMarkdown(source)}</article>`;18+ app.get(`/${name}`, (c) => {19+ cacheFor(c, false);20+ return c.html(page(ctx, { title: name, description, path: `/${name}`, body }));21+ });22+ }23+}src/pages/markdown.ts+7 −0
@@ -16,6 +16,8 @@1616 };1717 md.core.ruler.push("kepr_raw_links", (state) => {1818 const base = state.env as MarkdownBase;19+ // Kepr's own pages (docs, support) have no repo, so their links stay as written.20+ if (!base.repo) return;1921 const visit = (tokens: typeof state.tokens) => {2022 for (const token of tokens) {2123 const attr = token.type === "link_open" ? "href" : token.type === "image" ? "src" : null;@@ -37,6 +39,11 @@3739 3840 export function renderMarkdown(source: string, base: MarkdownBase): Html {3941 return trusted(md.render(source, base));42+}43+44+/** One of Kepr's own Markdown pages, such as the docs (§21.10). */45+export function renderSiteMarkdown(source: string): Html {46+ return trusted(md.render(source, {}));4047 }4148 4249 /**src/server/app.ts+2 −0
@@ -7,6 +7,7 @@77 import { isSafePath } from "../bucket/repo.ts";88 import { registerIssuePages } from "../issues/pages.ts";99 import { registerArchivesPage } from "../pages/archives.ts";10+import { registerDocsPages } from "../pages/docs.ts";1011 import { registerFileRoutes } from "../pages/files.ts";1112 import { registerFrontPages } from "../pages/front.ts";1213 import { registerHousekeeping } from "../pages/housekeeping.ts";@@ -62,6 +63,7 @@6263 const ctx = { domain: config.domain, kepr, mirrorDir: join(config.dataDir, "mirror") };6364 registerHousekeeping(app, ctx);6465 registerFrontPages(app, ctx);66+ registerDocsPages(app, ctx);6567 registerProfilePage(app, ctx);6668 registerRepoPage(app, ctx);6769 registerFileRoutes(app, ctx);tests/front.test.ts+11 −0
@@ -195,6 +195,17 @@195195 });196196 });197197 198+describe("docs and support", () => {199+ test("render the repo's Markdown, links left as written", async () => {200+ const docs = await body("/docs");201+ expect(docs).toContain("<h1>Run your own library</h1>");202+ expect(docs).toContain("<code>admin_password_hash</code>");203+ const support = await body("/support");204+ expect(support).toContain('<a href="/docs">self-hosting docs</a>');205+ expect(support).toContain('<meta name="description" content="Found a bug');206+ });207+});208+198209 test("motion stops under prefers-reduced-motion", async () => {199210 const css = await Bun.file(join(import.meta.dir, "../public/css/kepr.css")).text();200211 const blocks = [...css.matchAll(/@media \(prefers-reduced-motion: reduce\) \{([\s\S]*?)\n\}/g)]