feat(history): inline edit of transcriptions with edited indicator#4
Conversation
Let a mis-transcribed entry be corrected in place on the History page, then copied and pasted elsewhere — useful when the paste destination is not edit-friendly. - Pencil action opens an auto-growing textarea (Save / Cancel, with Cmd/Ctrl+Enter to save and Esc to cancel); Copy then picks up the edit. - New nullable `edited_at_ms` column doubles as the "was edited" flag and timestamp; an "edited" badge appears in the entry metadata. - The original recognizer output is preserved into `original_text` on the first edit (COALESCE), so "Show original" can always recover it. - `edited_at_ms` is added to pre-existing databases via an idempotent ALTER TABLE guarded by a column-existence check. - Sync Cargo.lock dikt version to 1.26.0 (matches Cargo.toml). Closes #3
There was a problem hiding this comment.
Code Review
This pull request adds support for manually editing transcription history items, updating the SQLite database schema with an edited_at_ms column, introducing a new Tauri command to handle updates, and implementing an inline editing UI in the SolidJS frontend. Feedback focuses on improving robustness: disabling keyboard shortcuts in the editing UI while a save is in progress, adding defensive checks to the textarea ref callback to prevent runtime errors in SolidJS, and using unwrap_or_default() when retrieving the system time to handle potential clock desynchronization issues.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const handleKeyDown = (event: KeyboardEvent) => { | ||
| if (event.key === 'Escape') { | ||
| event.preventDefault(); | ||
| cancelEdit(); | ||
| } else if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) { | ||
| event.preventDefault(); | ||
| void saveEdit(); | ||
| } | ||
| }; |
There was a problem hiding this comment.
The keyboard shortcuts for saving (Ctrl+Enter / ⌘+Enter) and cancelling (Escape) are not disabled while a save operation is in progress (saving() is true). This allows users to trigger multiple concurrent save requests or discard their edits mid-flight, potentially leading to race conditions or data loss if the save fails.
We should guard the keyboard event handler to ignore inputs while saving() is active.
| const handleKeyDown = (event: KeyboardEvent) => { | |
| if (event.key === 'Escape') { | |
| event.preventDefault(); | |
| cancelEdit(); | |
| } else if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) { | |
| event.preventDefault(); | |
| void saveEdit(); | |
| } | |
| }; | |
| const handleKeyDown = (event: KeyboardEvent) => { | |
| if (saving()) return; | |
| if (event.key === 'Escape') { | |
| event.preventDefault(); | |
| cancelEdit(); | |
| } else if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) { | |
| event.preventDefault(); | |
| void saveEdit(); | |
| } | |
| }; |
| ref={(el) => { | ||
| // Solid runs ref callbacks before the node is attached, so defer | ||
| // to a microtask: focus only works and scrollHeight is only | ||
| // measurable once the textarea is in the DOM. | ||
| queueMicrotask(() => { | ||
| autosize(el); | ||
| el.focus(); | ||
| const end = el.value.length; | ||
| el.setSelectionRange(end, end); | ||
| }); | ||
| }} |
There was a problem hiding this comment.
In SolidJS, ref callbacks can occasionally be called with null or undefined during unmounting or reactive updates depending on the context. Additionally, if the component is unmounted or the element is disconnected from the DOM before the microtask runs, calling el.focus() or autosize(el) (which accesses scrollHeight) can cause layout issues or silent failures.
Adding a defensive check to ensure el is defined and connected to the DOM before executing the microtask prevents these potential runtime errors.
| ref={(el) => { | |
| // Solid runs ref callbacks before the node is attached, so defer | |
| // to a microtask: focus only works and scrollHeight is only | |
| // measurable once the textarea is in the DOM. | |
| queueMicrotask(() => { | |
| autosize(el); | |
| el.focus(); | |
| const end = el.value.length; | |
| el.setSelectionRange(end, end); | |
| }); | |
| }} | |
| ref={(el) => { | |
| if (!el) return; | |
| queueMicrotask(() => { | |
| if (!el.isConnected) return; | |
| autosize(el); | |
| el.focus(); | |
| const end = el.value.length; | |
| el.setSelectionRange(end, end); | |
| }); | |
| }} |
| let now_ms = SystemTime::now() | ||
| .duration_since(UNIX_EPOCH) | ||
| .map_err(|e| e.to_string())? | ||
| .as_millis() as i64; |
There was a problem hiding this comment.
If the system clock is misconfigured or set to a time before the UNIX epoch (e.g., during early system boot or due to clock desynchronization), SystemTime::now().duration_since(UNIX_EPOCH) will return an Err. This will cause the entire manual edit operation to fail, potentially causing the user to lose their edited text.
Using unwrap_or_default() provides a safe fallback of 0 (or epoch time) and ensures the edit can always be saved successfully regardless of system clock anomalies.
| let now_ms = SystemTime::now() | |
| .duration_since(UNIX_EPOCH) | |
| .map_err(|e| e.to_string())? | |
| .as_millis() as i64; | |
| let now_ms = SystemTime::now() | |
| .duration_since(UNIX_EPOCH) | |
| .unwrap_or_default() | |
| .as_millis() as i64; |
|
🎉 This PR is included in version 1.28.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
Implements #3: edit a transcription directly on the History page, then copy the corrected text to paste elsewhere — useful when the paste destination isn't edit-friendly.
What's new
UI (
HistoryPage.tsx)Backend (
transcription_history.rs,commands.rs,db.rs,main.rs)update_transcription_history_item(id, text)command stamps a new nullableedited_at_mscolumn.original_texton the first edit (COALESCE), so "Show original" can always recover it; later edits don't overwrite it.edited_at_msis added to existing databases via an idempotentALTER TABLEguarded by a column-existence check (the schema usesCREATE TABLE IF NOT EXISTS, which can't alter existing tables).This all lives in the settings window (normal, non-transparent), so the WebView2 transparent-window reflow issues from the pill overlay don't apply to the inline textarea.
Verification
npx tsc --noEmit— cleancargo check— cleanedited_at_ms(not overwritten by re-edits)ensure_history_columnsadds the column to a legacy table and is idempotentCloses #3