diff

package
v0.0.0-...-7186417 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Implements the "diff" hotkey (design-docs/Diff.md): pick a past run to compare the current tree against, show only what differs, and drill down into a matched task's own per-tab unified diff between the two runs.

The matching engine behind design-docs/Diff.md: aligning two independent PlaybookState trees (an "old" run and a "new" run) so a diff tree/drill- down can be built from the result. Pure - no I/O, no UI - so it's usable identically whichever of the two sides happens to be live/replayed data.

Index

Constants

This section is empty.

Variables

View Source
var DiffChromeStyle = tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorFuchsia).Bold(true)

DiffChromeStyle is design-docs/Diff.md's own "yet another coloring so it's clear we are in diff mode" - a fourth chrome color alongside BarStyle (navy, live) and ReplayBarStyle (purple, revisit). fuchsia, like purple/maroon elsewhere in this app, is a fixed base-16 ANSI palette slot rather than an RGB approximation, so it renders reliably across terminal themes - see design-docs/Colors.md. The tree's own content is NOT recolored to match (see DiffTaskRowText/DiffHostRowText below) - only this chrome is; content keeps the normal outcome palette, with underline/strikethrough as the only diff signal, per your own "colored as normal... differences only visualized by underlining."

View Source
var TviewTagPattern = regexp.MustCompile(`\[[a-zA-Z0-9_,;: \-\."#]+\]`)

