07a9784d1c
* fix(share): route share links through gist.githack so big tasks render gistpreview.github.io fetches gists via the public GitHub API, which returns `content: ""` for files past GitHub's per-response content budget (~1 MB). For non-trivial sessions, share.html and snapshot.json land past that budget and gistpreview's `document.write(content)` renders a blank page. gist.githack.com proxies the raw file content directly, so multi-MB share.html renders correctly — at the cost of a one-time Cloudflare anti-phishing interstitial on first visit, accepted as the price of fidelity for big tasks. displayURL now normalises bare gist URLs and unpinned githack URLs to the canonical `gist.githack.com/<owner>/<id>/raw/share.html` form; legacy gistpreview URLs pass through unchanged (the owner is not recoverable from a gistpreview URL, so users can revoke + re-share to get the githack form going forward). * fix(share): address PR #1008 review feedback - displayURL now re-pins legacy gistpreview URLs stored without /share.html so they hit the styled view instead of silently falling back to the alphabetically-first README.md (flagged by Greptile). - Spec: unify two stale gistpreview references (API endpoint description and the publish scenario) to the canonical githack form, with a note that legacy gistpreview rows pass through. Flagged by CodeRabbit and Claude — the spec is the public contract; one stale mention undermines the migration. --------- Co-authored-by: Kandev Agent <agent@kandev.dev>
91 lines
3.0 KiB
Go
91 lines
3.0 KiB
Go
package share
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestBuildGistREADME_FullConversation(t *testing.T) {
|
|
t.Parallel()
|
|
completed := time.Date(2026, 5, 21, 12, 0, 0, 0, time.UTC)
|
|
snap := &Snapshot{
|
|
Version: SnapshotVersion,
|
|
ExportedAt: completed,
|
|
Task: TaskMeta{Title: "Investigate flaky test"},
|
|
Session: SessionMeta{
|
|
AgentType: "claude-acp",
|
|
Model: "claude-opus-4-7",
|
|
ExecutorType: "local_docker",
|
|
StartedAt: completed.Add(-time.Minute),
|
|
CompletedAt: &completed,
|
|
},
|
|
Messages: []Message{
|
|
{Role: roleUser, Ts: completed, Blocks: []Block{{Kind: blockKindText, Text: "why is X flaky?"}}},
|
|
{
|
|
Role: roleAssistant, Ts: completed,
|
|
Blocks: []Block{
|
|
{Kind: blockKindText, Text: "Looking into it."},
|
|
{
|
|
Kind: blockKindToolCall,
|
|
ToolName: "shell",
|
|
Text: "ran tests",
|
|
Args: json.RawMessage(`{"cmd":"go test ./..."}`),
|
|
},
|
|
{Kind: blockKindToolResult, Output: "FAIL pkg/foo TestX"},
|
|
{
|
|
Kind: blockKindDiff,
|
|
Path: "src/x.go",
|
|
UnifiedDiff: "--- a\n+++ b\n@@\n-old\n+new\n",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Redaction: RedactionLog{AppliedRules: []string{RuleAbsPath}},
|
|
}
|
|
|
|
md := BuildGistREADME(snap, "https://gist.githack.com/jane/mock-gist-1/raw/share.html")
|
|
assertContains(t, md, "# Investigate flaky test")
|
|
assertContains(t, md, "<kbd>claude-acp</kbd>")
|
|
assertContains(t, md, "<kbd>claude-opus-4-7</kbd>")
|
|
assertContains(t, md, "<kbd>local_docker</kbd>")
|
|
assertContains(t, md, "📊 Session details")
|
|
assertContains(t, md, "| **Agent** | claude-acp |")
|
|
assertContains(t, md, "Redacted before publish:")
|
|
assertContains(t, md, "🧑 User")
|
|
// User text is wrapped in a blockquote for visual accent.
|
|
assertContains(t, md, "> why is X flaky?")
|
|
assertContains(t, md, "🤖 Assistant")
|
|
assertContains(t, md, "🔧 <strong>shell</strong>")
|
|
// Tool args and output render via HTML <pre><code> rather than a
|
|
// triple-backtick fence so payloads containing ``` can't break out.
|
|
assertContains(t, md, `<pre><code class="language-json">`)
|
|
assertContains(t, md, `"cmd": "go test ./..."`)
|
|
assertContains(t, md, "📤 Tool output")
|
|
assertContains(t, md, "FAIL pkg/foo TestX")
|
|
assertContains(t, md, "**📝 `src/x.go`**")
|
|
assertContains(t, md, "```diff")
|
|
assertContains(t, md, "snapshot.json")
|
|
assertContains(t, md, "github.com/kdlbs/kandev")
|
|
}
|
|
|
|
func TestBuildGistREADME_NilAndEmpty(t *testing.T) {
|
|
t.Parallel()
|
|
if got := BuildGistREADME(nil, ""); got == "" {
|
|
t.Fatal("nil snapshot should still produce a non-empty README")
|
|
}
|
|
empty := &Snapshot{Task: TaskMeta{Title: "Untitled"}}
|
|
got := BuildGistREADME(empty, "https://gist.githack.com/jane/g1/raw/share.html")
|
|
if !strings.Contains(got, "_(No messages.)_") {
|
|
t.Fatalf("expected empty-messages placeholder, got: %s", got)
|
|
}
|
|
}
|
|
|
|
func assertContains(t *testing.T, haystack, needle string) {
|
|
t.Helper()
|
|
if !strings.Contains(haystack, needle) {
|
|
t.Fatalf("expected output to contain %q\n--- output ---\n%s", needle, haystack)
|
|
}
|
|
}
|