mirror of
https://github.com/hanzoai/ci.git
synced 2026-10-20 15:28:05 +00:00
The dashboard carried its own :root block — a hand-copy of the house palette, and being a copy it had already drifted off it. The status colours were GitHub Primer's (#3fb950 / #f85149 / #d29922) where the house says #10b981 / #ef4444 / #f59e0b. Every surface black was a shade wrong and blue-tinted (#0b0b0d) against a scale that is strictly neutral (--surface-0 #080808). The hairline border was an opaque #25252b where the house hairline is a 6% white wash. The font stacks named neither Geist Sans nor JetBrains Mono. Of nine colours exactly one — the accent — still matched. That is what a second component source looks like a few months in, and it is why this page had to stop being one. The values now come from @hanzo/brand, and they arrive as that package's own published artifact rather than as hex codes retyped here: styles/variables.css, which it ships as a plain custom-property sheet (exports["./styles/*"], documented for a bare <link>), vendored verbatim and go:embed-ed. dashboard.css holds what is left over — layout: what is a row, what sticks, what collapses on a phone — and names no colour, radius or type size of its own. Not a @hanzo/gui port, deliberately. gui is React over Tamagui and needs a bundler, which would put npm and a JS build on the path that ships the board you read when the builds are broken, and would trade one request that returns the answer for a shell that fetches it a second later. What this page ever needed from the design system was its token vocabulary, not its components — and gui's own shell reads these same var(--hanzo-*) names, so a Go binary and a React app now spend one vocabulary from one source. go.mod stays empty; the image stays the binary and a CA bundle. Two offline gates keep it honest, because vendoring alone would only move the copy rather than end it: - TestBrandCSSIsUpstreamBytes pins the sheet to the sha256 of the version it claims to be. Without it, "just darken that one border" is a one-character local edit that silently rebuilds the second palette and nothing ever catches it. This is go.sum's argument. - TestDashboardCSSNamesNoColours fails on any hex or rgb() the page writes for itself. The old :root block did not arrive wrong; it arrived one reasonable exception at a time. Neither gate touches the network, so proving we use one design system costs the pipeline no npm and no registry. TestRenderedPageShowsOnlyTheViewersOrg additionally pins at the HTML layer what scope_test.go pins at the predicate layer: a lux viewer's rendered page contains no other org's rows and no other org's name in the nav. The renderer is where that leak lived, and it is now the renderer that is asserted. Rendered and checked in a browser, not just compiled: every token resolves (surface-0 #080808, text-primary #fafafa, accent-muted #a78bfa, success/error/warning #10b981/#ef4444/#f59e0b, hairline rgba(255,255,255, .06), radius-card 8px, JetBrains Mono), color-scheme comes out dark via the sheet's own .dark hook, and header, chips, nav, table and footer now sit on one --space-6 gutter instead of the table drifting 12px left. Co-authored-by: Hanzo Dev <[email protected]>
79 lines
3.3 KiB
Go
79 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"net/http/httptest"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// render_test.go guards the two properties the view has to keep: that its design
|
|
// values come from exactly one place, and that it can only ever draw rows the
|
|
// viewer was already permitted to see.
|
|
|
|
// TestBrandCSSIsUpstreamBytes is the pin. The vendored sheet is only a source of
|
|
// truth while it is byte-for-byte what @hanzo/brand published; the moment it can
|
|
// be edited in place it is a fork wearing an upstream name, which is the exact
|
|
// state this repo was in when it carried its own :root block.
|
|
func TestBrandCSSIsUpstreamBytes(t *testing.T) {
|
|
sum := sha256.Sum256([]byte(brandCSS))
|
|
got := hex.EncodeToString(sum[:])
|
|
if got != brandCSSSHA256 {
|
|
t.Fatalf("brand/variables.css is not @hanzo/brand@%s\n got %s\n want %s\n"+
|
|
"A token refresh: re-fetch the sheet and set brandCSSSHA256 to the got value.\n"+
|
|
"A local colour edit: make it in @hanzo/brand and release it, not here.",
|
|
brandCSSVersion, got, brandCSSSHA256)
|
|
}
|
|
}
|
|
|
|
// colourLiteral matches a value that decides an appearance on its own — a hex,
|
|
// or an rgb()/hsl() function. `color-mix(in srgb, var(--x) ...)` is deliberately
|
|
// not one of these: it derives from a token instead of naming a new colour.
|
|
var colourLiteral = regexp.MustCompile(`#[0-9a-fA-F]{3,8}\b|\brgba?\(|\bhsla?\(`)
|
|
|
|
// TestDashboardCSSNamesNoColours is what makes "one source of truth" a fact
|
|
// rather than an intention. Vendoring the sheet is only half the job; if the
|
|
// page can still write a hex next to it, the second palette grows back one
|
|
// "just this once" at a time — which is how the old :root block came to hold
|
|
// GitHub's status colours instead of the house's.
|
|
func TestDashboardCSSNamesNoColours(t *testing.T) {
|
|
if m := colourLiteral.FindAllString(dashboardCSS, -1); len(m) > 0 {
|
|
t.Fatalf("dashboard.css names colours directly: %v\n"+
|
|
"Every colour must be a var() into @hanzo/brand; if the token you need "+
|
|
"does not exist, add it there rather than here.", m)
|
|
}
|
|
if !strings.Contains(dashboardCSS, "var(--") {
|
|
t.Fatal("dashboard.css references no tokens at all — it has stopped consuming the design system")
|
|
}
|
|
}
|
|
|
|
// TestRenderedPageShowsOnlyTheViewersOrg drives the HTML, not the predicates.
|
|
// scope_test.go proves visible() and orgs() are right; this proves the page is
|
|
// actually built from them — the leak that started all of this was a handler
|
|
// handing a template more than the viewer was owed, and a template cannot be
|
|
// trusted to be careful with a snapshot it can see all of.
|
|
func TestRenderedPageShowsOnlyTheViewersOrg(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
renderDashboard(w, snapshot{Runs: testRuns(), Repos: 3}, viewer{org: "lux"}, "", config{})
|
|
body := w.Body.String()
|
|
|
|
if !strings.Contains(body, ">lux/<") {
|
|
t.Fatal("lux viewer's own run is missing from the page")
|
|
}
|
|
// Rows: no other org's repo may be drawn.
|
|
for _, leaked := range []string{">hanzo/<", ">zoo/<"} {
|
|
if strings.Contains(body, leaked) {
|
|
t.Errorf("page rendered %s to a lux viewer", leaked)
|
|
}
|
|
}
|
|
// Nav: nor may another org's NAME, which discloses who builds here even
|
|
// when their runs are correctly hidden.
|
|
for _, leaked := range []string{"/?org=hanzo", "/?org=zoo", "all orgs"} {
|
|
if strings.Contains(body, leaked) {
|
|
t.Errorf("nav offered %q to a lux viewer", leaked)
|
|
}
|
|
}
|
|
}
|