Skip to content

Configurable annotation marker#185

Merged
umputun merged 2 commits into
umputun:masterfrom
jknlsn:feat/annotation-marker
May 13, 2026
Merged

Configurable annotation marker#185
umputun merged 2 commits into
umputun:masterfrom
jknlsn:feat/annotation-marker

Conversation

@jknlsn
Copy link
Copy Markdown
Contributor

@jknlsn jknlsn commented May 12, 2026

Configurable annotation marker

Adds --annotation-marker (env: REVDIFF_ANNOTATION_MARKER, config: annotation-marker) to let users swap the 💬 prefix for something else. Defaults to the emoji so nothing changes for existing setups.

All five render sites now go through annotPrefix() / annotFilePrefix() helpers instead of hardcoding the emoji. Input width and wrap-continuation indent use lipgloss.Width() so everything stays aligned regardless of marker width.

The marker is render-only — structured output still uses ## path:N (T) headers, so saved annotations stay portable between users with different markers. Flag lives on top-level options rather than Colors since it's not a colour value.

Empty string is a valid choice: --annotation-marker "" gives you a bare space prefix. The CLI default tag handles "not set", so empty only happens when you ask for it explicitly.

SKILL.md unchanged — this is a user-preference flag like --theme or --wrap, not something agents should auto-derive from context. Users set it in their config or env once.

Closes #184

@jknlsn jknlsn marked this pull request as ready for review May 12, 2026 11:36
@jknlsn jknlsn requested a review from umputun as a code owner May 12, 2026 11:36
@umputun umputun requested a review from Copilot May 12, 2026 16:20
Copy link
Copy Markdown
Owner

@umputun umputun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

few findings, biggest one is a width regression:

  1. input width drops 2 cols at app/ui/annotate.go:100,160. Old constants 6/12 were 1 (cursor) + prefix_width + 2 (border margin) per the original comments, the new 1 + lipgloss.Width(prefix) drops the +2. With the default marker the input is now 4/10 instead of 6/12, two columns wider than before, can bleed into the right padding on narrow panes. Either restore the +2 (2 + lipgloss.Width(...)) or document why the margin is no longer needed. Worth adding a regression test that pins absolute input width for the default marker plus at least one wide and one empty marker so this kind of drift surfaces in CI.

  2. testifylint will fail CI at app/config_test.go:427,454. assert.Equal(t, "", opts.AnnotationMarker) should be assert.Empty(t, opts.AnnotationMarker).

  3. gofmt struct-field alignment at app/main.go:206-213, app/ui/model_test.go:148-160, app/ui/annotate_test.go:510-520, 546-558. Inserting AnnotationMarker: widened the longest key but the surrounding fields were not re-aligned. make fmt cleans it up.

  4. stale test comment at app/ui/annotate_test.go:1137. "(12-6=6)" references the old hardcoded constants. The numerical result is still 6 under the new math (10-4=6), but the parenthetical no longer matches what is actually computed.

  5. weak assertion in TestModel_EmptyAnnotationMarkerExplicit. assert.Contains(t, rendered, " bare note", ...) would pass even if a hardcoded " 💬 " fallback regressed since the substring would still appear after the prefix. Anchor with a regex or full-row equality on the prefix.

  6. per-render alloc in annotPrefix() / annotFilePrefix(). Both concat a new string on every call from hot paths (composeAnnotationRows, annotationContinuationIndent, annotationPrefixBody, renderAnnotationOrInput, renderFileAnnotationHeader). Marker is immutable post-NewModel, would be cheaper to cache once on modelConfigState alongside annotationMarker.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a configurable annotation marker so users can replace the hardcoded 💬 prefix with a custom string via --annotation-marker (env REVDIFF_ANNOTATION_MARKER, config annotation-marker) while keeping the existing default behavior.

Changes:

  • Introduces the --annotation-marker option (CLI/env/config) and wires it through main -> ui.ModelConfig -> ui.Model.
  • Replaces hardcoded annotation marker render strings with annotPrefix() / annotFilePrefix() helpers and adjusts width/indent logic using lipgloss.Width().
  • Updates documentation and adds/extends tests for default, custom, and empty markers.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
