carlos / platform Public

console: vendor Lucide as in-code SVG constants — a CDN is not an option here

The console has the fleet status page's constraint exactly: no build step, no
framework, and no external request. Icons therefore have to be inlined, so
internal/console/icons.go is the server-side twin of Eleven's
web/static/icons.js — static 24x24 currentColor stroke SVGs, exposed to the
templates as {{icon "<lucide-slug>"}}.

template.HTML appears here and only here, and the file says why at length:
these are compile-time constants of our own markup, never user or bucket
data. The ISC notice and the lucide.dev source ride along in the header, and
so does Eleven's emoji rule — emoji are content, chrome is drawn.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell fed767dc14bcbb6a3533e3f8978be6a3f713392b parent 67cccf2
2 files changed, +81 −0
  • internal/console/icons.go +77 −0
  • internal/console/server.go +4 −0
diff --git a/internal/console/icons.go b/internal/console/icons.go
new file mode 100644
index 000000000..2597ae982
--- /dev/null
+++ b/internal/console/icons.go
@@ -0,0 +1,77 @@
+// The console's icon set — every piece of interface chrome, one file. This
+// is the server-side twin of Eleven's web/static/icons.js: static, in-code
+// SVG strings in the Lucide idiom (24×24 viewBox, currentColor, stroke, round
+// caps and joins), inlined into the page at render time.
+//
+// Vendored, never a CDN. The console has the same hard constraint as the
+// fleet status page — no build step, no JavaScript framework, and no external
+// request, ever. A <script src> or <link href> at some icon host would be a
+// second origin the console cannot vouch for, cannot cache-control, and
+// cannot serve when it is offline. Inlining the paths is the only option, so
+// the paths live here.
+//
+// Icons are vendored from Lucide — https://lucide.dev — used under the ISC
+// licence:
+//
+// Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as
+// part of Feather (MIT). All other copyright (c) for Lucide are held by
+// Lucide Contributors 2022.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies. THE
+// SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS.
+//
+// Emoji policy, inherited from Eleven's docs/icons.md: emoji are CONTENT — a
+// mark a person or a model chose, like the footer's factor-X disclosure — and
+// never chrome. Interface furniture uses these icons. Adding one = paste the
+// Lucide SVG's paths in below, keep the (Lucide: <slug>) comment, register it
+// in the icons map, use it as {{icon "<slug>"}}.
+//
+// Each icon carries aria-hidden="true" because every one of them sits beside
+// its own text label — the label is the accessible name, and a second one
+// from the icon would only be read out twice. An icon used as the ONLY label
+// must not use these constants bare: give its button or link an aria-label.
+package console
+
+import "html/template"
+
+// template.HTML is used HERE AND ONLY HERE in the console, and this is why it
+// is safe here and nowhere else: every string in this file is a compile-time
+// constant of our own markup. No user input, no bucket object, no registry
+// row, nothing derived from a request is ever concatenated into one. Anything
+// from those sources must stay a plain string so html/template escapes it —
+// a review in this repo has already flagged template.HTML on bucket-derived
+// text as a Critical finding, and that judgement stands.
+const (
+ // iconOpen is the shared opening tag: no width or height, so the icon
+ // sizes from the CSS .icon rule (1em) and tracks the text beside it.
+ iconOpen = `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" ` +
+ `stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">`
+
+ // The account switcher's disclosure caret (Lucide: chevron-down).
+ iconChevronDown template.HTML = iconOpen + `<path d="m6 9 6 6 6-6"/></svg>`
+
+ // The switcher menu's mark on the account you are currently in
+ // (Lucide: check).
+ iconCheck template.HTML = iconOpen + `<path d="M20 6 9 17l-5-5"/></svg>`
+
+ // "New account", in the same column the check sits in (Lucide: plus).
+ iconPlus template.HTML = iconOpen + `<path d="M5 12h14"/><path d="M12 5v14"/></svg>`
+)
+
+// icons maps each Lucide slug to its vendored markup. Slugs are the Lucide
+// names so the mapping back to lucide.dev stays obvious.
+var icons = map[string]template.HTML{
+ "chevron-down": iconChevronDown,
+ "check": iconCheck,
+ "plus": iconPlus,
+}
+
+// icon renders one chrome icon by its Lucide slug: {{icon "chevron-down"}}.
+// An unknown slug renders nothing rather than panicking a page mid-response —
+// a typo must cost a missing caret, not a 500. TestTemplateIconSlugsAllExist
+// is what actually catches the typo, at test time.
+func icon(slug string) template.HTML { return icons[slug] }
diff --git a/internal/console/server.go b/internal/console/server.go
index 712ef50d3..4815764dd 100644
--- a/internal/console/server.go
+++ b/internal/console/server.go
@@ -70,6 +70,10 @@ var templates = template.Must(template.New("").Funcs(template.FuncMap{
// public page and the design spec both fix; indexing by name keeps that
// order explicit in the template instead.
"mapGet": func(m map[string]*int, k string) *int { return m[k] },
+ // icon inlines one vendored Lucide SVG by slug — see icons.go for the
+ // set, the licence, and why template.HTML is safe there and nowhere
+ // else. Chrome is drawn; emoji stay content.
+ "icon": icon,
}).ParseFS(tmplFS, "templates/*.html"))
const (