❇️ dismissal from intervention

This commit is contained in:
2026-02-26 13:18:42 +10:00
parent 1bc0a82e73
commit 70f3b6337f
3 changed files with 49 additions and 2 deletions

View File

@@ -41,6 +41,27 @@ const rawAnnotationsField = StateField.define<TextAnnotation[]>({
// Per-session cache: annotation id → { text, suggestion extracted from blockquote } // Per-session cache: annotation id → { text, suggestion extracted from blockquote }
export const tooltipAnalysisCache = new Map<string, { text: string; suggestion: string | null }>() export const tooltipAnalysisCache = new Map<string, { text: string; suggestion: string | null }>()
// Debounced auto-dismissal when the user edits text inside a highlighted range.
// Exported so FeedbackPanel can cancel a pending timer on manual dismiss.
const EDIT_DISMISS_MS = 1200
const dismissTimers = new Map<string, ReturnType<typeof setTimeout>>()
export function cancelPendingDismiss(id: string): void {
const t = dismissTimers.get(id)
if (t !== undefined) { clearTimeout(t); dismissTimers.delete(id) }
}
function schedulePendingDismiss(id: string): void {
const existing = dismissTimers.get(id)
if (existing !== undefined) clearTimeout(existing)
dismissTimers.set(id, setTimeout(() => {
dismissTimers.delete(id)
const { removeAnnotation } = useEditorStore.getState()
removeAnnotation(id)
tooltipAnalysisCache.delete(id)
}, EDIT_DISMISS_MS))
}
// Extract the first blockquote from a markdown string — used as the applicable rewrite // Extract the first blockquote from a markdown string — used as the applicable rewrite
function extractBlockquote(md: string): string | null { function extractBlockquote(md: string): string | null {
const lines = md.split('\n') const lines = md.split('\n')
@@ -357,11 +378,36 @@ export function MarkdownEditor(): JSX.Element {
tr => tr.isUserEvent('undo') || tr.isUserEvent('redo') tr => tr.isUserEvent('undo') || tr.isUserEvent('redo')
) )
if (hasUndoRedo) { if (hasUndoRedo) {
// User is reverting edits — cancel any pending auto-dismissals so
// restored highlights aren't immediately removed.
dismissTimers.forEach((_, id) => cancelPendingDismiss(id))
const prev = update.startState.field(rawAnnotationsField) const prev = update.startState.field(rawAnnotationsField)
const curr = update.state.field(rawAnnotationsField) const curr = update.state.field(rawAnnotationsField)
if (prev !== curr) { if (prev !== curr) {
useEditorStore.getState().setAnnotations(curr) useEditorStore.getState().setAnnotations(curr)
} }
return
}
// Auto-dismiss annotations whose highlighted text the user edits.
// We check the pre-edit annotation positions (startState) against
// each changed range reported by the transaction.
if (update.docChanged) {
const preAnnotations = update.startState.field(rawAnnotationsField)
if (preAnnotations.length > 0) {
for (const tr of update.transactions) {
if (!tr.docChanged) continue
// Skip non-user events such as file loads (addToHistory=false).
if (tr.annotation(Transaction.addToHistory) === false) continue
tr.changes.iterChangedRanges((fromA, toA) => {
for (const ann of preAnnotations) {
if (fromA < ann.to && toA > ann.from) {
schedulePendingDismiss(ann.id)
}
}
})
}
}
} }
}) })
] ]

View File

@@ -5,6 +5,7 @@ import { useEditorStore } from '../../store/editorStore'
import type { TextAnnotation } from '../../types/editor' import type { TextAnnotation } from '../../types/editor'
import { import {
tooltipAnalysisCache, tooltipAnalysisCache,
cancelPendingDismiss,
analyseAnnotation, analyseAnnotation,
scrollToAnnotation, scrollToAnnotation,
applyAnnotation applyAnnotation
@@ -189,7 +190,7 @@ export function FeedbackPanel(): JSX.Element {
key={ann.id} key={ann.id}
ann={ann} ann={ann}
autoAnalyse={analyseAll} autoAnalyse={analyseAll}
onDismiss={() => { removeAnnotation(ann.id); tooltipAnalysisCache.delete(ann.id) }} onDismiss={() => { cancelPendingDismiss(ann.id); removeAnnotation(ann.id); tooltipAnalysisCache.delete(ann.id) }}
/> />
))} ))}
</div> </div>

File diff suppressed because one or more lines are too long