TviewTagPattern matches a real tview color/style tag - the same tag grammar tview.Escape/Unescape themselves use internally (confirmed against tview's own util.go), reused here rather than reinvented. Deliberately doesn't match "[[]", the literal-bracket escape sequence tview.Escape produces (that pattern's own character class excludes a second "[", so a genuine "[[]" is never mistaken for a tag here).

Functions

func BuildDiffOutputTabs

func BuildDiffOutputTabs(a TaskAlignment, host string, newSourceIndex, oldSourceIndex source.TaskSourceIndex) (names []string, contents []string)

BuildDiffOutputTabs is diff mode's own drill-down tab builder (design-docs/Diff.md): for a matched task alignment, unified-diffs each tab's own plain-text content between the two runs; for an unmatched one, falls back to SingleRunTabs (nothing to diff against). Docs is always shown once, undiffed - module docs don't vary per run. Resolved is deliberately omitted here, unlike the live tree's own drill-down: it doesn't represent a genuinely per-run recorded fact in the first place (design-docs/Drilldown, Resolved Values.md - it always re-resolves against *current* vars, regardless of which run's task is shown), and for a matched pair whose own source is unchanged it would just diff identically-resolved text against itself for no reason; when the source genuinely did change between runs, Task definition's own diff already surfaces that. Can be added later if it proves genuinely wanted once this is in front of you - not chased further for now.

func CsvSetEqual

func CsvSetEqual(a, b string) bool

CsvSetEqual reports whether a and b represent the same SET of comma- separated values (whitespace-trimmed per entry, order-insensitive, duplicates collapsed) - CsvOverlap's own sibling (revisitresolve.go), but requiring equality rather than merely an overlap. Backs ResolveDiffCandidates' own "did this past run use the exact same tags/ hosts as the current session" check (design-docs/Diff.md: "a different set of hosts" disqualifies a candidate outright, not just a partial mismatch - diffing two runs that didn't actually execute the same tasks wouldn't mean much).

func DecodeTaskHostResult

func DecodeTaskHostResult(task *playbook.TaskNode, host string) (map[string]interface{}, bool)

DecodeTaskHostResult decodes task's own raw result for host - the same decode step BuildOutputTabs already does inline, pulled out here since diff mode needs it independently for both the old and new side of a matched alignment.

func DiffColorTag

func DiffColorTag(o playbook.Outcome, flag string) string

DiffColorTag is ColorTag(o) with flag ("u"/"s"/"") appended as the tag's own third (flags) component when set - tview's own "[fg::flags]" form, already used elsewhere in this file (PlayRowText's own "[white::b]").

func DiffHostList

func DiffHostList(task *playbook.TaskNode, wholeLineFlag string, underlineHosts map[string]bool) string

DiffHostList renders task's own HostOrder as a space-separated, per-host colored list (ColorTag per outcome, matching TaskLabel's own collapsed-row convention in spirit - though without its shared-column- width shrink algorithm, deliberately, see FlattenDiffRows' own doc comment). wholeLineFlag, if non-empty, applies uniformly to every host (an unmatched task's whole line); otherwise underlineHosts says which specific hosts get their own "u" (a matched, differing task).

func DiffHostRowText

func DiffHostRowText(task *playbook.TaskNode, host string, flag string, selected bool) string

DiffHostRowText mirrors HostLabel's own format (host: outcome + detail) with flag ("u"/"s"/"") layered on as an extra tag component - see DiffColorTag. selected uses the same uniform convention DiffTaskLine does, not HostLabel's own per-outcome-colored-background variant.

func DiffHostRows

func DiffHostRows(a TaskAlignment, selectedID any, showOutput func(TaskAlignment, string)) []uikit.Row

DiffHostRows renders a TaskAlignment's own expanded host rows - shown while its collapsed row is expanded (FlattenDiffRows). An unmatched alignment renders every host from whichever side is present, all marked uniformly - the whole task is new/gone, so every one of its hosts is relevant. A matched, differing one renders *only* the hosts DifferingHosts actually flags: unlike the collapsed row (DiffHostList), which shows every host so the shared-column host list stays recognizable at a glance, expanding a task is a deliberate "show me what's different" - per a live bug report, a task with e.g. 9 unchanged hosts and 1 changed one listed all 10 host lines, burying the one that actually mattered. Enter on a host row calls showOutput(a, host) - the drill-down view (showDiffOutput, below). selectedID - see FlattenDiffRows' own doc comment.

func DiffPlayName

func DiffPlayName(pa PlayAlignment) *playbook.PlayNode

DiffPlayName is a PlayAlignment's own display name - whichever side is present (they share a name by construction when matched, see AlignPlays).

func DiffTaskDisplayWidth

func DiffTaskDisplayWidth(a TaskAlignment) int

DiffTaskDisplayWidth is DiffTitleColWidth's own per-row width measurement - the task's own title, plus, for an unmatched task, the plain-text UnmatchedMarker appended right after it (DiffTaskLine) - has to be accounted for here too, or the shared host column would stop lining up for exactly the rows that carry a marker.

func DiffTaskKey

func DiffTaskKey(a TaskAlignment) *playbook.TaskNode

DiffTaskKey is a TaskAlignment's own identity for expand/collapse tracking - whichever side is present.

func DiffTaskLine

func DiffTaskLine(task *playbook.TaskNode, titleColWidth int, wholeLineFlag, marker string, underlineHosts map[string]bool, selected bool) string

DiffTaskLine is DiffTaskRowText's own shared renderer for both the unmatched (wholeLineFlag/marker set, underlineHosts nil) and matched (wholeLineFlag/marker "", underlineHosts naming the differing ones) cases. marker (UnmatchedMarker's own plain-text "(old only)"/"(new only)", or "" for a matched task) renders right after the title, inside the same styled span - so it picks up wholeLineFlag's own strikethrough/italic/underline too, reinforcing rather than competing with it. Padding to titleColWidth (plus TitleHostGapFloor's worth of breathing room, same floor the live tree's own TaskLabel uses) is what actually lines hosts up across every row - see DiffTitleColWidth, which already accounts for marker's own width via DiffTaskDisplayWidth. selected uses the same uniform PureBlack-on-lightgray convention host.go's own HostRowText/revisit.go's own RevisitRowText already do for a read-only browsing list - not the live tree's own per-host colored-background blending (TaskLabel's selected variant), which would be considerably more machinery for a view this feature doesn't ask for; the title's own padding is included in that highlighted block, the hosts themselves (already individually colored by DiffHostList) are not.

func DiffTaskRowText

func DiffTaskRowText(a TaskAlignment, titleColWidth int, selected bool) string

DiffTaskRowText renders one task alignment's collapsed row, indented by TaskIndent - the same prefix the live tree's own TaskLabel uses to set a task row apart from a play row's own column-0 title (another part of "the formatting is totally off": task rows had no indent of their own at all in the first version of this, landing flush with play rows).

