Skip to content

Treat an applied patch as a named layer, not an undo blob - #318

Merged
juanmaguitar merged 4 commits into
trunkfrom
juanmaguitar/applied-patch-as-layer
Aug 13, 2026
Merged

Treat an applied patch as a named layer, not an undo blob#318
juanmaguitar merged 4 commits into
trunkfrom
juanmaguitar/applied-patch-as-layer

Conversation

@juanmaguitar

@juanmaguitar juanmaguitar commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Why

An applied PR remains part of a ticket’s work, but the app described every touched file as the contributor’s own and a failed Revert could blame the PR author. That attribution is misleading and makes the safe exit hard to find.

What changes

The applied patch is retained as a named layer:

  • conflict copy says a file includes changes from the named PR and may also contain the contributor’s edits;
  • Revert remains available whenever the patch text was retained;
  • pressing Revert performs the existing reverse-apply check and reports overlapping edits there;
  • a patch too large to retain offers save-a-copy plus discard-to-base;
  • the one-patch slot remains occupied until Revert or discard clears it.

Routine site:status no longer reads and diffs patch files synchronously. The proactive “absorbed” classifier/state and categorical “not by you” copy have been removed.

How to test this

Platforms: any.

  1. Apply a PR on a linked ticket.
    Expected: its named banner appears with Revert this patch.
  2. Preview another patch touching the same file.
    Expected: the warning says the file includes changes from the applied PR and may also contain your edits; it never says “not by you”.
  3. Edit a line brought by the applied PR and press Revert this patch.
    Expected: Revert fails without touching anything and explains the overlap, offering save-a-copy/discard rather than asking the author for a rebase.
  4. Undo that edit and press Revert again.
    Expected: it succeeds and clears the named layer.

What must not have happened: routine refreshes must not synchronously scan patch files; a failed Revert must not partially edit the tree or free the one-patch slot.

Risks and limitations

The banner does not predict whether a retained patch is still reversible; it answers only when the user requests Revert. This keeps routine main-process status reads cheap and keeps error details attached to the action they explain.

The desktop flow remains to be driven from the integration artifact.

Related

Fixes #306. Part of #309. Builds on #317 and sharpens #313.


Design decisions and alternatives considered

The previous version diagnosed every applied layer during site:status, adding synchronous file reads and an “absorbed” state. The simplified version uses the reverse apply operation as the diagnostic only when Revert is pressed.

Review outcome

2 [fix here] — both fixed: synchronous status-path diagnosis was removed, and same-file ownership now preserves uncertainty. Lint is clean and the branch’s full suite passes 979 tests.

Implementation notes

The simplification removed 310 net lines from the reviewed version. Patch text remains in the main process and never crosses IPC.

Screenshots or recording

No new layout. The existing applied-layer and failure banners render the revised copy/actions.

@juanmaguitar juanmaguitar added v1.0-rc2 ticket-work A ticket is a branch: its base, what it accumulates, and keeping it current labels Aug 12, 2026
@juanmaguitar
juanmaguitar force-pushed the juanmaguitar/applied-patch-as-layer branch from e1c9daf to 8e44701 Compare August 12, 2026 12:00
@juanmaguitar
juanmaguitar changed the base branch from juanmaguitar/the-patch-conflict-warning-measures-against-the to juanmaguitar/wrong-advise-rebase-author August 12, 2026 12:00
@juanmaguitar

Copy link
Copy Markdown
Collaborator Author

Rebased onto #313 and retargeted at its branch. Both changes add to describeApplyFailure and to the same call site in the renderer, so as siblings they collided; stacked, each diff stays its own.

The combination needed a decision rather than a mechanical merge, so it is stated here: a failed revert is asked about first, ahead of the pull-request framing. A revert only fails because the contributor's own edits are on the layer's lines, so "this pull request is behind trunk" is never the right sentence for it. The non-revert path keeps #313's ownWorkPaths.

Suite on the combined branches: 984 pass, 0 fail; lint clean.

@juanmaguitar
juanmaguitar force-pushed the juanmaguitar/applied-patch-as-layer branch from 8e44701 to ecc03b2 Compare August 12, 2026 12:07
@juanmaguitar
juanmaguitar changed the base branch from juanmaguitar/wrong-advise-rebase-author to juanmaguitar/unknown-ticket-base August 12, 2026 12:07
@juanmaguitar

Copy link
Copy Markdown
Collaborator Author

Rebased into the stack: #313#317#318. All three rewrite the same two places — describeApplyFailure and the block above Apply — so as siblings they collided; stacked, each diff stays its own.

Two combinations needed a decision rather than a mechanical merge, and both are in the last commit:

  • A failed revert is asked about first, ahead of the pull-request framing. A revert only fails because the contributor's own edits are on the layer's lines, so "this pull request is behind trunk" is never the right sentence for it.
  • The notice above Apply is composed, not chosen. Tell the contributor's own edits apart from the patch they applied #306's attribution owns the file list; When a ticket's base is unknown, the app guesses instead of saying so #308's base status says how far that list can be trusted. describePreviewNotice returns both, the renderer only picks a style. The fuller hedge is used where files are named — that a list built against today's trunk can name files you never touched and miss ones you did is the actionable half — and the quiet one stays for the case where nothing collided. describeOwnWorkWarning is superseded by it and removed; its cases moved onto the composed answer rather than going with it.

Suite on the stacked branches: 999 pass, 0 fail; lint clean.

@juanmaguitar

Copy link
Copy Markdown
Collaborator Author

