Skip to content

typdiff can generate invalid Typst when diffing labels and cross-references #7

@kjgoodrick

Description

@kjgoodrick

typdiff can generate invalid Typst when diffing labels and cross-references

Summary

I ran typdiff on two Typst files generated by Quarto from different versions of the same report. The source files compiled individually, but the generated diff file did not compile with Typst.

The main issue is that typdiff appears to treat Typst labels as ordinary text. When labels are renamed, or when content immediately after a label changes, diff markup can be inserted inside label syntax or can wrap the label itself. This breaks downstream #ref(...) calls and sometimes produces invalid Typst syntax.

I have a PR prepared for this issue and will submit it shortly.

Environment

  • typst 0.14.2
  • quarto 1.9.37
  • typdiff 0.1.1

Command used:

typdiff -o diff.typ old.typ new.typ
typst compile diff.typ diff.pdf

Problem 1: Diff markup inserted inside label syntax

old.typ

= Sample

<sample-widget-anchor>

Body.

new.typ

= Sample

<sample-widget_anchor>

Body.

Generated output

#let diff-added(body) = {
  set text(fill: rgb("#0000ff"))
  underline(body)
}
#let diff-deleted(body) = {
  set text(fill: rgb("#cc0000"))
  strike(body)
}

= Sample

<sample#diff-deleted[-]#diff-added[_]widget-anchor>

Body.

Error

error: unclosed delimiter
<sample#diff-deleted[-]#diff-added[_]widget-anchor>

Expected / preferred generated output

#let diff-added(body) = {
  set text(fill: rgb("#0000ff"))
  underline(body)
}
#let diff-deleted(body) = {
  set text(fill: rgb("#cc0000"))
  strike(body)
}

= Sample

// label changed from <sample-widget-anchor>
<sample-widget_anchor>

Body.

At minimum, typdiff should choose one valid label and avoid inserting diff markup inside <...> label syntax.

Problem 2: Labels can be wrapped in #diff-added[...], breaking refs

old.typ

#set heading(numbering: "1.")

See #ref(<sample-anchor>, supplement: [Section]).

= Sample

<sample-anchor>
Alpha beta gamma delta epsilon zeta eta theta.

new.typ

#set heading(numbering: "1.")

See #ref(<sample-anchor>, supplement: [Section]).

= Sample

<sample-anchor>
Inserted paragraph before old text.

Alpha beta gamma delta changed epsilon zeta eta theta.

Generated output

#let diff-added(body) = {
  set text(fill: rgb("#0000ff"))
  underline(body)
}
#let diff-deleted(body) = {
  set text(fill: rgb("#cc0000"))
  strike(body)
}

#set heading(numbering: "1.")

See #ref(<sample-anchor>, supplement: [Section]).

= Sample

#diff-added[<sample-anchor>
Inserted paragraph before old text.]

#diff-deleted[\<sample-anchor>
]Alpha beta gamma delta #diff-added[changed ]epsilon zeta eta theta.

Error

warning: label `<sample-anchor>` is not attached to anything
error: label `<sample-anchor>` does not exist in the document

Expected / preferred generated output

#let diff-added(body) = {
  set text(fill: rgb("#0000ff"))
  underline(body)
}
#let diff-deleted(body) = {
  set text(fill: rgb("#cc0000"))
  strike(body)
}

#set heading(numbering: "1.")

See #ref(<sample-anchor>, supplement: [Section]).

= Sample

<sample-anchor>

#diff-added[Inserted paragraph before old text.]

Alpha beta gamma delta #diff-added[changed ]epsilon zeta eta theta.

Since the label is unchanged between versions, it should remain outside the diff wrapper and continue to attach to the heading.

Suggested behavior

  • Treat <label> as atomic Typst syntax.
  • Never insert #diff-added / #diff-deleted inside a label.
  • Keep unchanged labels outside styled diff wrappers.
  • Preserve label attachment to structural elements, especially headings, figures, tables, and equations.
  • If labels differ, prefer a valid new label plus an optional comment, rather than visible/styled inline diff markup.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions