@nuasite/cms 0.8.0 → 0.8.2

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.
package/package.json CHANGED
@@ -14,7 +14,7 @@
14
14
  "directory": "packages/astro-cms"
15
15
  },
16
16
  "license": "Apache-2.0",
17
- "version": "0.8.0",
17
+ "version": "0.8.2",
18
18
  "module": "src/index.ts",
19
19
  "types": "src/index.ts",
20
20
  "type": "module",
@@ -1,5 +1,6 @@
1
1
  import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
2
2
  import { Z_INDEX } from '../constants'
3
+ import { isApplyingUndoRedo, recordChange } from '../history'
3
4
  import { cn } from '../lib/cn'
4
5
  import * as signals from '../signals'
5
6
  import { saveBgImageEditsToStorage } from '../storage'
@@ -410,6 +411,10 @@ function applyBgImageUpdate(
410
411
  const change = signals.getPendingBgImageChange(cmsId)
411
412
  if (!change) return
412
413
 
414
+ // Capture pre-mutation state for undo
415
+ const previousClassName = element.className
416
+ const previousStyleCssText = element.style.cssText
417
+
413
418
  const newChange = { ...change }
414
419
 
415
420
  // Apply bg image class change
@@ -451,6 +456,28 @@ function applyBgImageUpdate(
451
456
  || newChange.newBgPosition !== newChange.originalBgPosition
452
457
  || newChange.newBgRepeat !== newChange.originalBgRepeat
453
458
 
459
+ // Record undo action after DOM is mutated
460
+ if (!isApplyingUndoRedo) {
461
+ recordChange({
462
+ type: 'bgImage',
463
+ cmsId,
464
+ element,
465
+ previousClassName,
466
+ currentClassName: element.className,
467
+ previousStyleCssText,
468
+ currentStyleCssText: element.style.cssText,
469
+ previousBgImageClass: change.newBgImageClass,
470
+ currentBgImageClass: newChange.newBgImageClass,
471
+ previousBgSize: change.newBgSize,
472
+ currentBgSize: newChange.newBgSize,
473
+ previousBgPosition: change.newBgPosition,
474
+ currentBgPosition: newChange.newBgPosition,
475
+ previousBgRepeat: change.newBgRepeat,
476
+ currentBgRepeat: newChange.newBgRepeat,
477
+ wasDirty: change.isDirty,
478
+ })
479
+ }
480
+
454
481
  signals.setPendingBgImageChange(cmsId, newChange)
455
482
  saveBgImageEditsToStorage(signals.pendingBgImageChanges.value)
456
483
  }
@@ -549,11 +549,9 @@ export function discardAllChanges(onStateChange?: () => void): void {
549
549
  })
550
550
 
551
551
  cleanupHighlightSystem()
552
- signals.clearPendingChanges()
553
- signals.clearPendingImageChanges()
554
- signals.clearPendingColorChanges()
555
- signals.clearPendingBgImageChanges()
556
- signals.clearPendingAttributeChanges()
552
+ for (const entry of Object.values(signals.changeRegistry)) {
553
+ entry.mapSignal.value = new Map()
554
+ }
557
555
  clearAllEditsFromStorage()
558
556
  clearHistory()
559
557
  stopEditMode(onStateChange)
