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
6 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 −0
package.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": {

view file

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 {

view file

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+}

view file

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 /**

view file

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);

view file

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)]

view file