Unmatched (only present on one side): the whole line - title and every host - carries two marks at once: underline ("u") for a task only present in the new run, strikethrough+italic ("si") for one only present in the old run (strikethrough alone, originally - see design-docs/Diff.md's mosh note for why italic joined it rather than replacing it: strikethrough still reads as "something's gone" for terminals that render it, italic is what survives on the ones that don't), plus UnmatchedMarker's own plain-text "(old only)"/"(new only)" suffix, which needs no text-attribute support at all. Rendered from whichever side actually has data (there's nothing else to render it from).

Matched (present, and different, on both sides): rendered from the NEW task's own data (design-docs/Diff.md's "render based on the new version"), normal outcome coloring throughout, with only the individual hosts DifferingHosts identifies actually underlined.

func DiffTitleColWidth

func DiffTitleColWidth(alignments []PlayAlignment) int

DiffTitleColWidth returns the widest (in runes) task title among every row FlattenDiffRows would currently produce for alignments - every task row shares this one column width (DiffTaskLine below) so the trailing host list lines up at the same column regardless of any one row's own title length, the same alignment requirement the live tree's own ComputeHostColumnLayout satisfies for the main tree. A real, reported gap in the first version of this: with no shared column at all, every task row's own host list started immediately after THAT row's own title, at a different column per row - "the formatting is totally off," not a cosmetic nicety. Deliberately simpler than ComputeHostColumnLayout: no shrink-to-fit pass for a narrow terminal or a very long title - diff mode's own scale (a debugging aid, opened occasionally, not the constantly-redrawn main tree) doesn't call for that sophistication, just the basic shared-padding part that actually caused the reported problem.

func DiffTwoTexts

func DiffTwoTexts(a, b string) string

DiffTwoTexts unified-diffs a's vs b's own plain text (after StripTags) - "" if they're identical, same "nothing to show for this tab" convention BuildOutputTabs' own add() already uses.

func DifferingHosts

func DifferingHosts(a TaskAlignment) map[string]bool

DifferingHosts returns the set of hosts (out of NewTask.Hosts) that HostDiffers reports as different for a *matched* alignment - nil for an unmatched one (OldTask or NewTask nil), since there's no per-host comparison to make there; the whole row is marked instead (see diff.go's own DiffTaskRowText).

func FetchDocsSync

func FetchDocsSync(action string) uikit.ResolvedRender

FetchDocsSync fetches ansible-doc's own text for action synchronously - design-docs/Diff.md's Docs tab is "left as is" (shown once, undiffed), but diff mode's own drill-down doesn't have a live session to hang the main tree's async fetch-then-redraw-plus-cache machinery (NewLiveTUI's docsCache/resolveCache) off of, and this view is opened rare enough, relative to the live tree's own constant back-and-forth, that a brief synchronous ansible-doc invocation is an acceptable simplification rather than replicating that whole apparatus here too.

func FlattenDiffRows

func FlattenDiffRows(alignments []PlayAlignment, expanded map[*playbook.TaskNode]bool, selectedID any, showOutput func(TaskAlignment, string)) []uikit.Row

FlattenDiffRows walks alignments into an ordered row list - only plays that contain a difference (PlayAlignmentHasDifferences), only tasks that are themselves different (TaskDiffers), each task's own host rows included only while expanded[its own key] is true. Mirrors FlattenRows' general shape (same row type, same expand-by-identity convention) but is deliberately simpler: no shared host-column width/shrink algorithm (ComputeHostColumnLayout) - diff mode shows far fewer, more focused rows than the live tree ever does, so that sophistication isn't needed here.