@@ -701,7 +699,7 @@ export async function saveAllChanges(
701
699
  sourcePath: bestSourcePath ?? entry?.sourcePath ?? '',
702
700
  sourceLine: bestSourceLine ?? entry?.sourceLine ?? 0,
703
701
  sourceSnippet: bestSourceSnippet ?? entry?.sourceSnippet ?? '',
704
- colorChange: {
702
+ styleChange: {
705
703
  oldClass: origAttr?.value || '',
706
704
  newClass: newAttr.value,
707
705
  type: classType,
@@ -740,7 +738,7 @@ export async function saveAllChanges(
740
738
  sourcePath: entry?.sourcePath ?? '',
741
739
  sourceLine: entry?.sourceLine ?? 0,
742
740
  sourceSnippet: entry?.sourceSnippet ?? '',
743
- colorChange: {
741
+ styleChange: {
744
742
  oldClass: bgChange.oldClass,
745
743
  newClass: bgChange.newClass,
746
744
  type: bgChange.type,
@@ -1,6 +1,12 @@
1
1
  import { computed, signal } from '@preact/signals'
2
2
  import * as signals from './signals'
3
- import { saveAttributeEditsToStorage, saveColorEditsToStorage, saveEditsToStorage, saveImageEditsToStorage } from './storage'
3
+ import {
4
+ saveAttributeEditsToStorage,
5
+ saveBgImageEditsToStorage,
6
+ saveColorEditsToStorage,
7
+ saveEditsToStorage,
8
+ saveImageEditsToStorage,
9
+ } from './storage'
4
10
  import type { Attribute, UndoAction, UndoTextAction } from './types'
5
11
 
6
12
  // ============================================================================
@@ -159,52 +165,104 @@ function scrollToElement(element: HTMLElement): void {
159
165
  }
160
166
  }
161
167
 
162
- function applyReverse(action: UndoAction): void {
163
- switch (action.type) {
164
- case 'text':
168
+ /** Registry of undo/redo handlers keyed by action type.
169
+ * Adding a new UndoAction variant forces a TypeScript error until its handlers are added here. */
170
+ type UndoHandlers = {
171
+ [K in UndoAction['type']]: {
172
+ applyReverse: (action: Extract<UndoAction, { type: K }>) => void
173
+ applyForward: (action: Extract<UndoAction, { type: K }>) => void
174
+ }
175
+ }
176
+
177
+ const undoRegistry: UndoHandlers = {
178
+ text: {
179
+ applyReverse: (action) => {
165
180
  scrollToElement(action.element)
166
181
  applyTextState(action.cmsId, action.element, action.previousHTML, action.previousText, action.wasDirty)
167
- break
168
- case 'image':
182
+ },
183
+ applyForward: (action) => {
184
+ scrollToElement(action.element)
185
+ applyTextState(action.cmsId, action.element, action.currentHTML, action.currentText, true)
186
+ },
187
+ },
188
+ image: {
189
+ applyReverse: (action) => {
169
190
  scrollToElement(action.element)
170
191
  applyImageState(action.cmsId, action.element, action.previousSrc, action.previousAlt, action.wasDirty)
171
- break
172
- case 'color':
192
+ },
193
+ applyForward: (action) => {
194
+ scrollToElement(action.element)
195
+ applyImageState(action.cmsId, action.element, action.currentSrc, action.currentAlt, true)
196
+ },
197
+ },
198
+ color: {
199
+ applyReverse: (action) => {
173
200
  scrollToElement(action.element)
174
201
  applyColorState(action.cmsId, action.element, action.previousClassName, action.previousStyleCssText, action.previousClasses, action.wasDirty)
175
- break
176
- case 'attribute':
202
+ },
203
+ applyForward: (action) => {
177
204
  scrollToElement(action.element)
178
- applyAttributeState(action.cmsId, action.element, action.previousAttributes, action.wasDirty)
179
- break
180
- case 'seo':
181
- applySeoState(action.cmsId, action.previousValue, action.originalValue, action.wasDirty)
182
- break
183
- }
184
- }
185
-
186
- function applyForward(action: UndoAction): void {
187
- switch (action.type) {
188
- case 'text':
205
+ applyColorState(action.cmsId, action.element, action.currentClassName, action.currentStyleCssText, action.currentClasses, true)
206
+ },
207
+ },
208
+ bgImage: {
209
+ applyReverse: (action) => {
189
210
  scrollToElement(action.element)
190
- applyTextState(action.cmsId, action.element, action.currentHTML, action.currentText, true)
191
- break
192
- case 'image':
211
+ applyBgImageState(
212
+ action.cmsId,
213
+ action.element,
214
+ action.previousClassName,
215
+ action.previousStyleCssText,
216
+ action.previousBgImageClass,
217
+ action.previousBgSize,
218
+ action.previousBgPosition,
219
+ action.previousBgRepeat,
220
+ action.wasDirty,
221
+ )
222
+ },
223
+ applyForward: (action) => {
193
224
  scrollToElement(action.element)
194
- applyImageState(action.cmsId, action.element, action.currentSrc, action.currentAlt, true)
195
- break
196
- case 'color':
225
+ applyBgImageState(
226
+ action.cmsId,
227
+ action.element,
228
+ action.currentClassName,
229
+ action.currentStyleCssText,
230
+ action.currentBgImageClass,
231
+ action.currentBgSize,
232
+ action.currentBgPosition,
233
+ action.currentBgRepeat,
234
+ true,
235
+ )
236
+ },
237
+ },
238
+ attribute: {
239
+ applyReverse: (action) => {
197
240
  scrollToElement(action.element)
198
- applyColorState(action.cmsId, action.element, action.currentClassName, action.currentStyleCssText, action.currentClasses, true)
199
- break
200
- case 'attribute':
241
+ applyAttributeState(action.cmsId, action.element, action.previousAttributes, action.wasDirty)
242
+ },
243
+ applyForward: (action) => {
201
244
  scrollToElement(action.element)
202
245
  applyAttributeState(action.cmsId, action.element, action.currentAttributes, true)
203
- break
204
- case 'seo':
246
+ },
247
+ },
248
+ seo: {
249
+ applyReverse: (action) => {
250
+ applySeoState(action.cmsId, action.previousValue, action.originalValue, action.wasDirty)
251
+ },
252
+ applyForward: (action) => {
205
253
  applySeoState(action.cmsId, action.currentValue, action.originalValue, true)
206
- break
207
- }
254
+ },
255
+ },
256
+ }
257
+
258
+ function applyReverse(action: UndoAction): void {
259
+ const handlers = undoRegistry[action.type] as UndoHandlers[typeof action.type]
260
+ handlers.applyReverse(action as any)
261
+ }
262
+
263
+ function applyForward(action: UndoAction): void {
264
+ const handlers = undoRegistry[action.type] as UndoHandlers[typeof action.type]
265
+ handlers.applyForward(action as any)
208
266
  }
209
267
 
210
268
  // ============================================================================
@@ -323,6 +381,34 @@ function applySeoState(
323
381
  })
324
382
  }
325
383
 
384
+ function applyBgImageState(
385
+ cmsId: string,
386
+ element: HTMLElement,
387
+ className: string,
388
+ styleCssText: string,
389
+ bgImageClass: string,
390
+ bgSize: string,
391
+ bgPosition: string,
392
+ bgRepeat: string,
393
+ isDirty: boolean,
394
+ ): void {
395
+ if (element.isConnected) {
396
+ element.className = className
397
+ element.style.cssText = styleCssText
398
+ }
399
+
400
+ signals.updatePendingBgImageChange(cmsId, (c) => ({
401
+ ...c,
402
+ newBgImageClass: bgImageClass,
403
+ newBgSize: bgSize,
404
+ newBgPosition: bgPosition,
405
+ newBgRepeat: bgRepeat,
406
+ isDirty,
407
+ }))
408
+
409
+ saveBgImageEditsToStorage(signals.pendingBgImageChanges.value)
410
+ }
411
+
326
412
  // ============================================================================
327
413
  // Clear History
328
414
  // ============================================================================
@@ -496,6 +496,16 @@ let toastIdCounter = 0
496
496
  // Computed Values - Dirty Tracking
497
497
  // ============================================================================
498
498
 
499
+ /** All change categories that participate in dirty tracking.
500
+ * Adding a new category forces a TypeScript error until it's added to changeRegistry. */
501
+ type ChangeCategory = 'text' | 'image' | 'color' | 'bgImage' | 'seo' | 'attribute'
502
+
503
+ interface ChangeRegistryEntry {
504
+ mapSignal: Signal<Map<string, any>>
505
+ dirtyCount: ReturnType<typeof computed<number>>
506
+ hasDirty: ReturnType<typeof computed<boolean>>
507
+ }
508
+
499
509
  // Use factory for dirty tracking to reduce duplication
500
510
  const _pendingChangesDirty = createDirtyTracking(pendingChanges)
501
511
  const _pendingImageChangesDirty = createDirtyTracking(pendingImageChanges)
@@ -504,6 +514,19 @@ const _pendingBgImageChangesDirty = createDirtyTracking(pendingBgImageChanges)
504
514
  const _pendingSeoChangesDirty = createDirtyTracking(pendingSeoChanges)
505
515
  const _pendingAttributeChangesDirty = createDirtyTracking(pendingAttributeChanges)
506
516
 
517
+ export const changeRegistry: Record<ChangeCategory, ChangeRegistryEntry> = {
518
+ text: { mapSignal: pendingChanges, dirtyCount: _pendingChangesDirty.dirtyCount, hasDirty: _pendingChangesDirty.hasDirty },
519
+ image: { mapSignal: pendingImageChanges, dirtyCount: _pendingImageChangesDirty.dirtyCount, hasDirty: _pendingImageChangesDirty.hasDirty },
520
+ color: { mapSignal: pendingColorChanges, dirtyCount: _pendingColorChangesDirty.dirtyCount, hasDirty: _pendingColorChangesDirty.hasDirty },
521
+ bgImage: { mapSignal: pendingBgImageChanges, dirtyCount: _pendingBgImageChangesDirty.dirtyCount, hasDirty: _pendingBgImageChangesDirty.hasDirty },
522
+ seo: { mapSignal: pendingSeoChanges, dirtyCount: _pendingSeoChangesDirty.dirtyCount, hasDirty: _pendingSeoChangesDirty.hasDirty },
523
+ attribute: {
524
+ mapSignal: pendingAttributeChanges,
525
+ dirtyCount: _pendingAttributeChangesDirty.dirtyCount,
526
+ hasDirty: _pendingAttributeChangesDirty.hasDirty,
527
+ },
528
+ }
529
+
507
530
  export const dirtyChangesCount = _pendingChangesDirty.dirtyCount
508
531
  export const dirtyChanges = _pendingChangesDirty.dirtyItems
509
532
  export const hasDirtyChanges = _pendingChangesDirty.hasDirty
@@ -528,17 +551,11 @@ export const dirtyAttributeChangesCount = _pendingAttributeChangesDirty.dirtyCou
528
551
  export const dirtyAttributeChanges = _pendingAttributeChangesDirty.dirtyItems
529
552
  export const hasDirtyAttributeChanges = _pendingAttributeChangesDirty.hasDirty
530
553
 
531
- export const totalDirtyCount = computed(
532
- () =>
533
- dirtyChangesCount.value + dirtyImageChangesCount.value + dirtyColorChangesCount.value + dirtyBgImageChangesCount.value + dirtySeoChangesCount.value
534
- + dirtyAttributeChangesCount.value,
535
- )
554
+ const _registryEntries = Object.values(changeRegistry)
536
555
 
537
- export const hasAnyDirtyChanges = computed(
538
- () =>
539
- hasDirtyChanges.value || hasDirtyImageChanges.value || hasDirtyColorChanges.value || hasDirtyBgImageChanges.value || hasDirtySeoChanges.value
540
- || hasDirtyAttributeChanges.value,
541
- )
556
+ export const totalDirtyCount = computed(() => _registryEntries.reduce((sum, entry) => sum + entry.dirtyCount.value, 0))
557
+
558
+ export const hasAnyDirtyChanges = computed(() => _registryEntries.some((entry) => entry.hasDirty.value))
542
559
 
543
560
  // Navigation index for cycling through dirty elements
544
561
  export const changeNavigationIndex = signal<number>(0)
@@ -1345,14 +1362,12 @@ export function resetAllState(): void {
1345
1362
  showingOriginal.value = false
1346
1363
  currentEditingId.value = null
1347
1364
  currentComponentId.value = null
1348
- pendingChanges.value = new Map()
1365
+ for (const entry of Object.values(changeRegistry)) {
1366
+ entry.mapSignal.value = new Map()
1367
+ }
1368
+ // Non-dirty-tracked maps still cleared individually
1349
1369
  pendingComponentChanges.value = new Map()
1350
1370
  pendingInserts.value = new Map()
1351
- pendingImageChanges.value = new Map()
1352
- pendingColorChanges.value = new Map()
1353
- pendingBgImageChanges.value = new Map()
1354
- pendingSeoChanges.value = new Map()
1355
- pendingAttributeChanges.value = new Map()
1356
1371
  manifest.value = { entries: {}, components: {}, componentDefinitions: {} }
1357
1372
  aiState.value = createInitialAIState()
1358
1373
  blockEditorState.value = createInitialBlockEditorState()
@@ -170,13 +170,13 @@ export interface SavedBackgroundImageEdits {
170
170
  [cmsId: string]: SavedBackgroundImageEdit
171
171
  }
172
172
 
173
- /** Color change details for updating element color classes */
174
- export interface ColorChangePayload {
175
- /** The color class to replace (e.g., 'bg-blue-500') */
173
+ /** Style change details for updating element classes (colors, text styles, bg images) */
174
+ export interface StyleChangePayload {
175
+ /** The class to replace (e.g., 'bg-blue-500') */
176
176
  oldClass: string
177
- /** The new color class (e.g., 'bg-red-500') */
177
+ /** The new class (e.g., 'bg-red-500') */
178
178
  newClass: string
179
- /** Type of color/style change */
179
+ /** Type of style change */
180
180
  type:
181
181
  | 'bg'
182
182
  | 'text'
@@ -220,8 +220,8 @@ export interface ChangePayload {
220
220
  newSrc: string
221
221
  newAlt?: string
222
222
  }
223
- /** Color class change (for buttons, etc.) */
224
- colorChange?: ColorChangePayload
223
+ /** Style class change (colors, text styles, bg images) */
224
+ styleChange?: StyleChangePayload
225
225
  /** Attribute changes (for links, forms, etc.) */
226
226
  attributeChanges?: AttributeChangePayload[]
227
227
  }
@@ -630,12 +630,32 @@ export interface UndoSeoAction {
630
630
  wasDirty: boolean
631
631
  }
632
632
 
633
+ export interface UndoBgImageAction {
634
+ type: 'bgImage'
635
+ cmsId: string
636
+ element: HTMLElement
637
+ previousClassName: string
638
+ currentClassName: string
639
+ previousStyleCssText: string
640
+ currentStyleCssText: string
641
+ previousBgImageClass: string
642
+ currentBgImageClass: string
643
+ previousBgSize: string
644
+ currentBgSize: string
645
+ previousBgPosition: string
646
+ currentBgPosition: string
647
+ previousBgRepeat: string
648
+ currentBgRepeat: string
649
+ wasDirty: boolean
650
+ }
651
+
633
652
  export type UndoAction =
634
653
  | UndoTextAction
635
654
  | UndoImageAction
636
655
  | UndoColorAction
637
656
  | UndoAttributeAction
638
657
  | UndoSeoAction
658
+ | UndoBgImageAction
639
659
 
640
660
  declare global {
641
661
  interface Window {
@@ -1,53 +1,11 @@
1
1
  import fs from 'node:fs/promises'
2
2
  import path from 'node:path'
3
3
  import { getProjectRoot } from '../config'
4
+ import type { AttributeChangePayload, ChangePayload, SaveBatchRequest } from '../editor/types'
4
5
  import type { ManifestWriter } from '../manifest-writer'
5
6
  import type { CmsManifest, ManifestEntry } from '../types'
6
7
  import { acquireFileLock, escapeReplacement, normalizePagePath, resolveAndValidatePath } from '../utils'
7
8
 
8
- export interface ColorChangePayload {
9
- oldClass: string
10
- newClass: string
11
- type: 'bg' | 'text' | 'border' | 'hoverBg' | 'hoverText' | 'fontWeight' | 'fontStyle' | 'textDecoration' | 'fontSize'
12
- sourcePath?: string
13
- sourceLine?: number
14
- sourceSnippet?: string
15
- }
16
-
17
- export interface ImageChangePayload {
18
- newSrc: string
19
- newAlt: string
20
- }
21
-
22
- export interface AttributeChangePayload {
23
- attributeName: string
24
- oldValue: string | undefined
25
- newValue: string | undefined
26
- sourcePath?: string
27
- sourceLine?: number
28
- sourceSnippet?: string
29
- }
30
-
31
- export interface ChangePayload {
32
- cmsId: string
33
- newValue: string
34
- originalValue: string
35
- sourcePath: string
36
- sourceLine: number
37
- sourceSnippet: string
38
- htmlValue?: string
39
- childCmsIds?: string[]
40
- hasStyledContent?: boolean
41
- colorChange?: ColorChangePayload
42
- imageChange?: ImageChangePayload
43
- attributeChanges?: AttributeChangePayload[]
44
- }
45
-
46
- export interface SaveBatchRequest {
47
- changes: ChangePayload[]
48
- meta: { source: string; url: string }
49
- }
50
-
51
9
  export interface SaveBatchResponse {
52
10
  updated: number
53
11
  errors?: Array<{ cmsId: string; error: string }>
@@ -157,8 +115,8 @@ function applyChanges(
157
115
  continue
158
116
  }
159
117
 
160
- // Handle color class changes
161
- if (change.colorChange) {
118
+ // Handle style class changes (colors, text styles, bg images)
119
+ if (change.styleChange) {
162
120
  const result = applyColorChange(newContent, change)
163
121
  if (result.success) {
164
122
  newContent = result.content
@@ -323,10 +281,10 @@ function applyColorChange(
323
281
  content: string,
324
282
  change: ChangePayload,
325
283
  ): { success: true; content: string } | { success: false; error: string } {
326
- const { oldClass, newClass } = change.colorChange!
327
- // Prefer colorChange's own sourceLine (points to the class attribute)
284
+ const { oldClass, newClass } = change.styleChange!
285
+ // Prefer styleChange's own sourceLine (points to the class attribute)
328
286
  // over the outer change.sourceLine (may point to a data declaration)
329
- const sourceLine = change.colorChange!.sourceLine ?? change.sourceLine
287
+ const sourceLine = change.styleChange!.sourceLine ?? change.sourceLine
330
288
 
331
289
  // When oldClass is empty, we're adding a new color class (not replacing)
332
290
  if (!oldClass) {
@@ -536,7 +494,7 @@ function applyAttributeChanges(
536
494
  return { content: newContent, appliedCount: attrApplied, failedChanges }
537
495
  }
538
496
 
539
- function applyTextChange(
497
+ export function applyTextChange(
540
498
  content: string,
541
499
  change: ChangePayload,
542
500
  manifest: CmsManifest,
@@ -546,7 +504,14 @@ function applyTextChange(
546
504
  let newText = htmlValue ?? newValue
547
505
  newText = resolveCmsPlaceholders(newText, manifest)
548
506
 
549
- if (!sourceSnippet || !originalValue) {
507
+ // Resolve CMS placeholders in originalValue too — when a parent element has
508
+ // child CMS elements, originalValue contains {{cms:cms-N}} placeholders but
509
+ // the sourceSnippet contains the actual HTML for those children.
510
+ const resolvedOriginal = originalValue
511
+ ? resolveCmsPlaceholders(originalValue, manifest)
512
+ : originalValue
513
+
514
+ if (!sourceSnippet || !resolvedOriginal) {
550
515
  if (change.attributeChanges && change.attributeChanges.length > 0) {
551
516
  return { success: true, content }
552
517
  }
@@ -557,19 +522,30 @@ function applyTextChange(
557
522
  return { success: false, error: 'Source snippet not found in file' }
558
523
  }
559
524
 
560
- // Replace originalValue with newText WITHIN the sourceSnippet
561
- const updatedSnippet = sourceSnippet.replace(originalValue, newText)
525
+ // Replace resolvedOriginal with newText WITHIN the sourceSnippet
526
+ const updatedSnippet = sourceSnippet.replace(resolvedOriginal, newText)
562
527
 
563
528
  if (updatedSnippet === sourceSnippet) {
564
- // originalValue wasn't found in snippet - try HTML entity handling
565
- const matchedText = findTextInSnippet(sourceSnippet, originalValue)
529
+ // resolvedOriginal wasn't found in snippet - try HTML entity handling
530
+ const matchedText = findTextInSnippet(sourceSnippet, resolvedOriginal)
566
531
  if (matchedText) {
567
532
  const updatedWithEntity = sourceSnippet.replace(matchedText, newText)
568
533
  return { success: true, content: content.replace(sourceSnippet, updatedWithEntity) }
569
534
  }
535
+ // Try inner content replacement for text spanning inline HTML elements
536
+ // (e.g., <h3>text part 1 <span class="...">text part 2</span></h3>)
537
+ const innerMatch = sourceSnippet.match(/^(\s*<(\w+)\b[^>]*>)([\s\S]*)(<\/\2>\s*)$/)
538
+ if (innerMatch) {
539
+ const [, openTag, , innerContent, closeTag] = innerMatch
540
+ const textOnly = innerContent!.replace(/<[^>]+>/g, '')
541
+ if (textOnly === resolvedOriginal) {
542
+ return { success: true, content: content.replace(sourceSnippet, openTag + newText + closeTag) }
543
+ }
544
+ }
545
+
570
546
  return {
571
547
  success: false,
572
- error: `Original text "${originalValue.substring(0, 50)}..." not found in source snippet`,
548
+ error: `Original text "${resolvedOriginal.substring(0, 50)}..." not found in source snippet`,
573
549
  }
574
550
  }
575
551