socle

the provenance ledger: the log drops its table

commit 65137bffd74f89b7ad0fb8cdbfcd727066656159 · parent dc0cfb64ec · browse this tree
Ximianth, 2026-10-26 23:18:31 UTC

A museum documents an object's history as provenance; the log page now
reads that way.  Each commit is a ledger row under a light hand-rule:
the subject as prose, wrapping freely, and a soft attribution line
whose short sha wears a chip outline (the frame tile's small sibling,
drawn so repeat: round can stretch to any text width) and reads as the
button to the commit page.  The subject links too, in quiet ink: tap
targets and forge habit both want it, and a hundred accent rows would
shout.  Dropping the table is also the phone-width fix: nothing is
left to crush.
diff --git a/chrome.go b/chrome.go
index ed8bc62..f616807 100644
--- a/chrome.go
+++ b/chrome.go
@@ -319,15 +319,43 @@ footer.ground .colophon {
 	margin: .5rem 0 1.1rem;
 }
 
-/* Listing and log tables. */
-article table.tree td, article table.tree th,
-article table.log td, article table.log th { border: none; padding: .15rem .6rem; }
-article table.tree tbody tr:hover, article table.log tbody tr:hover { background: var(--well); }
+/* The ledger: provenance rows on the log page.  Subjects are prose and wrap
+   freely; the attribution line stays soft and small.  Links wear ink, not accent:
+   a hundred accent rows would shout; hover answers the "is it live" question. */
+article .provenance {
+	margin: -.4rem 0 1.6rem;
+	font-size: .78rem;
+	letter-spacing: .16em;
+	color: var(--ink-soft);
+}
+article ul.ledger { list-style: none; padding: 0; }
+ul.ledger li { padding: .5rem 0 .8rem; }
+ul.ledger .subject { margin: 0; max-width: 44rem; }
+ul.ledger .subject a { color: var(--ink); text-decoration: none; }
+ul.ledger .subject a:hover { color: var(--accent); text-decoration: underline; }
+ul.ledger .attrib {
+	margin: .3rem 0 0;
+	font-size: .78rem;
+	color: var(--ink-soft);
+}
+a.sha {
+	font-family: monospace;
+	color: var(--ink);
+	text-decoration: none;
+	display: inline-block;
+	padding: 0 .35rem;
+	margin-right: .35rem;
+}
+a.sha:hover { color: var(--accent); }
+
+/* Listing tables. */
+article table.tree td, article table.tree th { border: none; padding: .15rem .6rem; }
+article table.tree tbody tr:hover { background: var(--well); }
 article th { text-align: left; background: none; }
 /* Table links skip the underline: in a column of links it is pure noise;
    the accent color and the row hover carry the affordance.  Hover restores it. */
-article table.tree a, article table.log a { text-decoration: none; }
-article table.tree a:hover, article table.log a:hover { text-decoration: underline; }
+article table.tree a { text-decoration: none; }
+article table.tree a:hover { text-decoration: underline; }
 article td.name { white-space: nowrap; }
 article td.size { text-align: right; color: var(--ink-soft); white-space: nowrap; }
 article td.date { color: var(--ink-soft); white-space: nowrap; }
@@ -363,6 +391,18 @@ nav.crumbs, article h1, article h2 {
 	background-size: auto 10px;
 	padding-bottom: .55rem;
 }
