carlos / platform Public

status: fix round 1 — em-dash for tile nulls, clickable fallback link

Four review fixes on Task 4's viewer:
- The instances-tile awake/asleep nulls now route through asText/
  emDash() (a titled "unknown" span) instead of a bare "—" character
  spliced into a joined string, matching every other null path on
  the page.
- The incident-fallback URL is now a real <a>, built and inserted at
  render time from location.origin + location.pathname (index.html
  suffix stripped, trailing slash guaranteed) rather than plain text —
  still no http(s):// literal ships in the file itself.
- status_test.go now also asserts against protocol-relative
  references (src="//, href="//, url(//) alongside the existing
  http(s):// and <script src checks, without false-positiving on the
  inline script's own "// comment" lines.

Covering test: internal/status/status_test.go — TestPublishWritesPageAssets.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 64a4e66283fe50471c9465117a89f44f884f1844 parent 5bba0d9
2 files changed, +41 −17
  • internal/status/assets/index.html +34 −16
  • internal/status/status_test.go +7 −1
diff --git a/internal/status/assets/index.html b/internal/status/assets/index.html
index acc47a06c..04bb6842b 100644
--- a/internal/status/assets/index.html
+++ b/internal/status/assets/index.html
@@ -117,13 +117,18 @@
var instances = f.instances || {};
document.getElementById("tile-instances").replaceChildren(asText(instances.total));
- var detail = routeCols
- .map(function (k) {
- var v = instances[k];
- return k + " " + (v === null || v === undefined ? "—" : v);
- })
- .join(" · ");
- document.getElementById("tile-instances-detail").textContent = detail;
+ // Built as DOM nodes (not a joined string) so a null component renders
+ // through asText/emDash — a titled "unknown" span, same as every other
+ // null path on this page — rather than a bare "—" character that
+ // carries no title for a reader to hover.
+ var detailEl = document.getElementById("tile-instances-detail");
+ var detailNodes = [];
+ routeCols.forEach(function (k, i) {
+ if (i > 0) detailNodes.push(document.createTextNode(" · "));
+ detailNodes.push(document.createTextNode(k + " "));
+ detailNodes.push(asText(instances[k]));
+ });
+ detailEl.replaceChildren.apply(detailEl, detailNodes);
var capacity = f.capacity || {};
document.getElementById("tile-capacity").replaceChildren(asText(capacity.level));
@@ -189,16 +194,29 @@
});
}
- // renderFallback builds the incident-fallback sentence from `location` at
- // view time rather than a hardcoded absolute URL baked into this file —
- // the whole point of the same-origin rule (design spec §3): this page
- // must contain no http(s):// literal of its own, yet still needs to tell
- // a reader where the raw bucket copy lives once the serving box is gone.
+ // renderFallback builds the incident-fallback sentence — and its clickable
+ // link — from `location` at view time rather than a hardcoded absolute
+ // URL baked into this file: the whole point of the same-origin rule
+ // (design spec §3) is that this page must contain no http(s):// literal
+ // of its own, yet it still needs to tell a reader, with a real <a>, where
+ // the raw bucket copy lives once the serving box is gone. Built from
+ // location.origin + location.pathname (not location.href) specifically so
+ // a query string or hash on the viewed URL can never leak into the join.
function renderFallback() {
- var base = location.href.replace(/index\.html$/, "");
- var url = base + "fleet.json";
- document.getElementById("fallback").textContent =
- "If this page is unreachable, the same data is published at " + url + ".";
+ var dir = location.pathname.replace(/index\.html$/, "");
+ if (dir.charAt(dir.length - 1) !== "/") dir += "/"; // guard a root path with no trailing slash
+ var url = location.origin + dir + "fleet.json";
+
+ var fallback = document.getElementById("fallback");
+ var link = document.createElement("a");
+ link.href = url;
+ link.textContent = url;
+
+ fallback.replaceChildren(
+ document.createTextNode("If this page is unreachable, the same data is published at "),
+ link,
+ document.createTextNode(".")
+ );
}
fetch("fleet.json")
diff --git a/internal/status/status_test.go b/internal/status/status_test.go
index c36769a44..8df8f4f6c 100644
--- a/internal/status/status_test.go
+++ b/internal/status/status_test.go
@@ -413,7 +413,13 @@ func TestPublishWritesPageAssets(t *testing.T) {
if err != nil {
t.Fatalf("Get status/index.html: %v", err)
}
- for _, banned := range []string{"http://", "https://", "<script src"} {
+ // The last three entries catch protocol-relative references
+ // (src="//cdn...", href="//cdn...", url(//cdn...)) — another way to
+ // reach off-origin without ever spelling "http". A bare "//" substring
+ // isn't checked: this file's inline script is full of legitimate "//
+ // comment" lines, so the assertion targets the actual reference
+ // positions (an attribute/url() opening onto "//") instead.
+ for _, banned := range []string{"http://", "https://", "<script src", `src="//`, `href="//`, `url(//`} {
if strings.Contains(string(html), banned) {
t.Errorf("page must be same-origin only; found %q in index.html", banned)
}