Skip to content

fix(work): keep spaced paths from diff headers in the attempt diff canvas - #282

Merged
Lucas1479 merged 2 commits into
Code-Amadeus:mainfrom
NopeCharon:fix/unified-diff-spaced-header-paths
Nov 3, 2026
Merged

Lucas1479 merged 2 commits into
Code-Amadeus:mainfrom
NopeCharon:fix/unified-diff-spaced-header-paths

Conversation

@NopeCharon

Copy link
Copy Markdown
Contributor

What and why

Git leaves paths that only contain spaces unquoted, so the header for my notes.txt is diff --git a/my notes.txt b/my notes.txt. _paths_from_diff_header split it with shlex.split and took the 3rd and 4th words, so the paths became a/my and notes.txt.

Ordinary text edits hid this because the following --- a/... / +++ b/... lines overwrite the path. Deleted files (+++ /dev/null), binary files and mode-only changes have no such line. The canvas named them notes.txt and then appended the real path from changed_files as a second, hunk-less "modified" entry (details in #280).

Without a rename or copy, both names in the header are the same path behind their prefixes. That is also how Git itself splits an unquoted header. For unquoted headers, the parser now splits at the space where the two names match after removing their first path component, or where they are identical (diff.noprefix). Renames and copies keep the previous fallback and are still corrected by their rename from / rename to lines. Quoted headers are unchanged.

Fixes #280

Linked Issue for product-semantic or public-contract changes: none (routine fix)

Change class

  • Routine fix, documentation, test, maintenance, or presentation-only UI
  • Product-semantic or public-contract change discussed in the linked Issue
  • Isolated, default-off experiment

Owning layer: server/unified_diff.py, the parser that turns git diff output into the provider-neutral diff canvas shape.

User-visible effect, or none: in the Work attempt diff canvas, deleted, binary and mode-only files whose paths contain spaces keep their real path and are listed once.

Compatibility or migration impact, or none: none. The output shape is unchanged.

Evidence

Commands and manual journeys run:

  • python -m pytest -q tests/test_unified_diff_canvas.py tests/test_work_export_integration.py: 29 passed (Linux, uv sync --locked --extra dev). python tests/test_unified_diff_canvas.py also passes.

  • The new test test_parse_unified_diff_keeps_spaced_paths_without_marker_lines fails on main and passes with this change.

  • python -m ruff check server tests/test_unified_diff_canvas.py: passed.

  • Parsed real git diff HEAD output from a scratch repository after deleting my file.txt and editing the binary my img.bin. Before: file.txt (deleted), img.bin, plus duplicate my file.txt / my img.bin entries. After: my file.txt (deleted) and my img.bin only.

  • Relevant Python tests pass

  • CPU/model-less baseline remains supported

  • Electron npm run build passes when Electron code changed

  • Dependency audit passes when dependencies changed

  • Before/after screenshots are attached for visible UI changes

  • Documentation/examples are updated for changed settings or contracts

Final check

  • This PR addresses one coherent problem without unrelated cleanup
  • It does not add a speculative API, fallback, or compatibility path
  • No secrets, local state, model weights, voice material, or restricted assets are included
  • Third-party notices and provenance are preserved

…nvas

Git leaves paths that only contain spaces unquoted, so the header for
"my notes.txt" is "diff --git a/my notes.txt b/my notes.txt".
_paths_from_diff_header split it with shlex and took the third and fourth
words, which yields "a/my" and "notes.txt".

Text edits hid this because the following "--- a/..." / "+++ b/..." lines
overwrite the path. Deleted files (+++ /dev/null), binary files and
mode-only changes have no such line, so the canvas named them
"notes.txt" and then appended the real path from changed_files as a
second, hunk-less "modified" entry.

Without a rename or copy, both header names are the same path behind
their prefixes. For unquoted headers, split at the space where the two
names match after removing their first path component, or where they
are identical (diff.noprefix). Renames and copies keep the previous
fallback and are still corrected by their "rename from/to" lines.
Quoted headers are unchanged.

Fixes Code-Amadeus#280

@Lucas1479 Lucas1479 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you for the precise path-parsing fix and regression test. Independent real-Git checks confirm that deleted, binary and mode-only files with spaces retain their names and no longer produce duplicate canvas entries. The branch has been updated against main; the original patch is unchanged, and merge will wait for CI on this updated head. The comment about copy headers is broader than the current parser supports, but that is a separate existing copy-detection case and does not block this fix.

@Lucas1479
Lucas1479 merged commit b038a22 into Code-Amadeus:main Nov 3, 2026
15 checks passed
Lucas1479 added a commit that referenced this pull request Nov 3, 2026
## What and why

Fixes #281. Git patch lines are delimited by LF. Python's `splitlines()`
also splits form feeds, vertical tabs and Unicode separators that belong
inside a source line, producing truncated text, phantom changes and
incorrect line numbers in the Work diff canvas.

The parser now splits only on LF and retains its existing CRLF display
handling. The Host's new-file patch generator uses the same LF framing,
preserves empty/trailing blank lines and emits the missing-final-newline
marker based on LF. Final Git-delta assembly no longer strips source
whitespace from the last patch line.

Owning layers: `server/unified_diff.py` and
`server/work_artifact_registry.py`. Existing output schemas, permissions
and attribution rules are unchanged. The generator is shared by
Git-untracked files and new files in non-Git workspaces.

## Change class

- [x] Routine bug fix with regression coverage
- [ ] Product-semantic or public-contract change
- [ ] Isolated experiment

## Evidence

- The author's parser regression and real Git examples demonstrate the
original count/text corruption.
- Eight generator-to-parser cases cover embedded separators, other
control characters, empty files, blank/trailing blank lines, CRLF,
missing final LF and bare CR.
- Three end-to-end attempt cases cover Git-tracked, Git-untracked and
filesystem-new files, including trailing form-feed/space preservation.
- New regressions were observed failing before their corresponding
fixes.
- On Windows/Python 3.12.10, `python -X utf8 -m pytest
tests/test_local_git_provider_diff.py
tests/test_work_artifact_registry.py tests/test_unified_diff_canvas.py
tests/test_work_export_integration.py -q`: **61 passed** on head
`9b8bbe869901b53ca707eb1486a721b0218d1ade`, including merged #284 and
#282.
- Ruff on all four changed files and `git diff --check`: passed.
- No live provider calls or local Electron journey were needed for this
parser/generator change; hosted checks cover the broader application.

## Final check

- [x] One coherent content-preservation fix
- [x] No new dependency, schema, fallback or public configuration
- [x] No secrets, personal state or restricted assets
- [x] CPU/model-less baseline retained

---------

Co-authored-by: Lucas1479 <sli776@aucklanduni.ac.nz>
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.

Work diff canvas misnames deleted, binary and mode-only files whose paths contain spaces

2 participants