From ef94fac4acc0cf139a7abdcf31914ce95769eb11 Mon Sep 17 00:00:00 2001
From: Nexoniarz
Date: Sat, 12 Sep 2026 21:53:06 +0200
Subject: [PATCH] docs: a Markdown reader on the site, and the flag stays up
Two additions to docs/Website.
**The reader.** docs.html renders the 29 documents in docs/ in the site's own
theme -- index down the left, table of contents down the right, and links
between documents rewritten so ../history/schfs.md navigates inside the
reader instead of downloading a file.
The parser is ~150 lines in docs.js, not a dependency: it handles exactly
what docs/ uses, and a Markdown library would outweigh every other asset here
combined. Everything is escaped before a tag is emitted, so the only HTML
that reaches the page is HTML docs.js wrote. Verified by rendering all 30
documents (302 KB of Markdown) with zero failures, no unrestored sentinels
and no runaway loops.
One caveat, stated in the page itself rather than hidden: browsers block
fetch() on file:// URLs, so this page needs a server. It says so, with the
command, and offers the raw file as a fallback. The rest of the site is
unaffected.
**The flag.** A Progress Pride flag in the top bar and a line in the footer,
on both pages, permanently. Support with an end date is marketing. It is
deliberately small and deliberately not a page of its own -- this is an
operating system, and the flag is a statement, not a subject.
Co-Authored-By: Claude Opus 5
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
---
docs/Website/Assets/pride.svg | 22 +++
docs/Website/README.md | 43 +++-
docs/Website/docs.html | 63 ++++++
docs/Website/docs.js | 362 ++++++++++++++++++++++++++++++++++
docs/Website/index.html | 19 +-
docs/Website/style.css | 207 +++++++++++++++++++
6 files changed, 708 insertions(+), 8 deletions(-)
create mode 100644 docs/Website/Assets/pride.svg
create mode 100644 docs/Website/docs.html
create mode 100644 docs/Website/docs.js
diff --git a/docs/Website/Assets/pride.svg b/docs/Website/Assets/pride.svg
new file mode 100644
index 0000000..be1b140
--- /dev/null
+++ b/docs/Website/Assets/pride.svg
@@ -0,0 +1,22 @@
+
diff --git a/docs/Website/README.md b/docs/Website/README.md
index 4007a92..c67ad36 100644
--- a/docs/Website/README.md
+++ b/docs/Website/README.md
@@ -5,12 +5,49 @@ Three files and a folder of marks. No build step, no dependencies, no
framework — open `index.html` and it works, including from a `file://` URL.
```
-index.html the page
+index.html the front page
+docs.html the documentation reader
style.css the palette, transcribed from include/theme.h
-app.js two behaviours: the boot log types, sections fade in
-Assets/ shift.svg (the logo), uberblock.svg (SchFS), guru.svg (PSOGM)
+app.js the front page: the boot log types, sections fade in
+docs.js the Markdown renderer and the doc index
+Assets/ shift.svg (logo), uberblock.svg (SchFS), guru.svg (PSOGM),
+ pride.svg
```
+## The docs reader
+
+`docs.html` renders the Markdown in `docs/` in the site's own theme, with the
+index down the left and a table of contents down the right. Links between
+documents are rewritten to stay inside the reader, so `../history/schfs.md`
+in an architecture page navigates rather than downloads.
+
+The parser is about 150 lines in `docs.js` and handles what `docs/` actually
+uses: headings, fenced code, tables, lists, quotes, rules and inline marks.
+A Markdown library would be larger than everything else here put together.
+Every document is escaped before a single tag is emitted — the only HTML that
+reaches the page is HTML `docs.js` wrote.
+
+**This one page needs a server.** Browsers refuse `fetch()` on `file://` for
+the same reason they refuse it cross-origin, so opening `docs.html` from a
+file gives a panel explaining that instead of the docs. Everything else works
+from a file. To read them locally:
+
+```sh
+cd /home/nexoniarz/Dokumenty/Projects/SchOS
+python3 -m http.server 8000
+# then http://localhost:8000/docs/Website/docs.html
+```
+
+Adding a document means adding one line to the `DOCS` array in `docs.js`. The
+reader will only load a path that appears there.
+
+## The flag
+
+`Assets/pride.svg` is in the top bar and in the footer, on both pages, and it
+is not seasonal. Support with an end date is marketing; this is a project that
+belongs to whoever writes it. It is also deliberately small — the site is
+about an operating system, and the flag is a statement, not a subject.
+
## The rules it follows
**The palette is not a design decision.** It is `include/theme.h`, copied. A
diff --git a/docs/Website/docs.html b/docs/Website/docs.html
new file mode 100644
index 0000000..77a0481
--- /dev/null
+++ b/docs/Website/docs.html
@@ -0,0 +1,63 @@
+
+
+
+
+
+Docs — SchOS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/Website/docs.js b/docs/Website/docs.js
new file mode 100644
index 0000000..10061d8
--- /dev/null
+++ b/docs/Website/docs.js
@@ -0,0 +1,362 @@
+/* SPDX-License-Identifier: Apache-2.0
+ *
+ * The documentation reader.
+ *
+ * Renders the project's Markdown in the site's own theme, so the docs can be
+ * read without leaving the page and without a build step. No dependency: the
+ * parser below handles exactly what docs/ actually uses -- headings, fenced
+ * code, tables, lists, quotes, and inline marks. Pulling in a 40 KB Markdown
+ * library to render 29 files would outweigh every other asset here combined.
+ *
+ * Everything is escaped before any tag is emitted. The only HTML that reaches
+ * the page is HTML this file wrote.
+ */
+(function () {
+ "use strict";
+
+ /* ---- the index ---------------------------------------------------- */
+
+ var DOCS = [
+ { group: "Start here", items: [
+ ["README.md", "Overview"],
+ ["plan/1.0.md", "The 1.0 definition"],
+ ["licensing.md", "Licensing"]
+ ]},
+ { group: "Architecture", items: [
+ ["architecture/README.md", "The system, layer by layer"],
+ ["architecture/boot.md", "Boot"],
+ ["architecture/kernel.md", "Kernel"],
+ ["architecture/syscalls.md", "Syscalls"],
+ ["architecture/filesystem.md", "The VFS"],
+ ["architecture/schfs.md", "SchFS"],
+ ["architecture/devices.md", "Device model"],
+ ["architecture/usb.md", "USB"],
+ ["architecture/graphics.md", "Graphics"],
+ ["architecture/networking.md", "Networking"],
+ ["architecture/init.md", "init"]
+ ]},
+ { group: "Guides", items: [
+ ["guides/building.md", "Building"],
+ ["guides/testing.md", "Testing"],
+ ["guides/layout.md", "Tree layout"],
+ ["guides/adding-a-driver.md", "Adding a driver"]
+ ]},
+ { group: "Plan", items: [
+ ["plan/README.md", "What plan/ is for"],
+ ["plan/roadmap.md", "Roadmap"],
+ ["plan/foundations.md", "Foundations"],
+ ["plan/init.md", "schinit"],
+ ["plan/wayland.md", "Wayland"],
+ ["plan/hybrid.md", "Hybrid kernel"],
+ ["plan/schapi.md", "SchAPI"]
+ ]},
+ { group: "History", items: [
+ ["history/README.md", "Why history/ exists"],
+ ["history/kernel.md", "Kernel"],
+ ["history/schfs.md", "SchFS"],
+ ["history/moving-to-linux.md", "Moving to Linux"]
+ ]},
+ { group: "Branding", items: [
+ ["branding/README.md", "The three marks"]
+ ]}
+ ];
+
+ /* ---- helpers ------------------------------------------------------- */
+
+ function esc(s) {
+ return s.replace(/&/g, "&").replace(//g, ">");
+ }
+
+ /* Resolve a doc-relative link against the doc it appeared in, so
+ "../history/schfs.md" inside architecture/ lands on history/schfs.md. */
+ function resolve(from, href) {
+ var base = from.split("/").slice(0, -1);
+ var parts = href.split("/");
+ for (var i = 0; i < parts.length; i++) {
+ if (parts[i] === "." || parts[i] === "") continue;
+ if (parts[i] === "..") base.pop();
+ else base.push(parts[i]);
+ }
+ return base.join("/");
+ }
+
+ /* encodeURIComponent turns every "/" into %2F, which works but makes a
+ shared link unreadable. Slashes are legal in a query value, so keep them. */
+ function docHref(p) {
+ return "?doc=" + encodeURIComponent(p).replace(/%2F/g, "/");
+ }
+
+ function slug(s) {
+ return s.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-");
+ }
+
+ /* ---- inline marks --------------------------------------------------- */
+
+ /* U+0001 as the placeholder sentinel: it cannot occur in a Markdown source
+ file, so a code span lifted out here can never be confused with document
+ text when it is put back. */
+ var SENTINEL = "\u0001";
+
+ function inline(s, from) {
+ /* Code spans come out first and go back last, so nothing inside a backtick
+ pair is mistaken for emphasis or for a link. */
+ var spans = [];
+ s = s.replace(/`([^`]+)`/g, function (_, code) {
+ spans.push("" + esc(code) + "");
+ return SENTINEL + (spans.length - 1) + SENTINEL;
+ });
+
+ s = esc(s);
+
+ s = s.replace(/!\[([^\]]*)\]\(([^)\s]+)[^)]*\)/g, function (_, alt, src) {
+ return '';
+ });
+
+ s = s.replace(/\[([^\]]+)\]\(([^)\s]+)[^)]*\)/g, function (_, text, href) {
+ if (/^https?:/.test(href))
+ return '' + text + "";
+ if (href.charAt(0) === "#")
+ return '' + text + "";
+ if (/\.md($|#)/.test(href)) {
+ var cut = href.indexOf("#");
+ var hash = cut >= 0 ? href.slice(cut) : "";
+ var path = cut >= 0 ? href.slice(0, cut) : href;
+ return '' + text + "";
+ }
+ /* Anything else is a file in the repo rather than a document. Link at it
+ relative to docs/, which is where this page's ../ points. */
+ return '' + text + "";
+ });
+
+ s = s.replace(/\*\*([^*]+)\*\*/g, "$1");
+ s = s.replace(/(^|[^*])\*([^*\n]+)\*/g, "$1$2");
+
+ var restore = new RegExp(SENTINEL + "(\\d+)" + SENTINEL, "g");
+ return s.replace(restore, function (_, n) { return spans[+n]; });
+ }
+
+ /* ---- blocks --------------------------------------------------------- */
+
+ function render(md, from, headings) {
+ md = md.replace(/\r\n?/g, "\n").replace(//g, "");
+ var L = md.split("\n"), out = [], i = 0, m;
+
+ function listBlock(ordered) {
+ var tag = ordered ? "ol" : "ul", items = [];
+ while (i < L.length) {
+ var li = L[i].match(ordered ? /^\s*\d+\.\s+(.*)$/ : /^\s*[-*+]\s+(.*)$/);
+ if (!li) {
+ /* an indented continuation line belongs to the item above it */
+ if (items.length && /^\s+\S/.test(L[i])) {
+ items[items.length - 1] += " " + L[i].trim();
+ i++;
+ continue;
+ }
+ break;
+ }
+ items.push(li[1]);
+ i++;
+ }
+ out.push("<" + tag + ">");
+ items.forEach(function (t) { out.push("
" + inline(t, from) + "
"); });
+ out.push("" + tag + ">");
+ }
+
+ function cells(row) {
+ return row.replace(/^\s*\|/, "").replace(/\|\s*$/, "").split("|")
+ .map(function (c) { return c.trim(); });
+ }
+
+ while (i < L.length) {
+ var line = L[i];
+
+ if (!line.trim()) { i++; continue; }
+
+ /* fenced code */
+ if (/^\s*```/.test(line)) {
+ var lang = line.replace(/^\s*```/, "").trim(), buf = [];
+ i++;
+ while (i < L.length && !/^\s*```/.test(L[i])) buf.push(L[i++]);
+ i++;
+ out.push('
" + esc(buf.join("\n")) + "
");
+ continue;
+ }
+
+ /* table: a pipe row followed by a separator row */
+ if (/^\s*\|/.test(line) && /^\s*\|?[\s:|-]+$/.test(L[i + 1] || "") && /-/.test(L[i + 1] || "")) {
+ var head = cells(line);
+ i += 2;
+ out.push('
');
+ head.forEach(function (c) { out.push("
" + inline(c, from) + "
"); });
+ out.push("
");
+ while (i < L.length && /^\s*\|/.test(L[i])) {
+ out.push("
");
+ continue;
+ }
+
+ /* heading */
+ if ((m = line.match(/^(#{1,6})\s+(.*?)\s*#*$/))) {
+ var lvl = m[1].length, text = m[2], id = slug(text);
+ if (lvl <= 3) headings.push({ level: lvl, text: text, id: id });
+ out.push("' + inline(text, from) + "");
+ i++;
+ continue;
+ }
+
+ /* rule */
+ if (/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) { out.push(""); i++; continue; }
+
+ /* blockquote */
+ if (/^\s*>/.test(line)) {
+ var q = [];
+ while (i < L.length && /^\s*>/.test(L[i])) q.push(L[i++].replace(/^\s*>\s?/, ""));
+ out.push("
" + render(q.join("\n"), from, []) + "
");
+ continue;
+ }
+
+ if (/^\s*[-*+]\s+/.test(line)) { listBlock(false); continue; }
+ if (/^\s*\d+\.\s+/.test(line)) { listBlock(true); continue; }
+
+ /* paragraph: runs to the next blank line or block opener */
+ var para = [];
+ while (i < L.length && L[i].trim() &&
+ !/^\s*(```|>|#{1,6}\s|[-*+]\s|\d+\.\s|\|)/.test(L[i]) &&
+ !/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(L[i])) {
+ para.push(L[i++]);
+ }
+ if (para.length) out.push("
" + inline(para.join(" "), from) + "
");
+ else i++; /* a block opener we do not handle: never spin on it */
+ }
+ return out.join("\n");
+ }
+
+ /* ---- the page ------------------------------------------------------- */
+
+ var nav = document.getElementById("docnav");
+ var article = document.getElementById("doc");
+ var tocEl = document.getElementById("toc");
+
+ function buildNav() {
+ DOCS.forEach(function (g) {
+ var h = document.createElement("p");
+ h.className = "navgroup";
+ h.textContent = g.group;
+ nav.appendChild(h);
+ var ul = document.createElement("ul");
+ g.items.forEach(function (it) {
+ var li = document.createElement("li");
+ var a = document.createElement("a");
+ a.href = docHref(it[0]);
+ a.textContent = it[1];
+ a.dataset.path = it[0];
+ li.appendChild(a);
+ ul.appendChild(li);
+ });
+ nav.appendChild(ul);
+ });
+ }
+
+ function markCurrent(path) {
+ Array.prototype.forEach.call(nav.querySelectorAll("a"), function (a) {
+ a.classList.toggle("on", a.dataset.path === path);
+ });
+ }
+
+ function buildToc(headings) {
+ tocEl.innerHTML = "";
+ var real = headings.filter(function (h) { return h.level >= 2; });
+ if (real.length < 2) { tocEl.hidden = true; return; }
+ tocEl.hidden = false;
+ var t = document.createElement("p");
+ t.className = "navgroup";
+ t.textContent = "On this page";
+ tocEl.appendChild(t);
+ var ul = document.createElement("ul");
+ real.forEach(function (h) {
+ var li = document.createElement("li");
+ li.className = "lvl" + h.level;
+ var a = document.createElement("a");
+ a.href = "#" + h.id;
+ a.textContent = h.text;
+ li.appendChild(a);
+ ul.appendChild(li);
+ });
+ tocEl.appendChild(ul);
+ }
+
+ function fail(title, body) {
+ article.innerHTML = "
" + title + "
" + body;
+ tocEl.hidden = true;
+ }
+
+ function load(path) {
+ markCurrent(path);
+ document.title = "Docs — SchOS";
+ article.setAttribute("aria-busy", "true");
+
+ fetch("../" + path).then(function (r) {
+ if (!r.ok) throw new Error(r.status + " " + r.statusText);
+ return r.text();
+ }).then(function (text) {
+ var headings = [];
+ article.innerHTML = render(text, path, headings);
+ buildToc(headings);
+ article.removeAttribute("aria-busy");
+ if (headings.length) document.title = headings[0].text + " — SchOS docs";
+ var hash = location.hash.slice(1);
+ var target = hash && document.getElementById(hash);
+ if (target) target.scrollIntoView();
+ else window.scrollTo(0, 0);
+ }).catch(function (e) {
+ article.removeAttribute("aria-busy");
+ /* The common case, and worth naming precisely: browsers refuse fetch()
+ on file:// for the same reason they refuse it cross-origin, so this
+ one page needs a server even though nothing here is dynamic. */
+ if (location.protocol === "file:") {
+ fail("This page needs a local server",
+ "
Browsers block fetch() on file:// URLs, so the " +
+ "reader cannot load the Markdown. The rest of the site works from a file; " +
+ "this page does not. Serve the repository root:
" +
+ '
cd /home/nexoniarz/Dokumenty/Projects/SchOS\n' +
+ "python3 -m http.server 8000
" +
+ "
Then open http://localhost:8000/docs/Website/docs.html.
');
+ }
+ });
+ }
+
+ function pathFromUrl() {
+ var m = /[?&]doc=([^]+)/.exec(location.search);
+ var p = m ? decodeURIComponent(m[1]) : "README.md";
+ /* Only ever load something that is in the index above. */
+ var known = DOCS.some(function (g) {
+ return g.items.some(function (it) { return it[0] === p; });
+ });
+ return known ? p : "README.md";
+ }
+
+ buildNav();
+ load(pathFromUrl());
+
+ /* Intercept in-page navigation so the reader does not reload its own shell. */
+ document.addEventListener("click", function (ev) {
+ var a = ev.target.closest ? ev.target.closest("a") : null;
+ if (!a || ev.metaKey || ev.ctrlKey || ev.shiftKey || a.target) return;
+ var href = a.getAttribute("href") || "";
+ if (href.indexOf("?doc=") !== 0) return;
+ ev.preventDefault();
+ history.pushState(null, "", href);
+ load(pathFromUrl());
+ });
+
+ window.addEventListener("popstate", function () { load(pathFromUrl()); });
+})();
diff --git a/docs/Website/index.html b/docs/Website/index.html
index 619b2a6..0239fe0 100644
--- a/docs/Website/index.html
+++ b/docs/Website/index.html
@@ -16,12 +16,15 @@
SchOS
+
+
+
@@ -263,17 +266,18 @@
plan/; and the bugs worth remembering are kept in
history/, in full, including the wrong turns.
SchOS — kernel SchIFT, filesystem SchFS.Apache-2.0, with the xv6-derived core under MIT.
+
+
+ Built by, and for, people of every orientation and gender identity.
+ The flag stays up all year.
+