site/docs.html Documents the new --annotation-marker option in the generated options table.
README.md Adds the new flag/env var to the CLI options table.
plugins/codex/skills/revdiff/references/config.md Documents the new flag/env/config key in the reference table.
.claude-plugin/skills/revdiff/references/config.md Mirrors config reference documentation update for the claude plugin.
app/ui/model.go Extends ModelConfig / internal config state to carry AnnotationMarker into UI rendering.
app/ui/model_test.go Updates the shared test model factory to set a default annotation marker.
app/ui/diffview.go Switches annotation rendering call sites to use the new prefix helpers; updates continuation indent logic.
app/ui/annotate.go Adds annotPrefix() / annotFilePrefix() helpers and updates annotation input width calculation to use lipgloss.Width().
app/ui/annotate_test.go Adds coverage for custom and empty annotation markers and updates imports/config for NewModel.
app/main.go Plumbs opts.AnnotationMarker into ui.ModelConfig.
app/config.go Adds the new CLI/env/config option with default 💬.
app/config_test.go Adds tests covering flag/env/config parsing for the new option (including explicit empty).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/ui/diffview.go Outdated
Comment on lines +701 to +706
func (m Model) annotationContinuationIndent(firstLogicalLine string) string {
switch {
case strings.HasPrefix(firstLogicalLine, "\U0001f4ac file: "):
return strings.Repeat(" ", lipgloss.Width("\U0001f4ac file: "))
case strings.HasPrefix(firstLogicalLine, "\U0001f4ac "):
return strings.Repeat(" ", lipgloss.Width("\U0001f4ac "))
case strings.HasPrefix(firstLogicalLine, m.annotFilePrefix()):
return strings.Repeat(" ", lipgloss.Width(m.annotFilePrefix()))
case strings.HasPrefix(firstLogicalLine, m.annotPrefix()):
return strings.Repeat(" ", lipgloss.Width(m.annotPrefix()))
Comment thread app/config.go
LineNumbers bool `long:"line-numbers" ini-name:"line-numbers" env:"REVDIFF_LINE_NUMBERS" description:"show line numbers in diff gutter"`
Blame bool `long:"blame" ini-name:"blame" env:"REVDIFF_BLAME" description:"show blame gutter"`
WordDiff bool `long:"word-diff" ini-name:"word-diff" env:"REVDIFF_WORD_DIFF" description:"highlight intra-line word-level changes in paired add/remove lines"`
AnnotationMarker string `long:"annotation-marker" ini-name:"annotation-marker" env:"REVDIFF_ANNOTATION_MARKER" default:"💬" description:"prefix shown before annotation lines"`
@jknlsn jknlsn force-pushed the feat/annotation-marker branch from 31ac16e to d0265b0 Compare May 12, 2026 20:41
Copy link
Copy Markdown
Owner

@umputun umputun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all 6 round-1 items fixed, verified locally. Tests green, lint 0 issues.

two copilot inline comments from round 1 still worth a look:

  1. indent inference ambiguity at app/ui/diffview.go:701-710. Real edge-case bug. annotationContinuationIndent uses HasPrefix(firstLogicalLine, m.annotFilePrefix()) to pick indent width, but a line-level annotation whose body starts with file: produces 💬 file: ... and matches the file-prefix branch, so continuation rows get the 9-space indent instead of 3. Triggers on multi-line line-annotations only. Worse with empty marker since annotFilePrefix becomes " file: " and any body starting with file: mis-matches. Fix is trivial since composeAnnotationRows already has prefix in scope: drop the HasPrefix switch and just do indent := strings.Repeat(" ", lipgloss.Width(prefix)) inline, then either inline-delete annotationContinuationIndent or have it take prefix directly.

  2. no newline/control-char validation on --annotation-marker. A \n/\r/\t in REVDIFF_ANNOTATION_MARKER (config file or env) would break layout silently. Nit, worst case is the user breaks their own rendering, but a strings.ContainsAny(marker, "\n\r\t") check at parse time with a clear error costs nothing.

re #5 (the empty-marker assertion): the assert.NotContains(rendered, "💬") you added catches a hardcoded emoji fallback regression, which was the actual worry. Fine as-is.

@jknlsn
Copy link
Copy Markdown
Contributor Author

jknlsn commented May 12, 2026

Added fixes and tests for those copilot catches, thanks!

Copy link
Copy Markdown
Owner

@umputun umputun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, thx

@umputun umputun merged commit c91a54b into umputun:master May 13, 2026
@jknlsn jknlsn deleted the feat/annotation-marker branch May 13, 2026 00:25
umputun added a commit that referenced this pull request May 13, 2026
post-merge cleanup for #185:

- modelConfigState.annotationMarker was stored but never read; only the
  cached annotPrefix/annotFilePrefix variants are used at call sites.
- the validation in parseArgs rejects \n/\r/\t but the error message
  only mentioned newlines and tabs. broadened to "control characters"
  so the wording covers CR too.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow customising the annotation marker symbol

3 participants