carlos / platform Public

console: pin the chrome to inline SVG and the templates to no off-origin fetch — vendoring is only true while a test says so

Five assertions, all of which fail loudly the day someone reaches for a CDN
link or types a tick character back in:

  - the switcher trigger renders a real <svg> in the vendored idiom, and no
    text glyph (▾ ✓ ✕ → …) survives anywhere in the header;
  - the footer keeps its factor-X robot, so a later "strip the emoji" sweep
    cannot take the disclosure with it — emoji are content;
  - no template references a subresource. This is internal/status's
    banned-substrings idiom with the one carve-out the console needs and the
    status page does not: the console legitimately LINKS out (11factor.org,
    a canary's host), and a link is navigation the reader chooses. What is
    banned is anything the browser fetches on its own — <script src>, <link>,
    src=, @import, url(http, protocol-relative hrefs, any cdn./unpkg/jsdelivr
    /googleapis host, and lucide.dev itself;
  - every {{icon "slug"}} in every template resolves, which is what turns
    icon()'s deliberately forgiving empty-string return into a build-time
    failure instead of a silently missing caret;
  - every vendored icon is self-contained: 24×24, currentColor, aria-hidden,
    no absolute URL, no <image>, no hardcoded size.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell da69c70d2b56301486deb4c7936caccfa97cda28 parent 5f619fe
1 file changed, +175 −0
  • internal/console/icons_test.go +175 −0
diff --git a/internal/console/icons_test.go b/internal/console/icons_test.go
new file mode 100644
index 000000000..9ccb1e8b8
--- /dev/null
+++ b/internal/console/icons_test.go
@@ -0,0 +1,175 @@
+package console
+
+import (
+ "io/fs"
+ "regexp"
+ "strings"
+ "testing"
+)
+
+// The switcher's chrome is drawn, not typed: the disclosure caret and the
+// menu's marks are inline SVG from icons.go, and the text glyphs they replace
+// ("▾", "✓") are gone from the header. A glyph is whatever the reader's font
+// decides it is — a caret that renders as a black triangle on one machine and
+// a coloured emoji on another is not a control, and neither size nor colour
+// obeys the page. An icon does both.
+func TestSwitcherChromeIsInlineSVGNotTextGlyphs(t *testing.T) {
+ fx := newConsoleFixture(t)
+ c := signInPaul(t, fx)
+ _, _, body := get(t, c, fx.u("/"))
+ h := header(t, body)
+
+ summary, _, ok := strings.Cut(h, "</summary>")
+ if !ok {
+ t.Fatalf("no <summary> in the header:\n%s", h)
+ }
+ // The trigger's caret: a real <svg>, in the vendored idiom, hidden from
+ // assistive tech because the account name beside it is the label.
+ if !strings.Contains(summary, "<svg") {
+ t.Errorf("the switcher trigger has no inline <svg> caret:\n%s", summary)
+ }
+ for _, want := range []string{`viewBox="0 0 24 24"`, `stroke="currentColor"`, `aria-hidden="true"`} {
+ if !strings.Contains(summary, want) {
+ t.Errorf("the caret svg is missing %s:\n%s", want, summary)
+ }
+ }
+ // The menu marks the current account and offers a new one, both with
+ // icons rather than a reused tick character.
+ if n := strings.Count(h, "<svg"); n < 3 {
+ t.Errorf("header has %d inline icons, want the caret plus a mark per menu row:\n%s", n, h)
+ }
+ // No chrome glyph survives anywhere in the header. Text arrows, ticks and
+ // crosses are exactly what the icon set exists to replace.
+ for _, glyph := range []string{"▾", "▼", "▽", "⌄", "✓", "✔", "✕", "✖", "×", "→", "←"} {
+ if strings.Contains(h, glyph) {
+ t.Errorf("header still uses the text glyph %q as chrome:\n%s", glyph, h)
+ }
+ }
+}
+
+// Emoji are content, never chrome (Eleven's docs/icons.md). The footer's
+// robot is a factor-X disclosure — a mark this platform puts on LLM-written
+// text everywhere, from README.md to site/platform — so it is content and it
+// stays. This test pins that distinction so a later sweep of "remove the
+// emoji" cannot quietly take the disclosure with it.
+func TestFactorXDisclosureKeepsItsEmoji(t *testing.T) {
+ fx := newConsoleFixture(t)
+ c := signInPaul(t, fx)
+ _, _, body := get(t, c, fx.u("/"))
+ foot, _, ok := strings.Cut(body, "</footer>")
+ if !ok {
+ t.Fatalf("no <footer> in:\n%s", body)
+ }
+ if !strings.Contains(foot, "🤖") || !strings.Contains(foot, "written by an LLM") {
+ t.Errorf("the footer lost its factor-X disclosure:\n%s", foot)
+ }
+}
+
+// Same-origin, the console's version. internal/status asserts this on its
+// published page with a flat "no http(s):// anywhere" ban; the console cannot
+// use that ban verbatim because it legitimately LINKS out — the footer's
+// 11factor.org citation, an app's canary host. A link is navigation the
+// reader chooses. What must never appear is a SUBRESOURCE: anything the
+// browser fetches on its own while rendering the page — a script, a
+// stylesheet, a font, an image, an @import, a CSS url(). The console ships
+// one HTML document with its CSS inline and its icons inline, and that is the
+// whole of it. Vendoring the icons is what keeps this true.
+func TestConsoleTemplatesFetchNothingOffOrigin(t *testing.T) {
+ banned := []string{
+ "<script src", // an external script, the usual way an icon CDN arrives
+ "<link ", // stylesheets, fonts, preconnects, prefetches
+ "src=", // no <img>, <iframe> or <source> of any origin
+ "@import", // a stylesheet importing another
+ "url(http", // a CSS-referenced asset...
+ "url(//", // ...and the protocol-relative spelling of one
+ `href="//`, // protocol-relative, dodging the "http" ban entirely
+ "cdn.", // any CDN host, spelled anywhere at all
+ "lucide.dev", // the icons are vendored; the source URL lives in a Go comment
+ "unpkg",
+ "jsdelivr",
+ "googleapis",
+ }
+ err := fs.WalkDir(tmplFS, ".", func(path string, d fs.DirEntry, err error) error {
+ if err != nil || d.IsDir() {
+ return err
+ }
+ b, err := tmplFS.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ for _, bad := range banned {
+ if strings.Contains(string(b), bad) {
+ t.Errorf("%s must fetch nothing off-origin; found %q", path, bad)
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+var iconCall = regexp.MustCompile(`{{\s*icon\s+"([^"]*)"\s*}}`)
+
+// icon returns "" for an unknown slug so a typo cannot 500 a page mid-render.
+// This is the test that turns that empty string back into a failure, at build
+// time, where a missing caret is cheap to fix.
+func TestTemplateIconSlugsAllExist(t *testing.T) {
+ used := 0
+ err := fs.WalkDir(tmplFS, ".", func(path string, d fs.DirEntry, err error) error {
+ if err != nil || d.IsDir() {
+ return err
+ }
+ b, err := tmplFS.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ for _, m := range iconCall.FindAllStringSubmatch(string(b), -1) {
+ used++
+ if icon(m[1]) == "" {
+ t.Errorf("%s calls {{icon %q}}, which is not in icons.go", path, m[1])
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ if used == 0 {
+ t.Error("no template uses {{icon}} — the chrome went back to text glyphs")
+ }
+}
+
+// Every vendored icon is one self-contained, colour-inheriting, screen-reader
+// -silent 24×24 SVG. Getting any of these wrong is invisible in review and
+// obvious on the page: a hardcoded fill ignores dark mode, a stray absolute
+// URL reintroduces the request vendoring exists to avoid.
+func TestVendoredIconsAreSelfContained(t *testing.T) {
+ if len(icons) == 0 {
+ t.Fatal("the icon set is empty")
+ }
+ for slug, svg := range icons {
+ s := string(svg)
+ for _, want := range []string{
+ "<svg", "</svg>", `viewBox="0 0 24 24"`,
+ `stroke="currentColor"`, `aria-hidden="true"`, `class="icon"`,
+ } {
+ if !strings.Contains(s, want) {
+ t.Errorf("icon %q is missing %s: %s", slug, want, s)
+ }
+ }
+ for _, bad := range []string{"http://", "https://", "<image", "xlink:href", "url("} {
+ if strings.Contains(s, bad) {
+ t.Errorf("icon %q reaches outside the page (%q): %s", slug, bad, s)
+ }
+ }
+ // No width/height of their own: they size from the .icon CSS rule so
+ // one icon at a 0.9rem menu row and the same icon in body text are the
+ // same shape at two sizes, not two hand-tuned copies. (The leading
+ // space is what keeps stroke-width, a different thing entirely, out
+ // of this check.)
+ if strings.Contains(s, " width=") || strings.Contains(s, " height=") {
+ t.Errorf("icon %q hardcodes a size instead of sizing from the text: %s", slug, s)
+ }
+ }
+}