Skip to content

fix: preserve frozen slides when slide count changes#537

Merged
k1LoW merged 1 commit into
mainfrom
fix-freeze-slide-count-change
Jul 7, 2026
Merged

fix: preserve frozen slides when slide count changes#537
k1LoW merged 1 commit into
mainfrom
fix-freeze-slide-count-change

Conversation

@k1LoW

@k1LoW k1LoW commented Jul 7, 2026

Copy link
Copy Markdown
Owner

ref: #479

Problem

When a slide with freeze: true exists and the total number of slides changes by inserting or deleting slides, the frozen slide disappears or a wrong slide is preserved.

generateActions resolved freeze by overwriting before[i] with after[i] after slide-count padding, assuming that index i in before corresponds to index i in after. Inserting or deleting slides before a frozen slide breaks that assumption. The overwrite corrupted the padded slides and their internal new/delete marks, so the Hungarian mapping ended up updating or deleting the very slide that should have been preserved.

Approach (frozen slides as order-constrained wildcards)

Frozen slides cannot be matched by content similarity because their actual content may have diverged from the markdown source, which is often the reason for freezing in the first place. So the freeze resolution is moved out of the index-based pre-processing.

  • resolveFrozenSlides (new freeze.go) determines the counterpart of each frozen slide with an order-preserving alignment (Needleman-Wunsch style). Non-frozen slides act as anchors through content similarity, and frozen slides are matched by relative order between those anchors, with a score floor so that content divergence does not prevent matching. A second pass matches remaining frozen slides against unmatched before slides by similarity, covering frozen slides moved across anchors.
  • generateActions substitutes the counterpart's current content into after, so the existing similarity-based pipeline sees the pair as identical and generates no update or delete actions for it, while move actions still work. The former pre-processing that overwrote before[i] is removed.
  • A frozen slide without a counterpart (a brand-new page marked freeze: true) is created from its markdown content. Freezing protects it from the next apply onward.

Behavior change

The existing freeze slide moved test expected [A, C, C], where the frozen slide B was overwritten by an update and C ended up duplicated. The expectation is now [A, C, B]. The frozen slide keeps its actual content and is moved to the desired position, which matches the intent of freezing.

Tests

  • Adds the two failing cases from the issue report (insert and delete before a frozen slide).
  • Adds cases for diverged frozen content with insertion, all slides frozen with insertion between them, and a frozen slide without a counterpart.
  • Adds unit tests for resolveFrozenSlides in freeze_test.go.
  • go test ./..., golangci-lint run ./..., and a 30s FuzzGenerateActions run all pass.

Known limitations

This PR narrows the freeze failure surface to the two issue-reported cases and their close variants, but the Frozen-as-Anchor approach still infers the markdown-page ⟷ existing-slide correspondence rather than recording it. The following combinations remain ambiguous and can produce a surprising result.

  1. All slides frozen with a reorder. before = [A, B, C], after = [C(freeze), B(freeze), A(freeze)]. With no non-frozen anchors, the order-preserving alignment matches by position, so each slide's content stays put and the reorder intent is silently lost.
  2. Frozen slide whose layout has diverged, next to another same-layout-diverged sibling. before = [A(title), B(custom layout, hand-edited), C(title)], after = [A(title), B(freeze, title)]. getSimilarity returns 0 on layout mismatch, so both B and C land at the frozen floor with the same score; the DP tie-break can pick C as the counterpart and the intended B is then removed as a delete-marker.
  3. Multiple frozen slides moved across anchors. The second pass in resolveFrozenSlides is a greedy per-frozen-slide argmax. When several frozen slides compete for the same before slide, the winner depends on iteration order rather than joint optimality.

These are all instances of the residual identity gap noted in the architecture analysis; a follow-up along the "Stable Slide Identity" direction is needed to close them structurally.

generateActions resolved freeze by overwriting before[i] with after[i]
after slide-count padding, on the assumption that index i in before
corresponds to index i in after. Inserting or deleting slides before a
frozen slide breaks that assumption. The overwrite corrupted the padded
slides and their internal new/delete marks, so the Hungarian mapping
ended up updating or deleting the very slide that should have been
preserved.

Instead, resolve each frozen slide's counterpart before padding, using
an order-preserving alignment where non-frozen slides act as anchors
through content similarity and frozen slides are matched by relative
order between those anchors. Frozen slides cannot be matched by content
because their actual content may have diverged from the markdown
source, which is often the reason for freezing in the first place. The
counterpart's current content is then substituted into after, so the
similarity-based pipeline sees the pair as identical and generates no
update or delete actions for it while still allowing moves.

The "freeze slide moved" test expectation changes from [A, C, C] to
[A, C, B] because the frozen slide's actual content is now preserved
and moved instead of being overwritten by its neighbor.

Fix #479
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Code Metrics Report

main (e5d55df) #537 (8a636e3) +/-
Coverage 33.9% 34.8% +0.8%
Code to Test Ratio 1:0.9 1:0.9 +0.0
Test Execution Time 11s 12s +1s
Details
  |                     | main (e5d55df) | #537 (8a636e3) |  +/-  |
  |---------------------|----------------|----------------|-------|
+ | Coverage            |          33.9% |          34.8% | +0.8% |
  |   Files             |             31 |             32 |    +1 |
  |   Lines             |           3416 |           3467 |   +51 |
+ |   Covered           |           1161 |           1207 |   +46 |
+ | Code to Test Ratio  |          1:0.9 |          1:0.9 |  +0.0 |
  |   Code              |           7037 |           7118 |   +81 |
+ |   Test              |           6616 |           6969 |  +353 |
- | Test Execution Time |            11s |            12s |   +1s |

Code coverage of files in pull request scope (80.7% → 82.0%)

Files Coverage +/- Status
action.go 81.1% +0.3% modified
freeze.go 88.8% +88.8% added

Reported by octocov

@k1LoW k1LoW self-assigned this Jul 7, 2026
@k1LoW k1LoW added the enhancement New feature or request label Jul 7, 2026
@k1LoW
k1LoW requested a review from Copilot July 7, 2026 06:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes slide-freeze preservation when slide counts change (insert/delete), by resolving frozen slide counterparts via an order-preserving alignment rather than assuming before[i] corresponds to after[i]. This keeps frozen slides from being incorrectly updated/deleted during the existing similarity + Hungarian mapping pipeline while still allowing move actions.

Changes:

  • Added resolveFrozenSlides alignment logic to map frozen “after” slides to their most likely “before” counterparts without relying on index equality.
  • Updated generateActions to substitute preserved slide content into after for resolved frozen pairs (and to clear Freeze for brand-new frozen slides so append applies content).
  • Added unit tests for the frozen-slide resolver and expanded action-generation tests to cover insert/delete + divergence scenarios.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
freeze.go Introduces frozen-slide counterpart resolution via order-preserving DP alignment + similarity fallback.
freeze_test.go Adds focused unit coverage for frozen-slide resolution scenarios.
action.go Integrates frozen-slide resolution into generateActions and removes index-based freeze overwrite logic.
action_test.go Updates an existing expectation and adds regression tests for insert/delete/divergence/new-frozen-slide cases.

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

@k1LoW
k1LoW marked this pull request as ready for review July 7, 2026 07:31
@k1LoW
k1LoW merged commit 4070c5f into main Jul 7, 2026
3 checks passed
@k1LoW
k1LoW deleted the fix-freeze-slide-count-change branch July 7, 2026 08:00
@github-actions github-actions Bot mentioned this pull request Jun 22, 2026
@k1LoW k1LoW added bug Something isn't working and removed enhancement New feature or request labels Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants