@magic-marker/prosemirror-suggest-changes 0.3.1 → 0.3.3-wrap-unwrap.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/commands.d.ts +3 -1
  2. package/dist/commands.js +14 -2
  3. package/dist/contentBetween.d.ts +2 -0
  4. package/dist/contentBetween.js +33 -0
  5. package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapAllNodes.data.d.ts +38 -0
  6. package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapAllNodes.playwright.test.d.ts +1 -0
  7. package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapAllNodes.test.d.ts +1 -0
  8. package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapOneNode.data.d.ts +45 -0
  9. package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapOneNode.playwright.test.d.ts +1 -0
  10. package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapOneNode.test.d.ts +1 -0
  11. package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapSingleNode.data.d.ts +38 -0
  12. package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapSingleNode.playwright.test.d.ts +1 -0
  13. package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapSingleNode.test.d.ts +1 -0
  14. package/dist/features/wrapUnwrap/__tests__/blockquoteWrapAllNodes.data.d.ts +38 -0
  15. package/dist/features/wrapUnwrap/__tests__/blockquoteWrapAllNodes.playwright.test.d.ts +1 -0
  16. package/dist/features/wrapUnwrap/__tests__/blockquoteWrapAllNodes.test.d.ts +1 -0
  17. package/dist/features/wrapUnwrap/__tests__/blockquoteWrapSingleNode.data.d.ts +38 -0
  18. package/dist/features/wrapUnwrap/__tests__/blockquoteWrapSingleNode.playwright.test.d.ts +1 -0
  19. package/dist/features/wrapUnwrap/__tests__/blockquoteWrapSingleNode.test.d.ts +1 -0
  20. package/dist/features/wrapUnwrap/__tests__/listItemLiftLast.data.d.ts +54 -0
  21. package/dist/features/wrapUnwrap/__tests__/listItemLiftLast.playwright.test.d.ts +1 -0
  22. package/dist/features/wrapUnwrap/__tests__/listItemLiftLast.test.d.ts +1 -0
  23. package/dist/features/wrapUnwrap/__tests__/listItemLiftMiddle.data.d.ts +48 -0
  24. package/dist/features/wrapUnwrap/__tests__/listItemLiftMiddle.playwright.test.d.ts +1 -0
  25. package/dist/features/wrapUnwrap/__tests__/listItemLiftMiddle.test.d.ts +1 -0
  26. package/dist/features/wrapUnwrap/__tests__/listItemLiftMultipleToTheTop.data.d.ts +74 -0
  27. package/dist/features/wrapUnwrap/__tests__/listItemLiftMultipleToTheTop.playwright.test.d.ts +1 -0
  28. package/dist/features/wrapUnwrap/__tests__/listItemLiftMultipleToTheTop.test.d.ts +1 -0
  29. package/dist/features/wrapUnwrap/__tests__/listItemLiftNestedOnce.data.d.ts +71 -0
  30. package/dist/features/wrapUnwrap/__tests__/listItemLiftNestedOnce.playwright.test.d.ts +1 -0
  31. package/dist/features/wrapUnwrap/__tests__/listItemLiftNestedOnce.test.d.ts +1 -0
  32. package/dist/features/wrapUnwrap/__tests__/listItemLiftTop.data.d.ts +54 -0
  33. package/dist/features/wrapUnwrap/__tests__/listItemLiftTop.playwright.test.d.ts +1 -0
  34. package/dist/features/wrapUnwrap/__tests__/listItemLiftTop.test.d.ts +1 -0
  35. package/dist/features/wrapUnwrap/__tests__/listItemSinkMultiple.data.d.ts +181 -0
  36. package/dist/features/wrapUnwrap/__tests__/listItemSinkMultiple.page.d.ts +30 -0
  37. package/dist/features/wrapUnwrap/__tests__/listItemSinkMultiple.playwright.test.d.ts +1 -0
  38. package/dist/features/wrapUnwrap/__tests__/listItemSinkMultiple.test.d.ts +1 -0
  39. package/dist/features/wrapUnwrap/__tests__/listItemSinkOneOnce.data.d.ts +51 -0
  40. package/dist/features/wrapUnwrap/__tests__/listItemSinkOneOnce.playwright.test.d.ts +1 -0
  41. package/dist/features/wrapUnwrap/__tests__/listItemSinkOneOnce.test.d.ts +1 -0
  42. package/dist/features/wrapUnwrap/__tests__/listItemsLiftMixedLevels.data.d.ts +74 -0
  43. package/dist/features/wrapUnwrap/__tests__/listItemsLiftMixedLevels.playwright.test.d.ts +1 -0
  44. package/dist/features/wrapUnwrap/__tests__/listItemsLiftMixedLevels.test.d.ts +1 -0
  45. package/dist/features/wrapUnwrap/__tests__/nestedListItemLiftToOuter.data.d.ts +71 -0
  46. package/dist/features/wrapUnwrap/__tests__/nestedListItemLiftToOuter.playwright.test.d.ts +1 -0
  47. package/dist/features/wrapUnwrap/__tests__/nestedListItemLiftToOuter.test.d.ts +1 -0
  48. package/dist/features/wrapUnwrap/__tests__/nestedListItemsLiftMultipleLevels.data.d.ts +114 -0
  49. package/dist/features/wrapUnwrap/__tests__/nestedListItemsLiftMultipleLevels.playwright.test.d.ts +1 -0
  50. package/dist/features/wrapUnwrap/__tests__/nestedListItemsLiftMultipleLevels.test.d.ts +1 -0
  51. package/dist/features/wrapUnwrap/__tests__/testUtils.d.ts +5 -0
  52. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftMultipleNodesToTheTop.data.d.ts +44 -0
  53. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftMultipleNodesToTheTop.playwright.test.d.ts +1 -0
  54. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftMultipleNodesToTheTop.test.d.ts +1 -0
  55. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftMultipleNodesUp.data.d.ts +38 -0
  56. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftMultipleNodesUp.playwright.test.d.ts +1 -0
  57. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftMultipleNodesUp.test.d.ts +1 -0
  58. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeFromMiddle.data.d.ts +46 -0
  59. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeFromMiddle.playwright.test.d.ts +1 -0
  60. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeFromMiddle.test.d.ts +1 -0
  61. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeToTheTop.data.d.ts +57 -0
  62. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeToTheTop.playwright.test.d.ts +1 -0
  63. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeToTheTop.test.d.ts +1 -0
  64. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeUp.data.d.ts +45 -0
  65. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeUp.playwright.test.d.ts +1 -0
  66. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeUp.test.d.ts +1 -0
  67. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteWrap.data.d.ts +44 -0
  68. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteWrap.playwright.test.d.ts +1 -0
  69. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteWrap.test.d.ts +1 -0
  70. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteWrapMultiple.data.d.ts +44 -0
  71. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteWrapMultiple.playwright.test.d.ts +1 -0
  72. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteWrapMultiple.test.d.ts +1 -0
  73. package/dist/features/wrapUnwrap/addStructureMark.d.ts +41 -0
  74. package/dist/features/wrapUnwrap/addStructureMark.js +15 -0
  75. package/dist/features/wrapUnwrap/findMatchingNodeSides.d.ts +15 -0
  76. package/dist/features/wrapUnwrap/findMatchingNodeSides.js +133 -0
  77. package/dist/features/wrapUnwrap/handleStructureStep.d.ts +4 -0
  78. package/dist/features/wrapUnwrap/handleStructureStep.js +174 -0
  79. package/dist/features/wrapUnwrap/revertStructureSuggestion.d.ts +44 -0
  80. package/dist/features/wrapUnwrap/revertStructureSuggestion.js +374 -0
  81. package/dist/generateId.js +2 -2
  82. package/dist/index.d.ts +1 -1
  83. package/dist/index.js +1 -1
  84. package/dist/rebaseStep.d.ts +9 -0
  85. package/dist/rebaseStep.js +11 -0
  86. package/dist/replaceAroundStep.js +6 -1
  87. package/dist/replaceStep.js +5 -0
  88. package/dist/schema.d.ts +2 -1
  89. package/dist/schema.js +37 -1
  90. package/dist/testing/testBuilders.d.ts +1 -2
  91. package/dist/utils.d.ts +1 -0
  92. package/dist/utils.js +6 -2
  93. package/package.json +1 -1
  94. package/src/features/wrapUnwrap/README.md +198 -0