+/* Each ledger row sits under its own light hand-rule; the chip outline around a
+   sha is the small sibling of the frames, so it reads as a button, not a well. */
+ul.ledger li {
+	background-image: %s;
+	background-position: bottom left;
+	background-repeat: repeat-x;
+	background-size: auto 9px;
+}
+a.sha {
+	border: 7px solid transparent;
+	border-image: %s 8 / 7px round;
+}
 /* Rough-ruled frames, as border-images.  The tile is sliced at 16 and the border is
    near that size, so the stroke renders at the weight it was drawn. */
 .exhibit {
@@ -388,7 +428,7 @@ nav.arc a.va:active { background-image: %s; }
 nav.arc a.vb:active { background-image: %s; }
 nav.arc a.vc:active { background-image: %s; }
 `,
-		u("flank"), u("ground"), u("plinth"), u("rule"), u("frame"), u("frame"),
+		u("flank"), u("ground"), u("plinth"), u("rule"), u("rule"), u("chip"), u("frame"), u("frame"),
 		u("circle-a"), u("circle-b"), u("circle-c"),
 		u("circle-b"), u("circle-c"), u("circle-a"),
 		u("circle-c"), u("circle-a"), u("circle-b"))
diff --git a/pages.go b/pages.go
index 9d01fd7..47f42b6 100644
--- a/pages.go
+++ b/pages.go
@@ -561,13 +561,18 @@ func looksBinary(b []byte) bool {
 
 // ---- log ----
 
+// The provenance ledger: no table.  Each commit is a row under a light hand-ruled
+// line -- the subject as prose, free to wrap, and a soft second line carrying the
+// sha (a chip-outlined button to the commit page), author, and absolute date.
+// This shape is also why the page survives phone widths.
 var logTmpl = template.Must(template.New("log").Parse(`<h1>log: {{.Title}}</h1>
-<table class="log">
-<thead><tr><th>commit</th><th>date</th><th>author</th><th>subject</th></tr></thead>
-<tbody>
-{{range .Rows}}<tr><td class="hash"><a href="{{.Href}}">{{.Sha}}</a></td><td class="date">{{.Date}}</td><td>{{.Author}}</td><td>{{.Subject}}</td></tr>
-{{end}}</tbody>
-</table>
+<p class="provenance">provenance · newest first</p>
+<ul class="ledger">
+{{range .Rows}}<li>
+<p class="subject"><a href="{{.Href}}">{{.Subject}}</a></p>
+<p class="attrib"><a class="sha" href="{{.Href}}">{{.Sha}}</a> {{.Author}} · {{.Date}}</p>
+</li>
+{{end}}</ul>
 {{if .Capped}}<p>Showing the {{.Limit}} most recent commits.</p>
 {{end}}`))
 
diff --git a/sketch.go b/sketch.go
index 16b5284..443bf4d 100644
--- a/sketch.go
+++ b/sketch.go
@@ -139,6 +139,7 @@ func sketchAssets(seed string) map[string][]byte {
 		m["flank-"+p.scheme+".svg"] = flankSVG(seed, p)
 		m["ground-"+p.scheme+".svg"] = groundSVG(seed, p)
 		m["plinth-"+p.scheme+".svg"] = plinthSVG(seed, p)
+		m["chip-"+p.scheme+".svg"] = chipSVG(seed, p)
 		for _, v := range []string{"a", "b", "c"} {
 			m["circle-"+v+"-"+p.scheme+".svg"] = circleSVG(seed+"/"+v, p)
 		}
@@ -339,3 +340,28 @@ func plinthSVG(seed string, p sketchPalette) []byte {
 	sb.WriteString("</svg>\n")
 	return []byte(sb.String())
 }
+
+// chipSVG is the frame's small sibling: a border-image tile for inline chips
+// (the ledger's sha buttons).  A 48x48 tile sliced at 8 with lines at inset 4,
+// so a border a hair under the slice renders the stroke near 1:1; displacement
+// pins to zero at the slice crossings, same scheme as frameSVG, so repeat: round
+// tiles the middles cleanly at whatever width the chip's text wants.
+func chipSVG(seed string, p sketchPalette) []byte {
+	rng := newSketchRand(seed + "/chip/" + p.scheme)
+	var sb strings.Builder
+	svgOpen(&sb, 48, 48)
+	edge := func(d *strings.Builder, amp float64) {
+		j := func() float64 { return rng.rangef(-amp, amp) }
+		wobblePath(d, rng, []pt{{1.8 + j(), 4 + j()}, {8, 4}, {24 + j(), 4 + j()}, {40, 4}, {46.2 + j(), 4 + j()}}, amp)
+		wobblePath(d, rng, []pt{{44 + j(), 1.8 + j()}, {44, 8}, {44 + j(), 24 + j()}, {44, 40}, {44 + j(), 46.2 + j()}}, amp)
+		wobblePath(d, rng, []pt{{46.2 + j(), 44 + j()}, {40, 44}, {24 + j(), 44 + j()}, {8, 44}, {1.8 + j(), 44 + j()}}, amp)
+		wobblePath(d, rng, []pt{{4 + j(), 46.2 + j()}, {4, 40}, {4 + j(), 24 + j()}, {4, 8}, {4 + j(), 1.8 + j()}}, amp)
+	}
+	var d1, d2 strings.Builder
+	edge(&d1, 0.8)
+	edge(&d2, 1.1)
+	svgPath(&sb, d1.String(), p.ink, 1.2, 0.75)
+	svgPath(&sb, d2.String(), p.ink, 0.9, 0.28)
+	sb.WriteString("</svg>\n")
+	return []byte(sb.String())
+}
diff --git a/testdata/pages.md b/testdata/pages.md
index f5b0546..3b749bb 100644
--- a/testdata/pages.md
+++ b/testdata/pages.md
@@ -390,13 +390,17 @@ Content-Type: text/html; charset=utf-8
 <a class="vb" href="/fixturama/" title="repo home" style="--dy:2px">home</a>
 </nav>
 <main><article><h1>log: main</h1>
-<table class="log">
-<thead><tr><th>commit</th><th>date</th><th>author</th><th>subject</th></tr></thead>
-<tbody>
-<tr><td class="hash"><a href="/fixturama/commit/ced91a8519b59966124301e6383532f24223528b">ced91a8519</a></td><td class="date">2026-01-02</td><td>Ada Fixture</td><td>add docs and src</td></tr>
-<tr><td class="hash"><a href="/fixturama/commit/64ac67a35d36079e167cb8f929fd57f859c8b5d1">64ac67a35d</a></td><td class="date">2026-01-02</td><td>Ada Fixture</td><td>inaugurate with a readme</td></tr>
-</tbody>
-</table>
+<p class="provenance">provenance · newest first</p>
+<ul class="ledger">
+<li>
+<p class="subject"><a href="/fixturama/commit/ced91a8519b59966124301e6383532f24223528b">add docs and src</a></p>
+<p class="attrib"><a class="sha" href="/fixturama/commit/ced91a8519b59966124301e6383532f24223528b">ced91a8519</a> Ada Fixture · 2026-01-02</p>
+</li>
+<li>
+<p class="subject"><a href="/fixturama/commit/64ac67a35d36079e167cb8f929fd57f859c8b5d1">inaugurate with a readme</a></p>
+<p class="attrib"><a class="sha" href="/fixturama/commit/64ac67a35d36079e167cb8f929fd57f859c8b5d1">64ac67a35d</a> Ada Fixture · 2026-01-02</p>
+</li>
+</ul>
 </article></main>
 </div>
 <footer class="ground">
diff --git a/testdata/sketch.md b/testdata/sketch.md
index b55977c..8ee5cdf 100644
--- a/testdata/sketch.md
+++ b/testdata/sketch.md
@@ -56,6 +56,16 @@ Regenerate with `go test -run TestSketchSnapshots -testmark.regen` and review th
 </svg>
 ```
 
+### chip-light.svg
+
+[testmark]:# (chip-light.svg)
+```svg
+<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
+<path d="M2.4 4.1C4.3 4.1 6.1 4.2 8.0 4.0C13.3 3.7 18.6 4.1 23.9 3.4C29.2 3.5 34.6 4.4 40.0 4.0C42.3 3.7 44.6 4.5 46.9 3.6M43.2 2.2C43.3 4.2 43.7 6.1 44.0 8.0C44.1 13.3 44.3 18.6 44.2 24.0C43.9 29.3 44.6 34.7 44.0 40.0C43.9 42.1 44.4 44.3 44.0 46.4M45.9 44.8C43.9 44.9 41.9 44.8 40.0 44.0C34.5 44.4 29.0 43.6 23.5 44.0C18.3 44.2 13.2 44.7 8.0 44.0C5.8 44.0 3.8 45.1 1.6 44.6M4.0 46.9C4.4 44.6 4.8 42.3 4.0 40.0C3.9 34.5 3.5 29.1 4.1 23.6C3.5 18.4 3.4 13.2 4.0 8.0C4.4 6.2 3.3 4.3 4.1 2.5" fill="none" stroke="#24292f" stroke-width="1.2" stroke-opacity="0.75" stroke-linecap="round"/>
+<path d="M2.6 3.9C4.4 3.1 6.2 4.6 8.0 4.0C13.3 4.1 18.6 3.8 23.8 4.8C29.2 4.4 34.6 3.5 40.0 4.0C42.1 4.7 43.7 2.4 45.7 3.0M45.1 1.1C44.1 3.3 43.5 5.6 44.0 8.0C43.5 13.5 44.4 19.0 43.5 24.5C43.0 29.7 43.5 34.9 44.0 40.0C44.6 42.1 44.8 44.3 43.8 46.3M45.2 42.9C43.5 43.7 41.7 43.7 40.0 44.0C34.8 44.8 29.7 44.0 24.6 43.7C19.1 43.2 13.5 43.0 8.0 44.0C6.1 44.4 4.1 43.1 2.3 44.2M3.4 45.3C2.9 43.4 2.9 41.7 4.0 40.0C3.0 34.7 3.4 29.4 4.1 24.1C3.3 18.7 3.2 13.4 4.0 8.0C4.1 5.6 3.5 3.3 3.0 1.0" fill="none" stroke="#24292f" stroke-width="0.9" stroke-opacity="0.28" stroke-linecap="round"/>
+</svg>
+```
+
 ### circle-a-light.svg
 
 [testmark]:# (circle-a-light.svg)
@@ -137,6 +147,16 @@ Regenerate with `go test -run TestSketchSnapshots -testmark.regen` and review th
 </svg>
 ```
 
+### chip-dark.svg
+
+[testmark]:# (chip-dark.svg)
+```svg
+<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
+<path d="M1.3 3.6C3.5 3.9 5.8 3.8 8.0 4.0C13.1 4.4 18.2 4.6 23.4 4.8C28.9 4.8 34.5 4.8 40.0 4.0C41.9 4.4 43.9 4.0 45.7 4.8M44.5 1.7C44.2 3.8 44.0 5.9 44.0 8.0C44.1 13.2 44.3 18.4 44.2 23.6C44.5 29.0 44.2 34.5 44.0 40.0C44.0 42.1 44.4 44.3 43.8 46.4M46.6 43.8C44.4 43.6 42.2 43.6 40.0 44.0C34.6 44.1 29.2 44.3 23.8 44.0C18.6 43.8 13.3 44.4 8.0 44.0C5.8 44.1 3.6 44.8 1.3 44.1M4.1 45.5C3.8 43.7 3.2 41.8 4.0 40.0C3.5 34.4 4.6 28.9 4.0 23.3C4.6 18.2 4.2 13.1 4.0 8.0C3.8 5.7 3.8 3.4 3.5 1.1" fill="none" stroke="#e6edf3" stroke-width="1.2" stroke-opacity="0.75" stroke-linecap="round"/>
+<path d="M1.7 3.3C3.8 3.7 6.0 3.2 8.0 4.0C13.1 4.3 18.2 4.4 23.3 3.5C28.9 2.8 34.4 3.6 40.0 4.0C42.4 4.3 44.6 2.7 47.0 3.4M43.4 1.4C43.9 3.6 43.0 5.9 44.0 8.0C44.0 13.5 43.3 19.1 43.9 24.6C43.7 29.8 44.7 34.9 44.0 40.0C44.4 41.7 44.3 43.5 44.7 45.2M47.1 43.6C44.8 44.7 42.4 44.8 40.0 44.0C34.4 44.3 28.8 44.1 23.2 43.2C18.2 44.3 13.1 43.8 8.0 44.0C5.9 43.9 3.9 43.8 1.8 43.9M5.0 46.9C4.8 44.6 5.3 42.2 4.0 40.0C4.1 34.8 5.1 29.7 4.7 24.6C4.0 19.1 4.2 13.5 4.0 8.0C4.1 5.6 4.9 3.3 5.0 1.0" fill="none" stroke="#e6edf3" stroke-width="0.9" stroke-opacity="0.28" stroke-linecap="round"/>
+</svg>
+```
+
 ### circle-a-dark.svg
 
 [testmark]:# (circle-a-dark.svg)