diff --git a/internal/exporter/html.go b/internal/exporter/html.go index 752aee9..292ed2e 100644 --- a/internal/exporter/html.go +++ b/internal/exporter/html.go @@ -1,7 +1,6 @@ package exporter import ( - "encoding/json" "fmt" "html" "html/template" @@ -18,7 +17,6 @@ type htmlData struct { Format string Iterations []htmlIteration Summary htmlSummary - DataJSON template.JS } type htmlIteration struct { @@ -117,9 +115,6 @@ func buildHTMLData(t *trace.Trace) htmlData { } } - jsonBytes, _ := json.Marshal(d.Iterations) - d.DataJSON = template.JS(string(jsonBytes)) - return d } diff --git a/internal/exporter/html_test.go b/internal/exporter/html_test.go new file mode 100644 index 0000000..ac6f2d3 --- /dev/null +++ b/internal/exporter/html_test.go @@ -0,0 +1,82 @@ +package exporter + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/loop-eng/looprelay/internal/trace" +) + +func TestExportHTML_NoDeadDataJSON(t *testing.T) { + tr := trace.New([]trace.Iteration{ + { + Number: 1, + Phase: trace.PhaseAct, + Action: trace.Action{Type: "Edit", Target: "main.go", Diff: "+package main\n"}, + Tokens: trace.TokenUsage{InputTokens: 10, OutputTokens: 5}, + Context: trace.ContextState{FillPct: 42, Compactions: 1}, + CostUSD: 0.01, + }, + { + Number: 2, + Phase: trace.PhaseVerify, + Verification: &trace.Verification{Status: "success", Output: "ok"}, + Tokens: trace.TokenUsage{InputTokens: 8, OutputTokens: 2}, + CostUSD: 0.005, + }, + }) + tr.SourceFile = "demo.ltf" + tr.SourceFormat = "ltf" + tr.SessionID = "sess-1" + tr.Agent = "claude" + + out := filepath.Join(t.TempDir(), "replay.html") + if err := ExportHTML(tr, out); err != nil { + t.Fatalf("ExportHTML: %v", err) + } + + raw, err := os.ReadFile(out) + if err != nil { + t.Fatalf("read output: %v", err) + } + html := string(raw) + + if !strings.Contains(html, "Iteration 1") { + t.Error("expected iteration 1 in HTML") + } + if !strings.Contains(html, "Iteration 2") { + t.Error("expected iteration 2 in HTML") + } + if !strings.Contains(html, `const total = 2`) { + t.Error("expected JS total from summary") + } + // Regression: unused DataJSON must not be reintroduced into the page. + if strings.Contains(html, "DataJSON") { + t.Error("HTML must not reference unused DataJSON") + } + // Marshal of iterations used to inject a raw JSON blob into the page; ensure gone. + if strings.Contains(html, `"ActionType"`) || strings.Contains(html, `"PhaseClass"`) { + t.Error("HTML must not embed marshaled htmlIteration JSON (former DataJSON)") + } +} + +func TestBuildHTMLData_OmitsDataJSONComputation(t *testing.T) { + tr := trace.New([]trace.Iteration{ + {Number: 1, Phase: trace.PhasePlan, CostUSD: 0.02}, + }) + tr.SourceFile = "x.ltf" + tr.SourceFormat = "ltf" + + d := buildHTMLData(tr) + if len(d.Iterations) != 1 { + t.Fatalf("expected 1 iteration, got %d", len(d.Iterations)) + } + if d.Iterations[0].Phase != "plan" { + t.Errorf("expected plan phase, got %q", d.Iterations[0].Phase) + } + if d.Summary.TotalIterations != 1 { + t.Errorf("expected summary total 1, got %d", d.Summary.TotalIterations) + } +}