@@ -0,0 +1,198 @@
1
+ # Structure Suggestion Tracking & Reversal
2
+
3
+ ## Purpose
4
+
5
+ Track structural changes (wrap/unwrap in blockquotes, list item lift/sink) in
6
+ ProseMirror as "suggestions" that can be reverted later, without losing the
7
+ ability to reconstruct the original document state.
8
+
9
+ ---
10
+
11
+ ## Why Marks on Nodes?
12
+
13
+ **The Problem**: Document positions are ephemeral. A position like `from: 10`
14
+ becomes invalid after any edit before it.
15
+
16
+ **The Solution**: Anchor positions to **actual nodes** in the document using
17
+ marks. To recover a position:
18
+
19
+ 1. Find the marked node (which moves with document edits)
20
+ 2. Compute position from node's current `pos` + the `position` attribute:
21
+ - `position: "start"` → use `pos`
22
+ - `position: "end"` → use `pos + node.nodeSize`
23
+
24
+ This way, positions are **self-healing** — they remain valid as long as the
25
+ marked nodes exist.
26
+
27
+ **Why "start" vs "end"?** Because the 4 positions of a `ReplaceAroundStep` don't
28
+ always align with node boundaries in the same way. The mark must be placed on a
29
+ node that **exists in the final document**, and the position must be derivable
30
+ from that node's boundaries.
31
+
32
+ ---
33
+
34
+ ## Mark Structure
35
+
36
+ ### For `ReplaceAroundStep` (4 marks)
37
+
38
+ Each mark contains:
39
+
40
+ | Field | Purpose |
41
+ | ----------- | ------------------------------------------------------- |
42
+ | `id` | Suggestion ID (groups marks together) |
43
+ | `value` | `"from"` \| `"to"` \| `"gapFrom"` \| `"gapTo"` |
44
+ | `position` | `"start"` \| `"end"` (how to derive position from node) |
45
+ | `type` | `"replaceAround"` |
46
+ | `slice` | JSON of the slice for the **inverse** step |
47
+ | `insert` | Where gap content goes in the slice |
48
+ | `structure` | Boolean flag for structural step |
49
+
50
+ ### For `ReplaceStep` (2 marks)
51
+
52
+ Only `from` and `to` marks are needed (no gap positions).
53
+
54
+ | Field | Purpose |
55
+ | ----------- | ------------------------------------------ |
56
+ | `id` | Suggestion ID |
57
+ | `value` | `"from"` \| `"to"` |
58
+ | `position` | `"start"` \| `"end"` |
59
+ | `type` | `"replace"` |
60
+ | `slice` | JSON of the slice for the **inverse** step |
61
+ | `structure` | Boolean flag |
62
+
63
+ ### Mark Nesting
64
+
65
+ Marks are nested around content in the document:
66
+
67
+ ```javascript
68
+ doc(
69
+ structure[from, position:start](
70
+ structure[to, position:end](
71
+ blockquote(
72
+ structure[gapFrom, position:start](
73
+ structure[gapTo, position:end](
74
+ paragraph("Hello World")
75
+ ))))))
76
+ ```
77
+
78
+ When traversing with `nodesBetween`, we find each marked node and compute
79
+ positions from it.
80
+
81
+ ---
82
+
83
+ ## How Reversal Works
84
+
85
+ The `revertStructureSuggestion(suggestionId)` command:
86
+
87
+ ```
88
+ 1. FIND the main suggestion's marks
89
+ └── findStructureMarkGroupBySuggestionId(suggestionId)
90
+ └── Scan document for marks with matching ID
91
+ └── Collect: markFrom, markTo, [markGapFrom, markGapTo]
92
+
93
+ 2. FIND nested suggestions within the range
94
+ └── nodesBetween(from, to) to find all structure marks
95
+ └── Collect all unique suggestion IDs
96
+
97
+ 3. SORT by decreasing ID (revert newest first)
98
+ └── [3, 2, 1] — important for nested changes!
99
+
100
+ 4. For EACH suggestion, REVERT:
101
+ └── Compute positions: getPosFromMark(mark, pos, node)
102
+ └── Deserialize slice from JSON
103
+ └── Reconstruct inverse step:
104
+ - ReplaceAroundStep(from, to, gapFrom, gapTo, slice, insert, structure)
105
+ - or ReplaceStep(from, to, slice, structure)
106
+ └── Remove the marks
107
+ └── Apply the step
108
+ ```
109
+
110
+ **Why reverse order?** If step 1 created structure A, and step 2 modified within
111
+ A, you must undo step 2 before step 1, otherwise positions break.
112
+
113
+ ---
114
+
115
+ ## How Tests Work
116
+
117
+ Each test file has 3 data structures:
118
+
119
+ ```typescript
120
+ // 1. Initial document state (before any changes)
121
+ const initialState = doc(paragraph("Hello World"));
122
+
123
+ // 2. Final state (after applying steps, NO marks)
124
+ const finalState = doc(blockquote(paragraph("Hello World")));
125
+
126
+ // 3. Final state WITH structure marks embedded
127
+ // (manually constructed to simulate what forward tracking would produce)
128
+ const finalStateWithMarks = doc(
129
+ structure({ id: 1, data: { value: "from", position: "start", ... } },
130
+ structure({ id: 1, data: { value: "to", position: "end", ... } },
131
+ blockquote(
132
+ structure({ id: 1, data: { value: "gapFrom", ... } },
133
+ structure({ id: 1, data: { value: "gapTo", ... } },
134
+ paragraph("Hello World")
135
+ )))))
136
+ );
137
+
138
+ // The steps that transform initial → final
139
+ const steps = [{ stepType: "replaceAround", from: 0, to: 13, ... }];
140
+
141
+ // The inverse steps that transform final → initial
142
+ const inverseSteps = [{ stepType: "replaceAround", from: 0, to: 15, ... }];
143
+ ```
144
+
145
+ Three test cases per scenario:
146
+
147
+ ```typescript
148
+ // Test 1: Forward transformation works
149
+ it("applies steps correctly", () => {
150
+ assertDocumentChanged(initialState, finalState, applySteps(steps));
151
+ });
152
+
153
+ // Test 2: Inverse steps work (sanity check)
154
+ it("applies inverse steps correctly", () => {
155
+ assertDocumentChanged(
156
+ finalState,
157
+ initialState,
158
+ applySteps(inverseSteps.reverse()),
159
+ );
160
+ });
161
+
162
+ // Test 3: THE MAIN TEST - revert from marks works
163
+ it("reverts via structure suggestion", () => {
164
+ assertDocumentChanged(
165
+ finalStateWithMarks,
166
+ initialState,
167
+ revertStructureSuggestion(1),
168
+ );
169
+ });
170
+ ```
171
+
172
+ ---
173
+
174
+ ## What's Missing: Forward Tracking
175
+
176
+ The code that would:
177
+
178
+ 1. Intercept `ReplaceAroundStep` / `ReplaceStep` in suggest-changes mode
179
+ 2. Apply the step to get the new document
180
+ 3. Compute the inverse step
181
+ 4. Place structure marks on appropriate nodes with:
182
+ - Position encoding (which node, start vs end)
183
+ - Inverse step data (slice, insert, structure flag)
184
+
185
+ Currently `suggestReplaceAroundStep` in `replaceAroundStep.ts` converts
186
+ structural operations to `ReplaceStep` and uses insertion/deletion marks
187
+ instead, losing the structural information needed for proper reversal.
188
+
189
+ ---
190
+
191
+ ## Additional Notes
192
+
193
+ ### Offset Fields
194
+
195
+ The marks contain offset fields (`gapFromOffset`, `fromOffset`, `toOffset`,
196
+ `gapToOffset`) and a `debug` object with computed inverse positions. These are
197
+ present in test data but **not currently used** by the revert code — positions
198
+ are computed purely from node position + `position` field.