selectedID is whichever row's own id (a *PlayNode, *TaskNode, or DiffHostRowID) is currently under the cursor - every row is rendered in one pass, unlike the live tree's own FlattenRows (which renders unselected first and patches the one selected row's text afterward, needing a shared column-width computed once regardless of which row ends up selected). No such shared state exists here, so a single pass comparing each row's own id against selectedID as it's built is simpler and sufficient. Reported live: without doing this at all (an earlier version), no row was EVER rendered with its own selected styling - TreeList (unlike tview.List) has no built-in "current row" look of its own, so the cursor was completely invisible.

func HostDiffers

func HostDiffers(oldTask, newTask *playbook.TaskNode, host string) bool

HostDiffers reports whether host's own result differs between oldTask and newTask (outcome or output) - TaskDiffers' own per-host building block, and diff.go's own row rendering reuses it too, to decide which specific hosts get underlined on a matched, differing task's row (design-docs/Diff.md's "underline those hosts that are different"). false whenever host isn't recorded on both sides - same "host-set differences don't count" rule TaskDiffers itself follows.

func HostOutputDiffers

func HostOutputDiffers(oldTask, newTask *playbook.TaskNode, host string) bool

HostOutputDiffers compares host's own recorded output between oldTask and newTask - design-docs/Diff.md's "Different output (stdout, stderr, warning)" - via the exact same fields formatHostOutput already treats as distinct sections (PrimaryOutputField's own stdout-vs-msg choice, stderr, warnings), decoded from the same Raw[host] JSON both tasks already carry. A host missing from either side's own Raw (shouldn't happen for a host TaskDiffers has already confirmed is present in both Hosts maps, but not trusted blindly) decodes to "", comparing equal to itself rather than panicking.

func HostOutputSignature

func HostOutputSignature(raw json.RawMessage) (output, stderr, warnings string)

HostOutputSignature decodes raw once and extracts the three fields HostOutputDiffers compares, so a task/host pair's raw JSON is only ever decoded once per side rather than once per compared field.

func LastRunID

func LastRunID(cfg config.StateConfig, playbook, role string) string

LastRunID returns the most recently recorded RunID for playbook/role in cfg - a stand-in for "the current session's own saved run," looked up fresh from state.toml rather than threaded through NewLiveTUI's own live-generation plumbing (which changes across reruns and would need its own cross-goroutine-synchronized tracker to stay correct). Safe because, by the time 'd' is even pressable (processDone), the current generation's own invocation record has already been finalized (FinalizeInvocation) and is the last entry recorded for this target - nothing else can have appended after it without the user having done something else in a separate session concurrently.

func PlayAlignmentHasDifferences

func PlayAlignmentHasDifferences(pa PlayAlignment) bool

PlayAlignmentHasDifferences reports whether pa "contains tasks with differences" (design-docs/Diff.md) - true if any of its own task alignments differ (TaskDiffers). A play that only exists on one side needs no special case here: every one of its tasks is already an unmatched alignment (see AlignPlays/AlignTasks), and TaskDiffers is always true for those - a play can't reach zero tasks in the first place (aggregate.go's own Apply only ever creates a PlayNode once its first task starts), so there's always at least one to make this true.

func PlayNames

func PlayNames(plays []*playbook.PlayNode) []string

func ReplayRun

func ReplayRun(runID string) (*playbook.PlaybookState, error)

ReplayRun loads a saved run's own .jsonl (runlog.go) into a fresh PlaybookState - the exact replay mechanism OpenRevisitEntry already uses for showing a revisited run, reused here for diff mode's own comparison ("old") run.

func ResolveDiffCandidates

func ResolveDiffCandidates(currentPlaybook, currentRole, currentTags, currentHosts, excludeRunID string, cfg config.StateConfig) []revisit.RevisitEntry

ResolveDiffCandidates flattens cfg's history into the runs offerable as a diff comparison target for the current session (design-docs/Diff.md): same playbook/role, and the exact same tags/hosts as the current session's own (CsvSetEqual, not CsvOverlap - unlike ResolveRevisitEntries' own CLI-driven filter, a partial tag/host match here would silently compare two runs that didn't actually execute the same set of tasks). excludeRunID (the session currently on screen) is never offered against itself. Deliberately ignores every other passthrough arg (-e, -i, -vvv, ...) - only playbook/role and tags/hosts are checked, per design-docs/ Diff.md's own answer. Same "has a RunID" revisitability precondition and newest-first ordering as ResolveRevisitEntries - PruneMissingRunLogs is expected to have already run; this function has no I/O of its own.

func RunDiffFlow

func RunDiffFlow(currentState *playbook.PlaybookState, targetPlaybook, targetRole, currentTags, currentHosts string, currentSourceIndex source.TaskSourceIndex)

RunDiffFlow is the 'd' key's own entry point (tui.go's SetInputCapture, design-docs/Diff.md) - called from inside app.Suspend, so it owns the real terminal for as long as it runs and hands it back automatically on return. Loops between the candidate-run list (reusing revisit's own list UI, RunRevisitListTUI - just fed ResolveDiffCandidates instead of ResolveRevisitEntries) and the diff tree view, for as long as the user keeps picking runs to compare against - the same "list <-> detail" loop shape RunRevisitVerb already uses, one level down (a diff tree instead of a full live NewLiveTUI).

func RunDiffTreeTUI

func RunDiffTreeTUI(alignments []PlayAlignment, newSourceIndex, oldSourceIndex source.TaskSourceIndex)

RunDiffTreeTUI shows alignments' own filtered, diff-annotated tree in a standalone Application - not another NewLiveTUI (the data model here is pairs of tasks/hosts, not one PlaybookState; forcing it through NewLiveTUI's single-state-oriented plumbing - sourceIndex, requestRerun, live-generation bookkeeping - would fight it at every turn rather than reuse it, per design-docs/Diff.md's own implementation notes). newSourceIndex/oldSourceIndex back the drill-down's own Task-definition diffing (BuildDiffOutputTabs) - the current session's own sourceIndex, and one freshly built for the comparison run (RunDiffFlow).

Two nested "back" levels, both Esc/q: from the drill-down (viewingOutput) back to the tree, then from the tree back out of this Application entirely (to RunDiffFlow's own candidate list) - no 'd' binding inside diff mode itself, confirmed deliberate, not just unaddressed.

The cursor-highlighting/rebuild-on-toggle structure mirrors RunRevisitListTUI's own (revisit.go) - TreeList has no built-in "current row" look of its own - but rebuilds on more than a selection change: toggling a task's own expand state changes the row *count* too, handled the same way (recompute currentRows, re-add everything, restore the cursor by index).

func SingleRunTabs

func SingleRunTabs(task *playbook.TaskNode, host string, sourceIndex source.TaskSourceIndex, docs uikit.ResolvedRender, side string) (names []string, contents []string)

SingleRunTabs is BuildOutputTabs' own result, filtered down to what design-docs/Diff.md's drill-down asks for even for an unmatched (old-only/new-only) task alignment, which has nothing to diff against - "each tab just shows that one run's own single content, same as the normal (non-diff) drill-down would." Diff (ansible's own before/after) is dropped, same reasoning as the matched case below ("too confusing" applies whether or not there's a second run to compare against); so is Resolved, deliberately - see BuildDiffOutputTabs' own doc comment for why diff mode skips it for now.

side is "old"/"new" when task genuinely exists only on that side of the alignment - UnmatchedTaskNote is then prepended to the Task tab so the drill-down says outright why there's nothing diffed here, rather than silently looking identical to a normal single-run view (real user confusion, reported live: a task shown in the diff tree - e.g. a notify:-triggered handler that fired in one run and wasn't notified in the other - drilled into and showing no diff markup at all read as "no differences found" rather than "this task didn't run on the other side"). "" (the decode-failure fallback in BuildDiffOutputTabs, where the task genuinely exists on both sides) skips the note entirely - it would be actively wrong there.

func SortedHostOrder

func SortedHostOrder(hostOrder []string) []string

SortedHostOrder returns a copy of hostOrder sorted alphabetically - hostOrder itself is report-arrival order (aggregate.go's TaskNode.record appends whichever host happens to answer first), which is deliberately what render.go's plain-text dump follows, but is meaningless (and, with parallel forks, different from task to task) as a *display* order. The live tree's own TaskLabel never has this problem since it iterates state.AllHosts (alphabetically sorted) instead of any one task's HostOrder - diff mode has no equivalent run-wide host set spanning both the old and new PlaybookState trees, so it sorts locally here instead.

func StripTags

func StripTags(s string) string

StripTags removes tview color/style tags from s, leaving the plain text a tab's own content actually says - needed before diffing two runs' tab content (BuildDiffOutputTabs below): the existing tab builders (BuildTaskTab etc.) return tag-decorated text meant for direct display, and diffing that as-is would treat a plain color change (e.g. an outcome going from green to red) as a text difference, which is noise - the tree's own underline marking already surfaces outcome changes separately. Not a full tview tag parser, same "documented heuristic, good enough" tolerance this project already applies elsewhere (e.g. ColorizeYAML).

func TaskDiffers

func TaskDiffers(a TaskAlignment) bool

TaskDiffers reports whether a matched pair (both OldTask/NewTask set - see TaskAlignment's own doc comment) counts as "different" (design-docs/Diff.md's own "What counts as different" section): any host present in *both* tasks' own Hosts map has a different outcome, or different output (HostOutputDiffers below). A host present on only one side is skipped entirely - per design-docs/Diff.md's own "wouldn't count a difference in hosts... as a difference." Since display always follows the *new* task's own HostOrder (design-docs/Diff.md's "render based on the new version"), a host that only existed on the old side simply never appears in diff mode at all - nothing further needed for that to fall out correctly.

Always true for an unmatched alignment (OldTask or NewTask nil, by construction always "different") - callers that already know an alignment is unmatched don't need to call this at all, but it's safe to either way.

func TaskNames

func TaskNames(tasks []*playbook.TaskNode) []string

func UnmatchedMarker

func UnmatchedMarker(a TaskAlignment) string

UnmatchedMarker is DiffTaskLine's own plain-text signal for an unmatched task - "" for a matched one. Added alongside wholeLineFlag's strikethrough/italic, not instead of it, after live testing found strikethrough (design-docs/Diff.md) doesn't render at all over mosh, in any terminal - a marker needs no text-attribute support whatsoever, so it's the one signal guaranteed visible regardless of what the user's terminal (or terminal multiplexer) actually implements.

func UnmatchedTaskNote

func UnmatchedTaskNote(side string) string

UnmatchedTaskNote is SingleRunTabs' own callout, prepended to the Task tab for an old-only/new-only task alignment - see SingleRunTabs' doc comment for why this exists. Deliberately terse (one line, no elaboration) per live feedback - the strikethrough-marked tree row already says "unmatched"; this just confirms it in the one place a user might otherwise read "no diff shown" as "nothing found."

Types

type DiffHostRowID

type DiffHostRowID struct {
	// contains filtered or unexported fields
}

DiffHostRowID identifies one expanded host row for cursor-preservation/ selected-row-rendering purposes (RunDiffTreeTUI) - mirrors the live tree's own HostRowID{task, host} shape.

type PlayAlignment

type PlayAlignment struct {
	OldPlay, NewPlay *playbook.PlayNode
	Tasks            []TaskAlignment
}

PlayAlignment pairs up a play from an "old" run with its counterpart in a "new" run, plus its own tasks' alignment (AlignTasks). Exactly one of OldPlay/NewPlay is nil for a play that only exists on one side.

func AlignPlays

func AlignPlays(oldState, newState *playbook.PlaybookState) []PlayAlignment

AlignPlays is AlignTasks's own sibling, one level up: plays matched by name across the whole playbook, same algorithm, same reasoning. A whole play only existing on one side needs no special handling of its own here - every one of its own tasks becomes old-only/new-only via AlignTasks(nil, play)/AlignTasks(play, nil), which is exactly what makes it "contain tasks with differences" and show up once rendered, per design-docs/Diff.md - see PlayAlignmentHasDifferences below.

type TaskAlignment

type TaskAlignment struct {
	OldTask, NewTask *playbook.TaskNode
}

TaskAlignment pairs up a task from an "old" run with its counterpart in a "new" run. Exactly one of OldTask/NewTask is nil for a task that only exists on one side (added/removed since the old run, or moved into/out of a play that itself only exists on one side - see PlayAlignment); both set for a matched pair, further comparable via TaskDiffers.

func AlignTasks

func AlignTasks(oldPlay, newPlay *playbook.PlayNode) []TaskAlignment

AlignTasks aligns oldPlay's and newPlay's own task sequences by name, via difflib.SequenceMatcher (already a dependency - the same library BuildDiffTab already uses for line-level diffs, reused here for name- level sequence alignment instead of a custom LCS implementation).

Matching by name rather than by task.Path deliberately: editing the playbook between debug runs (the whole reason this feature exists) shifts line numbers for everything below the edit, even for tasks that didn't change themselves - path-matching would misread that as "this task no longer exists" and cascade into spurious mismatches for everything after the edit point. Worse, a role-originated session's own task.Path points at a freshly generated stub every single time (StartRoleSession) - path-matching would never match anything at all for a role diff, even with zero real changes. Name-based matching risks the opposite failure - two same-named tasks in one play misaligning - but SequenceMatcher's own alignment leans on surrounding position, not just raw equality, so it tends to do the sensible thing even then: same "documented heuristic, not chased further" tradeoff this codebase already makes elsewhere (TaskLabel's own truncation, PrimaryOutputField's stdout-vs-msg choice).

oldPlay/newPlay may themselves be nil - the play that owns them only exists on one side (see AlignPlays). Every task on the present side then becomes old-only/new-only uniformly, via the exact same 'i'/'d' opcode handling below - no special-casing needed here for that case.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL