Files
zzg 066485daf7 Surface cached tokens, fold permission-wait spans, fix clipped durations
Running a real Claude Code session against Fathom exposed three things that
only show up on real data:

- A run measured 16 input and 1,301 output tokens against 280,054 cache
  reads. Summing them reports "281k" and makes every cached run look huge;
  omitting them reports "1.3k" and hides where the context came from. Both
  are now tracked and shown separately.
- Folding required a matching tool_use_id, but Claude Code stamps that on
  the tool and tool.execution spans and omits it on tool.blocked_on_user, so
  a permission-wait row survived under every tool. The test is now
  containment plus a dotted name extension, folding 29 spans into 17 rows.
- Span durations were absolutely positioned past the end of their bar, so a
  full-width bar pushed its label off-screen; percentage geometry resolves
  against the padding box, so padding reserved nothing. Durations moved to
  their own grid column, which also aligns them.

Adds config tests, an annotated fathom.ini.example, and a test that runs
node --check over the embedded JS -- a mangled edit shipped a blank page
once, and nothing else validates JS that has no build step.
2026-09-15 22:01:54 -07:00

56 lines
1.5 KiB
Go

package web
import (
"os/exec"
"path/filepath"
"strings"
"testing"
)
// TestEmbeddedJSParses catches syntax errors in the embedded UI at build time.
//
// The UI is plain JS with no build step, which is the point — but it also means
// nothing validates it before it reaches a browser, where a syntax error is a
// blank page and a console message nobody is watching. A broken edit shipped
// exactly that way once; this makes it a test failure instead.
//
// Skips cleanly when no JS runtime is installed, since one is not otherwise
// required to build or run Fathom.
func TestEmbeddedJSParses(t *testing.T) {
node := findNode()
if node == "" {
t.Skip("no node/deno/bun on PATH; skipping UI syntax check")
}
files, err := filepath.Glob("static/*.js")
if err != nil || len(files) == 0 {
t.Fatalf("no embedded JS found: %v", err)
}
for _, f := range files {
out, err := exec.Command(node, "--check", f).CombinedOutput()
if err != nil {
t.Errorf("%s does not parse:\n%s", f, out)
}
}
}
func findNode() string {
// nvm installs node outside the PATH a test process inherits, so check the
// usual versioned locations too.
if p, err := exec.LookPath("node"); err == nil {
return p
}
matches, _ := filepath.Glob(homeDir() + "/.nvm/versions/node/*/bin/node")
if len(matches) > 0 {
return matches[len(matches)-1]
}
return ""
}
func homeDir() string {
out, err := exec.Command("sh", "-c", "echo $HOME").Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(out))
}