Files
petal/web/src/components/Editor/SuggestionHighlight.test.ts
prosolis 6783ce7a51 Suggestions: stop re-nagging resolved edits + fix typographic anchoring
Two fixes for the "accept Petal's change, then it nags about the same
sentence moments later" report:

- replacePending now suppresses any freshly-generated suggestion whose
  original->replacement matches one the user already accepted or
  dismissed for that doc. The model has no memory between passes, so
  without this it re-proposes the identical edit on the next checkpoint.

- findRange anchored suggestions by exact string match, which missed
  whenever the model echoed an `original` with plain ASCII (straight
  quotes, --, ...) while the document held the Typography-converted
  glyphs (curly quotes, em-dash, single-char ellipsis). The miss meant
  no highlight AND a silent no-op on accept, which then fed the re-nag
  above. foldTypography canonicalizes those variants (with a source
  index map for length changes) so matching survives the mismatch.

Covered by a server-side regression test (accept+dismiss then re-check
returns nothing) and frontend unit tests for the fold and anchoring.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
2026-06-26 10:35:57 -07:00

55 lines
2.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect } from 'vitest'
import { foldTypography, findRange } from './SuggestionHighlight'
import { Schema, type Node as PMNode } from '@tiptap/pm/model'
// A minimal doc/paragraph/text schema, enough to exercise findRange's textblock
// walk without pulling in the full editor.
const schema = new Schema({
nodes: {
doc: { content: 'block+' },
paragraph: { group: 'block', content: 'inline*', toDOM: () => ['p', 0] },
text: { group: 'inline' },
},
})
// Build a single-paragraph doc with the given plaintext.
const para = (text: string): PMNode => schema.node('doc', null, [schema.node('paragraph', null, text ? [schema.text(text)] : [])])
describe('foldTypography', () => {
it('folds curly quotes back to straight ASCII', () => {
expect(foldTypography('“Hes here,” she said').folded).toBe('"He\'s here," she said')
})
it('folds dashes and ellipsis, keeping a source-index map across length changes', () => {
const { folded, map } = foldTypography('wait—really...')
expect(folded).toBe('wait—really…')
// The em dash is already one glyph; the `...` collapsed 3 source chars to 1,
// so the trailing sentinel still points just past the last source char.
expect(map[map.length - 1]).toBe('wait—really...'.length)
})
it('treats `--` and `...` typed-but-unconverted as their canonical glyphs', () => {
expect(foldTypography('a--b...c').folded).toBe('a—b…c')
})
})
describe('findRange typographic anchoring', () => {
it('anchors a model echo with straight quotes onto curly-quote document text', () => {
const doc = para('She said “hello” to me')
const range = findRange(doc, '“hello”'.replace(/[“”]/g, '"')) // model echoed "hello"
expect(range).not.toBeNull()
// The matched span must cover the curly-quoted region in the real document.
expect(doc.textBetween(range!.from - 1, range!.to - 1)).toContain('hello')
})
it('anchors an ASCII `--` echo onto an em-dash in the document', () => {
const doc = para('I waited—forever')
const range = findRange(doc, 'waited--forever')
expect(range).not.toBeNull()
})
it('still returns null when the text genuinely is not present', () => {
expect(findRange(para('nothing to see'), 'absent phrase')).toBeNull()
})
})