package web import ( "fmt" "html/template" "strings" "sourcecraft.dev/bigbes/sr-ht-spec/prosediff" ) // inlineSimilarityThreshold is the Phase 0 verdict's presentation switch: a // modified prose block whose token similarity is at or above it renders as an // inline word diff, and one below it renders as a two-column old/new view. // // 13% of real prose modifications shred into interleaved fragments — those // paragraphs really were rewritten sentence by sentence — and every one of them // scores at or below 0.73. Rendering them inline makes one review in eight // unreadable, which is the one where the agent changed the most. prosediff // exports BlockChange.Similarity for exactly this decision and computes no HTML // itself; this is where the decision is made. const inlineSimilarityThreshold = 0.75 // diffView is the whole rendered diff of one document, ready for the proposal // template. Unchanged reports the degenerate case — a proposal that touches a // document without changing it — so the page can say so rather than show an // empty diff. type diffView struct { HTML template.HTML Stats prosediff.Stats Unchanged bool } // renderDocDiff diffs the approved (old) and proposed (new) source of one // document and renders it to HTML. // // It walks prosediff's block-change model rather than its text renderer: the // text renderer is for a terminal, and the review page needs headings grouped, // word edits marked with /, and — the load-bearing part — the // two-column fallback for shredded blocks. Every piece of document content is // HTML-escaped before it reaches the output; the only markup this produces is // its own structure. func renderDocDiff(oldSrc, newSrc []byte) diffView { d := prosediff.Compare(oldSrc, newSrc) view := diffView{Stats: d.Stats, Unchanged: !d.Stats.Changed()} if view.Unchanged { return view } var b strings.Builder b.WriteString(`
`) lastPath := "\x00" // impossible path, so the first real one always prints inHunk := false closeHunk := func() { if inHunk { b.WriteString(`
`) inHunk = false } } for _, c := range d.Changes { if c.Kind == prosediff.ChangeEqual { continue // the review shows only what changed } blk := c.New if blk == nil { blk = c.Old } if path := strings.Join(blk.HeadingPath, " › "); path != lastPath { closeHunk() lastPath = path shown := path if shown == "" { shown = "(document preamble)" } b.WriteString(`
`) b.WriteString(template.HTMLEscapeString(shown)) b.WriteString(`
`) inHunk = true } writeBlock(&b, c) } closeHunk() b.WriteString(`
`) view.HTML = template.HTML(b.String()) return view } // writeBlock renders one changed block. Insert/delete/move show the whole // block; a modify chooses among a code line diff, an inline word diff and the // two-column view, on the rules the design pins. func writeBlock(b *strings.Builder, c prosediff.BlockChange) { switch c.Kind { case prosediff.ChangeInsert: writeWholeBlock(b, "ph-insert", "added", c.New) case prosediff.ChangeDelete: writeWholeBlock(b, "ph-delete", "removed", c.Old) case prosediff.ChangeMoveIn: fmt.Fprintf(b, `
%s moved here (was line %d)
`, template.HTMLEscapeString(c.New.Label()), c.Old.StartLine) case prosediff.ChangeMoveOut: fmt.Fprintf(b, `
%s moved away (now line %d)
`, template.HTMLEscapeString(c.Old.Label()), c.New.StartLine) case prosediff.ChangeModify: writeModify(b, c) } } // writeWholeBlock renders an inserted or deleted block: its whole text, in a //
 for a non-prose kind so code keeps its wrapping, and reflowed prose
// otherwise.
func writeWholeBlock(b *strings.Builder, class, verb string, blk *prosediff.Block) {
	label := verb + " " + blk.Label()
	fmt.Fprintf(b, `
%s`, class, template.HTMLEscapeString(label)) writeBody(b, blk.Text, blk.Kind.Prose()) b.WriteString(`
`) } // writeModify renders a modified block. A code, frontmatter or HTML block has a // line-oriented edit script and renders line by line; a prose block has a word // edit script and renders inline when it stayed similar enough to follow, and // two-column when it did not. func writeModify(b *strings.Builder, c prosediff.BlockChange) { label := "changed " + c.New.Label() if c.StructureOnly { label = fmt.Sprintf("changed %s → %s (structure)", c.Old.Label(), c.New.Label()) } if c.Moved { label += fmt.Sprintf(" (moved from line %d)", c.Old.StartLine) } if len(c.Lines) > 0 { fmt.Fprintf(b, `
%s
`,
			template.HTMLEscapeString(label))
		writeLineSpans(b, c.Lines)
		b.WriteString(`
`) return } if c.Similarity >= inlineSimilarityThreshold { fmt.Fprintf(b, `
%s
`, template.HTMLEscapeString(label)) writeInlineSpans(b, c.Words) b.WriteString(`
`) return } // The two-column fallback: the block was rewritten enough that inline marks // would shred it. The old column keeps deletions, the new keeps insertions, // each still marked, so a reviewer reads two coherent paragraphs side by side. fmt.Fprintf(b, `
%s (rewritten)
`, template.HTMLEscapeString(label)) writeColumnSpans(b, c.Words, true) b.WriteString(`
`) writeColumnSpans(b, c.Words, false) b.WriteString(`
`) } // writeBody renders whole-block text: escaped, in a
 when it is not prose.
func writeBody(b *strings.Builder, text string, prose bool) {
	if prose {
		b.WriteString(`
`) b.WriteString(template.HTMLEscapeString(text)) b.WriteString(`
`) return } b.WriteString(`
`)
	b.WriteString(template.HTMLEscapeString(text))
	b.WriteString(`
`) } // writeInlineSpans renders a word edit script inline, marking deletions and // insertions where they sit. The Space flag reproduces prosediff's own spacing: // a span that replaces the one before it carries Space=false, so a one-word // substitution does not render with a gap in the middle. func writeInlineSpans(b *strings.Builder, spans []prosediff.Span) { emitted := false for _, s := range spans { if s.Space && emitted { b.WriteByte(' ') } emitted = true writeSpan(b, s) } } // writeColumnSpans renders one side of the two-column view: the old side keeps // equal and deleted words, the new side keeps equal and inserted ones. Both // still mark their changes, so each column is a readable paragraph that also // shows what moved. func writeColumnSpans(b *strings.Builder, spans []prosediff.Span, old bool) { emitted := false for _, s := range spans { keep := s.Op == prosediff.OpEqual || (old && s.Op == prosediff.OpDelete) || (!old && s.Op == prosediff.OpInsert) if !keep { continue } if s.Space && emitted { b.WriteByte(' ') } emitted = true writeSpan(b, s) } } // writeSpan writes one span's escaped text, wrapped in or for a // change and bare for an equal run. func writeSpan(b *strings.Builder, s prosediff.Span) { esc := template.HTMLEscapeString(s.Text) switch s.Op { case prosediff.OpDelete: b.WriteString("") b.WriteString(esc) b.WriteString("") case prosediff.OpInsert: b.WriteString("") b.WriteString(esc) b.WriteString("") default: b.WriteString(esc) } } // writeLineSpans renders a line-oriented edit script (a code fence, frontmatter // or HTML block) one line per row inside a
, each line class-marked.
func writeLineSpans(b *strings.Builder, spans []prosediff.Span) {
	for _, s := range spans {
		class := "ph-line-eq"
		switch s.Op {
		case prosediff.OpDelete:
			class = "ph-line-del"
		case prosediff.OpInsert:
			class = "ph-line-ins"
		}
		fmt.Fprintf(b, `%s`+"\n", class, template.HTMLEscapeString(s.Text))
	}
}