vitrine

restore slug.go, slug_test.go, style.css to tracking

commit 1ed81d60bd39f2bdf80daa165c0930406e3b6229 · parent 1a3eccc323 · browse this tree
Ximianth, 2026-07-24 16:05:40 UTC

The 'add moonshot-1-mem' commit accidentally recorded these files as
deleted (staging mishap; the working tree copies were never harmed).
A fresh clone of the previous commit would not have built.
Contents here are identical to what the styling-pass commit introduced,
as the working tree carried them forward the whole time.
diff --git a/slug.go b/slug.go
new file mode 100644
index 0000000..ac36e22
--- /dev/null
+++ b/slug.go
@@ -0,0 +1,84 @@
+package main
+
+import (
+	"strconv"
+	"strings"
+
+	"github.com/yuin/goldmark/ast"
+	"github.com/yuin/goldmark/parser"
+	"github.com/yuin/goldmark/text"
+)
+
+// slugify turns heading text into a URL fragment:
+// lowercase, alphanumerics kept, every other run of characters becomes one dash.
+func slugify(s string) string {
+	var b strings.Builder
+	dash := false
+	for _, r := range strings.ToLower(s) {
+		switch {
+		case r >= 'a' && r <= 'z' || r >= '0' && r <= '9':
+			if dash && b.Len() > 0 {
+				b.WriteByte('-')
+			}
+			dash = false
+			b.WriteRune(r)
+		default:
+			dash = true
+		}
+	}
+	return b.String()
+}
+
+// slugIDs implements parser.IDs so goldmark assigns our slugs as heading ids.
+// Duplicate headings get "-2", "-3", ... suffixes.
+type slugIDs struct {
+	used map[string]bool
+}
+
+func newSlugIDs() *slugIDs {
+	return &slugIDs{used: make(map[string]bool)}
+}
+
+func (s *slugIDs) Generate(value []byte, kind ast.NodeKind) []byte {
+	slug := slugify(string(value))
+	if slug == "" {
+		slug = "section"
+	}
+	candidate := slug
+	for n := 2; s.used[candidate]; n++ {
+		candidate = slug + "-" + strconv.Itoa(n)
+	}
+	s.used[candidate] = true
+	return []byte(candidate)
+}
+
+func (s *slugIDs) Put(value []byte) {
+	s.used[string(value)] = true
+}
+
+// headingAnchors is a goldmark AST transformer that appends a chainlink
+// anchor to every heading that received an id, linking to its own fragment.
+// Pure HTML output: works without javascript.
+type headingAnchors struct{}
+
+func (headingAnchors) Transform(doc *ast.Document, reader text.Reader, pc parser.Context) {
+	_ = ast.Walk(doc, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
+		if !entering {
+			return ast.WalkContinue, nil
+		}
+		h, ok := n.(*ast.Heading)
+		if !ok {
+			return ast.WalkContinue, nil
+		}
+		id, ok := h.AttributeString("id")
+		if !ok {
+			return ast.WalkContinue, nil
+		}
+		link := ast.NewLink()
+		link.Destination = append([]byte("#"), id.([]byte)...)
+		link.SetAttributeString("class", []byte("anchor"))
+		link.AppendChild(link, ast.NewString([]byte("\U0001F517"))) // chainlink emoji
+		h.AppendChild(h, link)
+		return ast.WalkContinue, nil
+	})
+}
diff --git a/slug_test.go b/slug_test.go
new file mode 100644
index 0000000..55a0050
--- /dev/null
+++ b/slug_test.go
@@ -0,0 +1,50 @@
+package main
+
+import (
+	"strings"
+	"testing"
+)
+
+func TestSlugify(t *testing.T) {
+	cases := map[string]string{
+		"What the verifier catches": "what-the-verifier-catches",
+		"Use before definition":     "use-before-definition",
+		"C++ / FFI (part 2)":        "c-ffi-part-2",
+		"  spaces  everywhere  ":    "spaces-everywhere",
+		"???":                       "",
+	}
+	for in, want := range cases {
+		if got := slugify(in); got != want {
+			t.Errorf("slugify(%q) = %q, want %q", in, got, want)
+		}
+	}
+}
+
+func TestHeadingAnchorsInRenderedPage(t *testing.T) {
+	srv := newTestServer(t)
+	rec := get(t, srv, "/demo/README.md")
+	body := rec.Body.String()
+	if !strings.Contains(body, `<h1 id="hello">`) {
+		t.Errorf("heading missing slug id; body:\n%s", body)
+	}
+	if !strings.Contains(body, `<a href="#hello" class="anchor">`) {
+		t.Errorf("heading missing chainlink anchor; body:\n%s", body)
+	}
+}
+
+func TestDuplicateHeadingsGetDistinctIDs(t *testing.T) {
+	srv := newTestServer(t)
+	// dup.md is written by newTestServer with two identical headings.
+	rec := get(t, srv, "/demo/dup.md")
+	body := rec.Body.String()
+	if !strings.Contains(body, `id="twice"`) || !strings.Contains(body, `id="twice-2"`) {
+		t.Errorf("duplicate headings not disambiguated; body:\n%s", body)
+	}
+}
+
+func TestStylesheetServed(t *testing.T) {
+	rec := get(t, newTestServer(t), "/-/style.css")
+	if rec.Code != 200 || !strings.HasPrefix(rec.Header().Get("Content-Type"), "text/css") {
+		t.Errorf("status = %d, content-type = %q", rec.Code, rec.Header().Get("Content-Type"))
+	}
+}
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..34676d3
--- /dev/null
+++ b/style.css
@@ -0,0 +1,102 @@
+/* repoview stylesheet.  Embedded in the binary; served at /-/style.css. */
+
+:root {
+	--ink: #24292f;
+	--ink-soft: #57606a;
+	--accent: #0a5fa4;
+	--rule: #d0d7de;
+	--paper: #fdfdfc;
+	--well: #f3f5f7;
+	--well-edge: #c9d1d9;
+}
+
+body {
+	font-family: sans-serif;
+	color: var(--ink);
+	background: var(--paper);
+	max-width: 56rem;
+	margin: 1rem auto;
+	padding: 0 1rem;
+	line-height: 1.5;
+}
+
+a { color: var(--accent); }
+
+nav {
+	border-bottom: 2px solid var(--accent);
+	padding-bottom: .5rem;
+	margin-bottom: 1rem;
+}
+nav .raw {
+	float: right;
+	font-size: .85rem;
+	border: 1px solid var(--rule);
+	border-radius: .25rem;
+	padding: .1rem .5rem;
+	text-decoration: none;
+}
+nav .raw:hover { background: var(--well); }
+
+ul.listing { list-style: none; padding-left: 1.5rem; }
+ul.listing li { padding: .15rem 0; }
+
+iframe.passthrough {
+	width: 100%;
+	height: 75vh;
+	border: 1px solid var(--well-edge);
+	border-radius: .25rem;
+	background: white;
+}
+
+/* Headings sit flush left; all other article content is inset,
+   so headings read as structure at a glance. */
+article > * { margin-left: 1.5rem; }
+article > h1, article > h2, article > h3,
+article > h4, article > h5, article > h6 { margin-left: 0; }
+
+article h1, article h2 {
+	border-bottom: 1px solid var(--rule);
+	padding-bottom: .2rem;
+}
+article h1 { color: var(--accent); }
+
+/* Chainlink anchors: subtle until the heading is hovered. */
+a.anchor {
+	font-size: .7em;
+	text-decoration: none;
+	margin-left: .4rem;
+	opacity: 0;
+}
+h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor,
+h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor,
+a.anchor:focus { opacity: .6; }
+
+/* Code blocks: inset further than prose, bordered, in a well. */
+article pre {
+	margin-left: 3rem;
+	background: var(--well);
+	border: 1px solid var(--well-edge);
+	border-left: 3px solid var(--accent);
+	border-radius: .25rem;
+	padding: .6rem .8rem;
+	overflow-x: auto;
+}
+article code {
+	background: var(--well);
+	border-radius: .2rem;
+	padding: .05rem .25rem;
+}
+article pre code { background: none; padding: 0; }
+
+article blockquote {
+	border-left: 3px solid var(--rule);
+	color: var(--ink-soft);
+	padding-left: .8rem;
+}
+
+article table { border-collapse: collapse; }
+article th, article td {
+	border: 1px solid var(--rule);
+	padding: .2rem .5rem;
+}
+article th { background: var(--well); }