Final integration review found one ownership mismatch: a second PR failure could call a file the contributor's even when its only known provenance was the previously applied layer. Fixed in d90eb07 by carrying that layer into the failure framing; the regression names the first PR and preserves uncertainty. Full branch suite: 980 passing.

@juanmaguitar

Copy link
Copy Markdown
Collaborator Author

A further final-review pass found two truthfulness issues and both are fixed in 90d2bad: all-applied changes are now described as already in the checkout when ticket/layer provenance makes trunk unprovable, and a stale Revert record is cleared through writeWorkMeta so the active branch actually loses the banner/slot. Regressions cover both; Node and Electron suites pass.

Base automatically changed from juanmaguitar/unknown-ticket-base to trunk August 13, 2026 07:42
juanmaguitar and others added 4 commits August 13, 2026 09:42
A ticket is a branch (#108): trunk, at most one applied patch or pull
request, and the contributor's own edits. The branch holds that
faithfully; the app's bookkeeping did not. `appliedPatch` was remembered
only as something to undo, so everything the UI said about the ticket was
read off the bare diff — and every file the applied pull request brought
was announced as the contributor's own writing.

Attribution. The record already stores which paths the patch touched, so
the pre-apply warning now names two owners instead of one: a file the
layer brought and nobody has edited over is described as coming from it,
and a file the contributor has also worked on keeps naming their work,
which is the answer that decides what they do next. Neither is dropped
from the warning. The same split reaches the failure narration: a revert
that will not come back out is the contributor's own edits sitting on
lines they applied, never a pull request its author should rebase.

Absorption. `revertable` used to mean "we stored the text". It now means
what the banner actually asks — can this layer still be lifted out of
this checkout, right now — measured by `diagnoseRemoval`, which reverses
the stored patch through the same `resolveFile`/`diagnoseHunks` machinery
the apply path uses and writes nothing. Once the contributor's edits are
on the patch's own lines it is absorbed: Revert stands down, and the
honest exits are saving a copy of the work and discarding the ticket to
its base, which on this project is a normal way forward rather than a
defeat. Nothing persists an "absorbed" flag, so undoing the overlapping
edit brings Revert back on its own — and absorption never frees the
one-patch slot, because the record survives as provenance.

The banner's decision lives in `src/renderer/applied-layer.cjs`, unit
tested directly; the patch text still never crosses IPC.

Fixes #306

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juanmaguitar
juanmaguitar force-pushed the juanmaguitar/applied-patch-as-layer branch from 90d2bad to 4fe4d87 Compare August 13, 2026 07:42
@juanmaguitar
juanmaguitar merged commit eb8d2fc into trunk Aug 13, 2026
3 checks passed
juanmaguitar added a commit that referenced this pull request Aug 13, 2026
## Why

After a contributor chooses **Discard this ticket to its base** from a
failed Revert, the discard succeeds but the old red patch error remains
on screen. The checkout is clean, yet the interface still claims the
operation failed.

The original #325 belonged to the stale stack that GitHub closed while
#324 was being recovered, so this PR lands the already approved fix
directly on `trunk`.

## What changes

After a successful discard, clear the stale apply error and related
preview state. If discard fails, retain all of that state so the
contributor can still understand and recover from the failure.

## How to test this

**Platforms:** macOS and Windows.

**Starting state:** ticket #65819 linked, PR #13017 applied, with this
contributor edit on an applied line in
`tests/phpunit/tests/customize/widgets.php`:

```php
$this->assertIsCallable( $args['sanitize_callback'], 'sanitize_callback is callable' ); // Mi cambio.
```

1. Click **Revert this patch**. Revert must be rejected, the checkout
must remain unchanged, and the red error must offer **Save a copy of
your work** and **Discard this ticket to its base**.
2. Click **Discard this ticket to its base**.
3. The applied PR, contributor edit, applied-layer card, and red error
must all disappear. Ticket #65819 must remain linked.

**What must not have happened:**

- A failed discard must not clear the error or preview state.
- The ticket itself must not be unlinked.
- No unrelated ticket work may be removed.

This flow passed in the approved integration artifact at commit
`012d7df` on macOS and Windows.

## Risks and limitations

The final Git tree is exactly the tree manually approved for the release
candidate. Self-review: 0 [fix here] · 0 [follow-up].

## Related

Replacement landing for #325. Follow-up to #318 and #330.

---

<details>
<summary>Design decisions and alternatives considered</summary>

The cleanup is derived in the existing pure reducer and only committed
after `outcome.ok`. Keeping failure state unchanged avoids hiding
recovery information after an unsuccessful destructive operation.

A standalone PR avoids reusing the closed stack topology that caused
#324 not to land on `trunk`.

</details>

<details>
<summary>Review outcome (required — see AGENTS.md)</summary>

0 [fix here] · 0 [follow-up]. Lint, 1010/1010 tests, and `git diff
--check` pass. The branch resolves to approved tree
`d3904782d5e434df8591e99d3ccf2a49a52ebcc9`.

</details>

<details>
<summary>Implementation notes</summary>

Recovery head before squash: `6a88a26`. It contains one commit directly
on #330's squash commit.

</details>

<details>
<summary>Screenshots or recording</summary>

The user-visible flow was manually validated on both target platforms in
the approved integration artifact.

</details>
@juanmaguitar
juanmaguitar deleted the juanmaguitar/applied-patch-as-layer branch August 21, 2026 09:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ticket-work A ticket is a branch: its base, what it accumulates, and keeping it current v1.0-rc2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tell the contributor's own edits apart from the patch they applied

1 participant