# Supervertaler Workbench - Changelog

All notable changes to Supervertaler Workbench are documented in this file.

**Current Version:** v1.10.120 (May 21, 2026)


## v1.10.120 – May 21, 2026

### Added

- **QuickTrans now docks as an always-on panel, beside TermLens.** A trimmed version of the QuickTrans popup that lives as a tab next to TermLens, in two places: under the translation grid, and in the Match Panel (top-right, above the TM boxes). It auto-fetches the cheap/free machine-translation engines (Google, MyMemory, Microsoft, …) for the current segment, while AI providers (Claude, OpenAI, Gemini, …) appear as on-demand **Fetch** buttons so paid calls only happen when you ask. Click any result to drop it into the target cell. Each panel only fetches while its tab is visible, so there's no needless work or cost.
  - You can mix and match: TermLens under the grid with QuickTrans on the right, the reverse, or the same panel in both spots. Choices persist across restarts.
  - The View menu's old TermLens placement items are consolidated into a single **View ▸ 📑 TermLens / QuickTrans panel ▸ Hide / Show above grid / Show below grid** submenu; the TermLens vs QuickTrans choice is made via the dock's own tab bar.

### Fixed

- **Ctrl+Alt+Q now translates the current segment when used inside Workbench.** Previously the global QuickTrans hotkey always did a clipboard copy first, so pressing it in a target cell with nothing selected showed stale clipboard text (or nothing). It now detects that Workbench is the active window and translates the current selection — or the whole source segment if nothing is selected — exactly like the right-click QuickTrans. The clipboard-capture flow is unchanged when the hotkey is used from other applications.


## v1.10.119 – May 21, 2026

### Fixed

- **Custom (OpenAI-compatible) endpoints now always use the active profile's model.** When you kept more than one custom-endpoint profile – or switched the active one without re-saving AI Settings – the status bar and the actual API request fell back to a stale legacy `custom_openai_model` value, while the QuickTrans panel showed the correct one. The result was a confusing mismatch (e.g. the panel said `Pro/deepseek-ai/DeepSeek-V3` but requests went out as `deepseekv4`), and the endpoint would reject the call. All surfaces – status-bar indicator, QuickTrans panel, and every translation request – now resolve the model from the active profile, so they always agree.


## v1.10.118 – May 21, 2026

### Fixed

- **Double-tap Shift now actually opens the context menu.** Tapping Shift twice quickly (within 350 ms, while the Workbench window is active) is meant to open the context menu of the focused widget, but it never worked: the handler sent a synthetic `Shift+F10` key press, which Qt only translates into a context-menu event at the windowing layer – so it never reached the cell editor or the grid. It now sends a real context-menu event directly, anchored at the text cursor.


## v1.10.117 – May 20, 2026

### Fixed

- **Edit Termbase Entry dialog — removed stray label under target synonyms.** A leftover "Alternative translations. First item = preferred term." instruction line under the target ("… synonyms:") column was making the two synonym columns lopsided. It has been removed so the source and target sections line up evenly.


## v1.10.116 – May 20, 2026

### Changed

- **Edit Termbase Entry dialog — clearer synonym handling.** The source and target synonym sections are now always visible side-by-side (no more collapse triangles to expand), matching the Supervertaler for Trados term editor. The dialog opens fully expanded so the whole form is visible without scrolling, the wasted vertical space above the "… synonyms:" labels is gone (the surrounding group boxes were dropped), and the synonym list boxes are now white like the other fields.


## v1.10.115 – May 20, 2026

### Changed

- **Status column now sits between Source and Target by default.** The "Show Status column between Source and Target" option (Settings ▸ View) is on by default; uncheck it to move the Status column back to the far right. (Existing projects keep whatever you had set.) The option's label/tooltip were reworded to make the two positions clear.


## v1.10.114 – May 20, 2026

### Fixed

- **Dark theme: unreadable text on highlighted backgrounds.** The selected segment's source/target text now renders black on the pale-blue selection tint (it was light-on-light in dark themes), and anchored-comment text now renders black on its amber highlight. Both highlights are always light, so black is readable in every theme.


## v1.10.113 – May 20, 2026

### Added

- **Reach comments straight from the grid.** Hovering an anchored comment's yellow highlight in a source or target cell now shows the comment as a tooltip. Right-clicking it adds a **💬 Open comment** item that switches to the Comments tab and flashes the relevant entry. Segment-level comments (no highlighted range) are reachable too: right-clicking anywhere in a commented segment's cell — or the segment's Status cell — offers **💬 Open comment(s)**, and the Status cell's tooltip now lists the segment's comments.


## v1.10.112 – May 20, 2026

### Removed

- **Dead `_last_selected_termbase_ids` storage.** The Add-to-Termbase dialog stored the last-used termbase IDs purely for the old Alt+Left quick-add, which was removed in v1.10.111. With no consumer left, the assignment (and its log line) are gone.


## v1.10.111 – May 20, 2026

### Removed

- **"Quick Add to Termbase" (Alt+Left).** Removed the redundant last-used-termbase quick-add. The project termbase (Alt+Up) and background termbase (Alt+Down) quick-adds cover the useful cases; Ctrl+Alt+T still opens the full add dialog. Gone from the shortcut list, the editor right-click menu, and the keyboard handlers, along with the now-dead backing code.


## v1.10.110 – May 20, 2026

### Changed

- **Keyboard Shortcuts list hides unusable entries.** Settings ▸ Keyboard Shortcuts no longer shows actions you can't actually invoke or rebind: the two legacy "Cycle to Next/Previous Match (from grid)" slots (never wired to a key) and the redundant duplicate "Clear filter" row (same Ctrl+Shift+F as "Filter on selected text", which is the real one). Added a `hidden` flag in the shortcut registry; hidden entries are also excluded from the exported cheatsheet and conflict checks.


## v1.10.109 – May 20, 2026

### Removed

- **Dead "Switch to Grid/List/Document View" shortcuts.** These three entries (Ctrl+1/2/3) showed in Settings ▸ Keyboard Shortcuts but did nothing — Workbench has a single grid view and the backing methods no longer exist. Removed from the shortcut registry. (The memoQ-style "insert match #1…#9" slots are unaffected and remain available to bind.)

### Fixed

- **Stale "F9" voice-dictation fallback.** The in-code fallback default for the dictation shortcut now matches the real default (Ctrl+Shift+Space) instead of the long-dead F9.


## v1.10.108 – May 20, 2026

### Fixed

- **Dictate button showed the wrong shortcut.** The grid's voice button was hard-labelled "🎤 Dictate (F9)", but the dictation key has long defaulted to Ctrl+Shift+Space (and is configurable). The button now reads the live `voice_dictate` binding — e.g. "🎤 Dictate (Ctrl+Shift+Space)" — and updates if you rebind it. Tooltips no longer reference F9.


## v1.10.107 – May 20, 2026

### Changed

- **Grid shows all segments by default.** The editor grid now opens with every segment on one page instead of 50 per page. Projects with more than 2000 segments automatically fall back to 500 per page on open to keep the initial layout snappy; you can still switch to any size (or "All") via the Per-page selector. Added a "500" option to that selector.

### Fixed

- **Shift+PgUp / Shift+PgDn range selection under "All".** These extended the selection to the very top/bottom of the document when the grid was in "All" mode (the page-worth guard only checked for the old string value, not the current sentinel). They now select a screenful as intended.


## v1.10.106 – May 20, 2026

### Added

- **"Similar Term Found" merge prompt.** When you add a term whose source already exists with a different target (or whose target exists with a different source), a dialog now offers to fold the new term into the existing entry as a synonym — **Add as Synonym**, **Add & Edit…** (merge then open the entry editor), **Keep Both**, or **Cancel** — instead of silently creating a near-duplicate. Works on all term-add paths (Ctrl+Alt+T dialog, Alt+Left quick-add, Alt+Up project, Alt+Down background) and only interrupts when there is an actual conflict, so quick-adds stay silent and fast otherwise. Brings the Workbench to parity with the Supervertaler for Trados plugin against the shared termbase schema. Exact duplicates still behave as before.

### Fixed

- **Keyboard Shortcuts list.** The "Add Comment from Selection" (Ctrl+M) shortcut is now listed and editable in Settings ▸ Keyboard Shortcuts — it was bound in code but missing from the list. Removed the phantom "TM Manager (Launch in tab)" entry, which squatted on Ctrl+M in the list but was never wired to anything. ("TM Manager (Separate window)" / Ctrl+Shift+M is unchanged.)


## v1.10.105 – May 20, 2026

### Changed

- **Session Log moved out of the right-panel tabs.** The "📋 Session Log" tab is gone from the panel beside the grid. The log now opens in its own movable window via a new **📋 Log** button on the segment editor toolbar (beside Always-On) or **Tools ▸ Log Window**. The detached window shows the full backlog and updates live, and the button works even before Settings has been opened.

### Removed

- **Ctrl+N (Focus Segment Note Tab) shortcut.** Removed the binding and its entry in the Keyboard Shortcuts list. Commenting on segments is unaffected — Ctrl+M still opens the comment dialog (anchored to the selected source/target text, or segment-level when nothing is selected).


## v1.10.104 – May 20, 2026

### Changed

- **Pre-translated status icon** changed from the robot (🤖) to a lightning bolt (⚡), so it is no longer indistinguishable from MT (which keeps the robot). The lightning was already the status's internal match symbol, so the change is consistent.


## v1.10.103 – May 20, 2026

### Added

- **PM / CM status badges.** Perfect Match and Context Match segments now show small coloured text badges (PM in blue-grey, CM in green) in the Status column, mirroring how Trados Studio labels them, instead of the ⭐ and 💎 emoji. The redundant 101%/102% figure is dropped because the badge already conveys it.
- **QuickLauncher → Copy to clipboard.** The grid right-click QuickLauncher menu (and the Ctrl+Q popup) gains a third action per prompt: Copy to clipboard. It expands the selected prompt against the current segment (source, target, languages) and copies the result, ready to paste into an online LLM such as Claude.ai or ChatGPT. No API call is made and no API key is needed.
- **Live TermLens placement.** Two View-menu items — Show TermLens above grid / Show TermLens below grid — dock the under-grid TermLens panel above or below the grid immediately, with no project reopen. Clicking the active item hides the panel again. Replaces the old "tabs above grid" settings checkbox, which only took effect after a restart.

### Fixed

- **Ctrl+Q opened the wrong panel.** Ctrl+Q (and Alt+K) were jumping to the SuperLookup tab — a leftover from the Sidekick retirement — despite being labelled "Open QuickLauncher". They now pop up the QuickLauncher prompt menu for the current segment.

### Changed

- Type-column paragraph marks (¶) render a point smaller, so the heavier glyph sits better alongside the heading and list labels.


## v1.10.102 – May 18, 2026

### Release (GitHub release rollup of v1.10.83 → v1.10.102)

Published GitHub release tagging the v1.10.83 → v1.10.102 series — twenty patch versions shipped over a single marathon day rolling up the TermLens-mirror-popup parity work, the new Ctrl+Shift+B Term Picker dialog, all the popup ergonomics fixes (ring rendering, height shrink-to-fit, keyboard focus, Ctrl-tap-to-close), the always-on cell scrollbar fix, the synonym-aware Term Picker, the contextual ? help buttons, and the help-site updates (new TermLens popup + Term Picker pages, GitBook-style click-to-zoom, stronger sidebar indent guides). No new functional changes vs. v1.10.101 — this version exists purely to mark the release boundary.


## v1.10.101 – May 18, 2026

### Fixed (TermLens popup: stray ? from docked panel; bottom row clipped on long segments)

**1. Stray ? button inside the popup.** v1.10.100 removed the popup-level help button, but the popup embeds a `TermLensWidget` instance in popup mode, and `set_popup_mode(True)` was only hiding the refresh + font-zoom buttons in the docked widget's header — not the help button I added in v1.10.99. So the docked panel's `?` kept showing up inside the popup wrapper. Reported by a user: "the lens pop-up still has the question mark". Added `_btn_help` to the list of buttons `set_popup_mode` hides.

**2. Bottom row of chips clipped on long segments.** `_fit_height_to_content` was running on a single `QTimer.singleShot(0, …)` tick, but on multi-row segments the FlowLayout's `heightForWidth` returned a slightly-under-estimated height because the chips' corner-indicator overlays + final auto-sized geometry hadn't settled by then. Result: the popup was sized to fit roughly N–1 rows out of N, with the last row clipped at the bottom.

Three reinforcing fixes:

- Call `QApplication.processEvents()` at the top of `_fit_height_to_content` to flush any pending layout passes before measuring.
- Schedule a second fit pass 80 ms after the first, as a safety net for any remaining chip-sizing drift.
- Bumped the bottom comfort buffer from 4 px to 12 px so any small under-counts in heightForWidth don't clip the last row.


## v1.10.100 – May 18, 2026

### Removed (TermLens popup ? button — wrong affordance for a transient surface)

User feedback after testing v1.10.99: the `?` button in the TermLens popup's hint row was disturbing the popup's compact-floating-card feel and was a poor fit semantically. The popup auto-closes on mouse-move > 4 px, on focus loss, and on any non-modifier key press, by design — so any clickable affordance inside it is fundamentally awkward. Moving the mouse toward the button closes the popup before the click registers.

Reverted to the v1.10.98 hint-only row, with one addition: the hint string now ends in `F1 help`, so the help affordance is still discoverable. F1 itself continues to work via the application-level `_HelpEventFilter` plus the `set_topic(self, Topics.GLOSSARY_TERMLENS_POPUP)` call I added in v1.10.99 — that part stays. Pressing F1 anywhere inside the popup opens `workbench/termbases/termlens-popup/` in the browser; the focus shift to the browser then closes the popup, which is exactly the desired UX (read the docs without an extra dismiss step).

The `?` buttons on the docked TermLens panel and on the Term Picker dialog stay as-is — both are persistent surfaces where a clickable button makes sense.


## v1.10.99 – May 18, 2026

### Added (contextual ? help buttons on the TermLens popup, Term Picker dialog, and docked TermLens panel)

User request: "Could we add contextual question marks that link to the relevant help pages on the Term Picker and TermLens pop-up, as well as on the TermLens window in Supervertaler Workbench?"

Three small `?` buttons added, each routed through the existing `modules/help_system.open_help()` helper so the URL resolution stays consistent with F1 and every other in-app ? affordance:

- **Docked TermLens panel** — `?` button at the far right of the header row, alongside the existing 🔄 refresh button and A-/A+ font-zoomers. Opens `workbench/termbases/termlens/`.
- **TermLens popup (Ctrl tap)** — `?` button at the right edge of the keyboard-hint row at the bottom, balanced by a same-width spacer on the left so the centred hint label stays visually centred. Opens `workbench/termbases/termlens-popup/`.
- **Term Picker dialog (Ctrl+Shift+B)** — `?` button between the hint label and the Insert / Cancel buttons in the dialog footer. Opens `workbench/termbases/term-picker/`.

Two new `Topics` constants — `GLOSSARY_TERMLENS_POPUP` and `GLOSSARY_TERM_PICKER` — match the two new help pages added to the Supervertaler-Help repo earlier today. All three buttons also `set_topic` on their containing widget so F1 anywhere inside the popup / dialog / panel falls through to the same help page via the global `_HelpEventFilter`. The popup's button suppresses focus-close around the browser launch so the popup doesn't tear itself down when focus shifts to the new browser window.


## v1.10.98 – May 18, 2026

### Fixed (Ctrl tap auto-inserted an NT instead of opening the popup after adding a synonym)

Reported by a user: "After adding a synonym, when I press Ctrl, TermLens popup is no longer shown, but a term is inserted at the cursor."

Two compounding bugs, both fixed:

**1. Cache staleness after termbase edits.** Adding a synonym (and other termbase write operations) leaves the per-segment `termbase_cache` entry stale or empty for the segment the user is on, even though the in-memory `termbase_index` is already up to date. `show_term_insert_popup` and `show_term_picker_dialog` now fall back to a direct `_search_termbase_in_memory(segment.source)` call when the cache returns empty — the popup never opens with a misleadingly empty chip set again, regardless of whether the cache repopulation has caught up yet.

**2. "Smart single-NT shortcut" removed.** The pre-v1.10.87 numbered-list popup had a behaviour where if a segment had no termbase matches and exactly one NT match, Ctrl-tap would skip the popup and auto-insert the NT text directly. That made sense in the old model (the popup was just a chooser), but the v1.10.87 TermLens-mirror popup IS the affordance — the user wants to see what's on offer before committing. Combined with bug #1, the auto-insert was firing on segments where the user *did* have termbase matches but the cache was momentarily empty, inserting an unrelated NT at the cursor and looking like the popup had silently spawned a term insertion. Removed the auto-insert path entirely; always show the popup now, even on single-item segments.


## v1.10.97 – May 18, 2026

### Removed (Ctrl+Shift tap event filter — Ctrl+Shift+B remains the canonical Term Picker shortcut)

User feedback after three rounds of trying to make the Ctrl+Shift tap chord work reliably: "can we remove the Ctrl+Shift shortcut and just use Ctrl+Shift+B?" Pulled `_CtrlShiftTapEventFilter` and all its diagnostic plumbing in one go.

Background: v1.10.90 introduced the modifier-only chord as a companion to the lone-Ctrl-tap, hoping to mirror the ergonomic feel of how the TermLens-popup opens. It turned out to be a constant source of trouble — AltGr on Dutch / German / other layouts emits Ctrl+Alt key events, which poisoned the chord state via the "other modifier pressed" path; the session log filled up with `[Ctrl+Shift tap]` chatter on every chord attempt; and the per-event logging itself was slowing down the lone-Ctrl tap enough that *that* started feeling broken too. Plus there was no obvious fix that wouldn't either keep firing falsely or fail to fire when intended.

Removed:
- The `_CtrlShiftTapEventFilter` class entirely (~200 lines)
- The installation block in `setup_global_shortcuts`
- The `[Term Picker]` diagnostic logs in `show_term_picker_dialog` (added in v1.10.92 to triage the chord)

Kept:
- The `Ctrl+Shift+B` `QShortcut` registered via `create_shortcut("term_picker", "Ctrl+Shift+B", …)` — that was always the canonical, configurable Term Picker trigger
- The lone Ctrl tap (via `_LoneCtrlEventFilter`) for the TermLens popup, plus its second-Ctrl-tap-to-close behaviour
- The `Term Picker` dialog itself (modules/term_picker_dialog.py) — unchanged

Net effect: session log no longer floods on every modifier chord, the lone-Ctrl tap should feel responsive again, and there's one canonical Term Picker shortcut (`Ctrl+Shift+B`, fully remappable via the in-app shortcut manager).


## v1.10.96 – May 18, 2026

### Fixed (always-on scrollbar on source/target cells; Ctrl+Shift tap chord state poisoned by unrelated typing)

**Two-in-one fix.**

**1. Spurious scrollbar in source/target cells.** A user on a laptop screen reported: "no matter what I do with the resizing of the text size, I always have a scroll bar in the target box." Cause: the `EditableGridTextEditor` (target cell) and `ReadOnlyGridTextEditor` (source cell) used `Qt.ScrollBarPolicy.ScrollBarAsNeeded`. QTextEdit's AsNeeded heuristic compares document height (which includes sub-pixel line metrics + the `DocumentMargin` we set to 0 + any zero-width-space padding the invisible-character machinery adds) against viewport height — a 1-pixel off-by-one between those two on certain font rendering paths is enough to surface the scrollbar even when the text visually fits.

Switched both cell editors to `ScrollBarAlwaysOff`. The row auto-resize machinery (`resizeRowToContents` called after every confirm and on grid load) keeps each cell tall enough for its content, so AlwaysOff is safe — content never gets clipped in practice. The actively-being-edited delegate editor (the QTextEdit that pops up when you double-click a cell) keeps AsNeeded so very long in-progress edits still get a usable scrollbar.

**2. Ctrl+Shift tap chord poisoned by unrelated typing.** v1.10.93's diagnostic logs caught what the previous two fixes missed: `_other_key=True` was already set at the moment of the Shift press in a clean Ctrl+Shift chord. Root cause in `_CtrlShiftTapEventFilter`: the non-modifier KeyPress branch was setting `_other_key=True` **unconditionally** on every keystroke, even when no chord was in progress. So pressing a normal letter (or arrow key, or function key) between two Ctrl+Shift tap attempts left `_other_key=True` carrying over — because `_reset()` only fires when both modifiers come up, and typing a normal letter doesn't touch the modifier state at all.

Fix: gate the `_other_key=True` set on `if self._ctrl_held or self._shift_held` — same condition that already gated the diagnostic log line. Idle keystrokes outside an active chord-building phase no longer affect future chord detection.


## v1.10.95 – May 18, 2026

### Changed (TermLens popup height now shrinks to fit the chips)

User feedback: "Sometimes the TermLens pop-up window is bigger than it needs to be. Can we make it so that it's never much bigger than the amount of text it needs to show?" The v1.10.89 fixed-height fallback used a placeholder of 220–380 px regardless of segment length, leaving roughly a third of the popup as empty space below short segments.

Replaced with `_fit_height_to_content`, which runs on the tick after construction (so Qt has finished its first layout pass) and asks the inner `FlowLayout` for its `heightForWidth(content_width)` — the authoritative measurement of the wrapped chip area. The popup height then = chip area height + outer chrome (FlowLayout margins, scroll-area frame, TermLensWidget outer layout, card padding, hint label, popup border) + a small 4-px comfort buffer so a tight fit doesn't trigger the vertical scrollbar.

Width is unchanged from v1.10.89 (fixed at 60 % of screen, capped 560–980 px) — width-shrinking would force the chips to wrap into narrower / taller layouts, defeating the goal. Height is clamped at 65 % of screen for very long segments; the scroll area handles anything beyond that. Re-anchored to the cursor after resize so the bottom edge doesn't snap off-screen on short segments.

v1.10.87's broken `_shrink_to_content` read `inner.sizeHint()` directly, which for a FlowLayout returns its `minimumSize()` (one chip wide) — that's why the popup collapsed to a one-column-tall column back then. The new path goes through `heightForWidth` instead, which actually wraps the chips at the popup's real width and reports the resulting total height.


## v1.10.94 – May 18, 2026

### Changed (TermLens popup current-chip ring: thinner stroke, 1 px instead of 2 px)

User feedback after v1.10.93: "Reduce the thickness of the blue outline somewhat." The 2-px ring on a ~25-px-tall chip looked heavy next to the chip's own 1-px native border. Switched both the term-chip ring (`_ChipContainer.paintEvent`) and the NT-chip ring (`_NTChipLabel.paintEvent`) from a 2-px stroke to a 1-px stroke, with a 0.5-px half-pixel inset so the antialiased line lands crisply on a pixel boundary rather than straddling two. Reads as a delicate accent against the chip background instead of an outline marker.


## v1.10.93 – May 18, 2026

### Fixed (Ctrl+Shift tap now actually fires — ShortcutOverride was poisoning the chord state)

v1.10.92's diagnostic logs revealed the real bug behind "Ctrl+Shift does nothing": every chord attempt finished with `_other_key=True` even when the user pressed nothing but Ctrl and Shift. The cause was the `ShortcutOverride` branch in `_CtrlShiftTapEventFilter` — Qt fires `ShortcutOverride` speculatively whenever a key press *might* match a registered shortcut, including the Shift key press inside a Ctrl+Shift chord (because Qt is checking whether `Ctrl+Shift+<anything>` is bound to anything yet). My filter was treating that speculative override as "the user pressed some other key", which poisoned the chord state before it could complete.

Fix: stop setting `_other_key=True` on `ShortcutOverride`. Keep the lighter `_armed=False` guard if I ever want to add it back, but the `KeyPress` branch already handles the "real other key was pressed" case via its `else` clause on non-modifier keys, so the `ShortcutOverride` interception was redundant *and* harmful.

Added per-event diagnostic logs inside the chord-detection KeyPress branch (`KeyPress Ctrl`, `KeyPress Shift`, `KeyPress other key=…`) so that if the chord still misbehaves in some edge case, the next session log will pinpoint exactly which event broke it.


## v1.10.92 – May 18, 2026

### Fixed (current-chip ring now actually wraps the chip; Ctrl+Shift tap diagnostics)

**Ring corners no longer look square — they're now genuinely rounded around the chip.** v1.10.91 moved the ring painting "inside the chip" but kept it in `TermBlock.paintEvent`. The problem: Qt paints children *after* their parent's `paintEvent`, so the chip's solid stylesheet background fill was drawn ON TOP of the ring, covering it everywhere except where the chip's rounded-corner cut-outs let the ring poke through. That's the "square corners" effect — really just the ring fragments outside the chip's rounded shape, visible because the chip's corners are transparent at the very edges. Reported by a user: "corners are not outlined in blue".

Fix: move the ring-painting into `_ChipContainer.paintEvent` itself, layered on top of the QSS background via `super().paintEvent()` first. Same treatment for NT chips: promoted `nt_label` from a bare QLabel to a new `_NTChipLabel(QLabel)` subclass that does the same thing. `TermBlock.set_current` / `NTBlock.set_current` just forward to the chip-level `set_current` now. Ring corners use 3 px radius for term chips (matching the chip's `border-radius: 3px`) and 2 px for NT pills (matching `border-radius: 2px`), so the curve follows the chip's outline cleanly. Colour bumped to Material-blue 900 (`#0D47A1`) and deep amber (`#BF360C`) respectively for stronger contrast.

**Ctrl+Shift tap diagnostics.** The user reported "Ctrl+Shift does nothing" even though `Ctrl+Shift+B` works. The state-machine analysis showed the chord *should* fire — so there's something subtle happening in the wild that the unit-test path didn't catch. Added structured logging to `_CtrlShiftTapEventFilter` and to `show_term_picker_dialog`: whenever both modifiers are released we log the armed-state + other-key-state, and whenever the dialog is invoked we log how many matches we found. If the chord is still silent after this version, the session log will reveal whether the filter is failing to detect it (no `[Ctrl+Shift tap]` line at all), detecting it but bailing on a third modifier (`aborted: other modifier still held`), firing but finding no segment selected (`invalid current_row`), or firing successfully but the dialog flashed past too fast to notice.

In all cases the `Ctrl+Shift+B` QShortcut continues to work as the canonical Term Picker trigger.


## v1.10.91 – May 18, 2026

### Fixed (TermLens popup current-chip ring no longer half-clipped)

The blue "currently-highlighted chip" ring that v1.10.87 added to the TermLens popup was being drawn 2 px *outside* the chip's geometry (`adjusted(-2, -2, 2, 2)`), then painter-clipped against the TermBlock's tight 1-px contents margins — the right and bottom strokes vanished, leaving a "half-ring" that wrapped the top-left of the chip and just disappeared elsewhere. Reported by a user with the "apparatuses" chip selected: "the blue selection indicator … doesn't go all the way around the term."

Fix: draw the ring *inside* the chip's geometry with a 0.5-px half-pixel inset for crisp antialiasing, instead of outside. The 2-px stroke now sits flush along the chip's own rounded border with no risk of being clipped by the parent widget's bounds. Colour bumped from Material-blue 700 to 800 for slightly stronger contrast against the light-blue chip backgrounds; NT-chip ring bumped from amber 700 to deep amber (`#E65100`) for the same reason against the pastel-yellow NT pill. The ring now reads as a clean, fully-enclosing rectangle on every chip in the popup regardless of where the chip sits in the FlowLayout.


## v1.10.90 – May 18, 2026

### Added (TermLens-popup ergonomics: Esc closes reliably, second Ctrl tap toggles closed, cycle skips no-hit chips, Ctrl+Shift tap opens Term Picker)

Four follow-up tweaks from the v1.10.89 test session — all small, all UX wins:

**1. Esc reliably closes the popup.** v1.10.89 fixed focus enough that arrow-key cycling worked, but in some configurations Esc still wasn't reaching the popup's `keyPressEvent` — apparently a focused descendant inside the inner widget (e.g. a chip's source QLabel) was eating the Esc and not propagating. Added a `WidgetWithChildrenShortcut`-scoped `QShortcut` for Esc on the popup itself, which catches the key regardless of which descendant is focused. The existing `keyPressEvent` Esc handler stays as a backup; both are idempotent (close is a no-op after the first call).

**2. Second Ctrl tap toggles the popup closed.** A user request: "Can you also make pressing Control again close it?" The first Ctrl tap opens the popup via the app-level `_LoneCtrlEventFilter`; that filter is dormant while the popup is the active window, so we added a local in-popup detection — `keyPressEvent` arms `_ctrl_tap_armed` on Ctrl-down, any non-modifier key disarms it, and `keyReleaseEvent` closes the popup if Ctrl was released cleanly. Net effect: the Ctrl-tap gesture is fully symmetric — same key opens and closes. `show_term_insert_popup` also gained a toggle guard so a Ctrl-tap from outside the popup (main window active) toggles it closed via the existing path, not just opens a duplicate.

**3. Cycling now skips chips without matches.** The docked TermLens panel renders a `TermBlock` for *every* word token in the segment, even ones with no termbase hit (they appear as bare source words underneath the segment). The popup's chip cycle (Right/Left/Tab) was visiting these no-hit blocks, leaving the user staring at a highlighted empty source word that did nothing on Enter. `get_term_blocks` gained an `only_with_matches=True` parameter that filters them out; all five call sites in `modules/termlens_popup.py` use it. NT blocks are always included since every NTBlock represents a real non-translatable match by construction.

**4. Ctrl+Shift tap opens the Term Picker.** Another user request: "could we change it to quickly pressing Ctrl and Shift?" Added `_CtrlShiftTapEventFilter` modelled on `_LoneCtrlEventFilter` — fires when both modifiers are pressed cleanly with no other key in between, then both released. The conventional `Ctrl+Shift+B` QShortcut is kept in parallel so the in-app shortcut manager still has something to remap; the chord tap is purely additive. Edge cases: pressing `Ctrl+Shift+B` correctly does NOT trigger the picker chord (the `B` press disarms), and `Ctrl+Shift+Q` / `Ctrl+Shift+P` / other in-app shortcuts continue to work unchanged.

Implementation notes: both event filters share the same idiom — track `_armed` set on clean modifier sequences, cleared on any other key / wheel / shortcut override. Both filters check `isActiveWindow()` on the main window so they go quiet when the popup itself is active. The popup's own in-class Ctrl-tap detection handles the "close while popup is active" case so the symmetry works regardless of which window has focus.


## v1.10.89 – May 18, 2026

### Fixed (TermLens-mirror popup: keyboard navigation, Esc-to-close, sane width — and the missing Term Picker shortcut)

Four issues caught the moment v1.10.87 went on-screen. All four come from foot-guns specific to frameless top-level Qt popups, which I underestimated on the first pass:

**1. Arrow keys went to the segment grid, not the popup.** The v1.10.87 popup used `Qt.WindowType.Tool | FramelessWindowHint`. Tool windows on Windows don't auto-activate, so when the popup appeared, keyboard focus stayed on whichever widget had it before (almost always the segment-grid table). Result: Right / Left / Down / Up navigated the grid rows underneath the popup instead of cycling chips inside it. Fix: use `Qt.WindowType.Dialog | FramelessWindowHint` (Dialog activates on show) plus explicit `raise_() + activateWindow() + setFocus(PopupFocusReason)` in `showEvent`. Together those three calls guarantee the popup wins focus on Windows, including from inside a still-active QTableWidget.

**2. Esc didn't close the popup.** Same root cause as #1 — Esc went to whatever had focus (the grid), not to our `keyPressEvent`. Same fix: once focus is on the popup, Esc lands in our handler and closes cleanly.

**3. Popup rendered too narrow and too tall.** v1.10.87 had a `_shrink_to_content` pass that read `self._inner.sizeHint()` shortly after construction. For a `FlowLayout`-based widget that's near-zero — FlowLayout doesn't report a meaningful preferred size — so we shrunk the popup to ≈ 350 px wide × 600 px tall, with chips stacking one or two per row. Replaced the shrink with a fixed default size scaled to the screen the cursor is on (`560–980 px wide × 220–380 px tall`), letting the FlowLayout wrap chips to 4–6 per row, with the inner scroll area handling overflow on the rare ultra-long segment.

**4. Ctrl+Shift+P did nothing.** The Scratchpad menu action already binds `Ctrl+Shift+P` (set at QAction level, not via the shortcut manager), and our new QShortcut on the same key combo lost the conflict silently. Switched the Term Picker default to `Ctrl+Shift+B` (termBase picker — clear mnemonic, previously unbound). Users who customised the default still have whatever they set via the shortcut manager; only the out-of-the-box default changed.

**5. focusOutEvent was too aggressive.** While debugging, found that the v1.10.87 `focusOutEvent` close fired every time focus moved *inside* the popup (e.g. between the chip area and the hint label as a side effect of layout passes), not just when the popup itself lost top-level focus. Replaced with a `changeEvent`-based check on `QEvent.Type.ActivationChange` — that fires only when the popup window becomes inactive (focus moved to a different top-level window). Combined with the existing mouse-move auto-close, the popup now feels properly disposable without false closures.

Net effect: pressing the popup shortcut now actually puts focus on the popup, arrow keys cycle chips, 1–9 inserts, E opens the editor, I toggles the metadata sticky popup, Esc closes. `Ctrl+Shift+B` opens the Term Picker. The popup renders at a comfortable 600-980 px wide so chips wrap horizontally like the docked panel.


## v1.10.88 – May 18, 2026

### Fixed (Term Picker now unpacks each entry's target_synonyms into expandable sub-rows)

The v1.10.87 Term Picker (Ctrl+Shift+P) only treated *multiple termbase entries that share the same source word* as expandable synonyms. Actual `target_synonyms` recorded on a single entry — the canonical "this entry has alternative target spellings" case — collapsed into the bare primary row with no ▸ affordance and no way to see them, even though the docked TermLens chip showed a ≡ corner indicator promising they were there.

`modules/term_picker_dialog.build_picker_matches` now also walks each match's `target_synonyms` list and adds them as synonym sub-rows under the same parent. When an entry both has its own synonyms AND shares its source word with another termbase's entry, all sources collapse under one parent (own-synonyms first, then the second entry's primary + its own synonyms) in the original first-seen order.

Smoke-tested with a single PATENTS entry "met → complete with" carrying `target_synonyms=['comprising', 'including']` (produces 1 parent + 2 ▸-expandable sub-rows) and a mixed case where BRANTS "inrichting → device" with own-synonym "apparatus" is followed by PATENTS "inrichting → arrangement" (produces 1 parent + 2 sub-rows in source order).


## v1.10.87 – May 18, 2026

### Added (Trados-parity term-insertion shortcuts: TermLens-mirror popup on Ctrl, Term Picker grid on Ctrl+Shift+P)

Two separate term-insertion paths now, matching the Trados plugin exactly. The user side-by-sided the Workbench Ctrl popup against Trados's and asked for parity: "I want to mirror this functionality in Supervertaler Workbench, so both different shortcuts with the two systems."

**Ctrl (lone tap) — TermLens-mirror popup.** Replaces the v1.10.x numbered-list "Insert term or non-translatable" popup with a borderless floating mirror of the docked TermLens panel. Same chip rendering as the panel (same colours — pink for project, blue for regular, amber for NT, red for forbidden, purple for abbreviation matches — same +N badge, same ≡ / ℹ corner indicators, same hover sticky popups), positioned at the cursor.

Keyboard inside the popup:

  - Right / Down / Tab → next chip (wraps); Left / Up → previous chip
  - Enter → insert the highlighted chip
  - 1–9 → directly insert chip N (same numbering as the docked panel's Alt+N shortcut, so muscle memory carries over)
  - E → open the editor for the highlighted chip (popup closes first, dialog opens on top)
  - I → toggle the sticky metadata popup for the highlighted chip
  - Esc → close

Auto-closes on mouse movement >4 px, focus loss, or any unhandled key — with a pure-modifier carve-out so the Ctrl-release that opens the popup doesn't immediately close it. The numbered-list `modules/term_insert_popup.py` is removed. New `modules/termlens_popup.py` does the wrapping; it embeds a `TermLensWidget` in a new "popup mode" (zoom buttons + refresh + info label hidden, tighter margins) and layers cycling + auto-close + insertion-guard logic on top. Single-flight insert guard against click + Enter racing for the same chip.

`TermBlock` and `NTBlock` gain a `set_current(bool)` method that draws a 2-px blue (or amber, for NT) rounded outline around the chip via a new `paintEvent` — the docked panel never calls it, so the docked rendering is unchanged. `TermLensWidget` gains `get_term_blocks()` (returns the ordered chip list — mirrors Trados's `BuildSegmentBlocks` factory in spirit) and `set_popup_mode(bool)` (toggles the chrome).

**Ctrl+Shift+P — Term Picker modal dialog.** New tabular grid showing all matches for the current segment with columns `#`, source-lang, target-lang, Termbase. Synonyms collapse by default as a `▸` indicator in the `#` column; Right-arrow expands to `▾` with indented `└` sub-rows. Row backgrounds: pink for project termbase, blue for regular, amber for NT, grey for synonym sub-rows. Keyboard: ↑/↓ navigate (with wrap), ←/→ collapse/expand, Enter inserts, 0–9 selects + auto-inserts (auto-insert only fires when total matches ≤9, to avoid surprising the user when their digit was meant as the leading digit of a two-digit number).

New `modules/term_picker_dialog.py` (~330 lines) holds `TermPickerDialog` + a `build_picker_matches()` helper that converts the raw segment match data into the dialog's row schema (groups multiple termbase entries on the same source word as primary + synonym sub-rows). Dialog size + column widths persist via the main settings dict — `Supervertaler._term_picker_settings` — so the user's preferred layout sticks across sessions.

Wired up in `Supervertaler.py` via the existing `create_shortcut("term_picker", "Ctrl+Shift+P", …)` helper with `ApplicationShortcut` context so the binding survives focus being in any QTextEdit. The lone-Ctrl-tap event filter (`_LoneCtrlEventFilter`) is unchanged — it still calls `show_term_insert_popup` — only the popup body that method shows is new.

Net effect: Workbench and Trados now offer the same two-shortcut, two-popup paradigm. Ctrl for fast "I know what I want, insert it" flow; Ctrl+Shift+P for "let me look at the full grid, including synonyms, before I commit".


## v1.10.86 – May 18, 2026

### Changed (TermLens +N badge now counts only cross-entry alternatives, not target synonyms — matches Trados)

Clean three-way separation between the chip's status indicators:

 - **+N badge** = "N more termbase entries hit this source word" (i.e. another termbase has its own competing translation)
 - **≡ corner indicator** = "this chip has synonyms available in the popup"
 - **ℹ corner indicator** = "this chip has metadata (definition / domain / notes / URL)"

The pre-v1.10.86 `+N` calculation was `len(translations) - 1`, which counted target synonyms inlined from the primary entry the same as cross-termbase hits. Result: a chip like "complete with" — single termbase entry with 2 target synonyms — would render as `complete with +2` even though there was only one underlying entry. User compared the Workbench TermLens side-by-side with the Trados TermLens and confirmed Trados renders the same source word as just `complete with` (no badge) because Trados scopes `+N` to entries, not surface forms. "I think we should change it so that the little +N indicates only whether there are multiple alternative term-based entries."

Fix: count only the entries in the translation stack that *don't* carry the `is_synonym=True` flag set by `build_matches_dict` when it inlines `target_synonyms`. Synonyms still appear in the +N popup with their own numbered shortcuts (Alt+8, Alt+9, …) so they're still insertable — they just don't bloat the on-chip badge any more. The ≡ corner indicator does the disclosure job on its own.

Net effect: `complete with` now renders bare (no +N), matching Trados; `device +1` keeps its +1 because the two hits are from genuinely different termbases (BRANTS + PATENTS); chips with both extra entries *and* synonyms show a `+N` reflecting only the cross-entry count plus a ≡ for the synonyms.


## v1.10.85 – May 18, 2026

### Fixed (TermLens synonym indicator no longer false-positives on chips that just have multiple termbase hits)

The indigo ≡ corner indicator on a chip is supposed to mean "this entry has synonyms" — extra source spellings or extra target equivalents recorded in `termbase_synonyms` for that term. v1.10.83/v1.10.84 had a leftover fallback in the `has_synonyms` computation that *also* triggered the indicator whenever a chip had more than one translation in its stack, on the theory that the `+N` more-translations badge was "synonyms-ish". It's not. When BRANTS has "inrichting → device" and PATENTS has "apparatus → inrichting", that chip has `len(translations) == 2` — two unrelated termbase entries that happen to share a source word — but neither entry has any actual synonyms. The user got a misleading ≡ indicator promising synonyms they wouldn't find by right-clicking either entry.

Reported by a user: "the word inrichting = device has a synonym indicator. However, when I right click on the term and look at it and both of its different term-base entries, none of them have a synonym. 'verbinding = bond' also has a synonym indicator, but when I opened it, one of its term-base entries does have a synonym, so this one's correct."

Fix: removed the `or len(self.translations) > 1` fallback in `TermBlock.init_ui`'s `has_synonyms` calculation. The indicator now appears only when at least one translation in the stack carries actual synonym data — either an explicit `source_synonyms`/`target_synonyms` list on the primary entry, or the `is_synonym=True` marker on entries that `build_matches_dict` inlined from a parent's `target_synonyms`. The `+N` badge continues to signal "there are more options here" on its own — it never needed the ≡ icon to make that clear.

Net effect: ≡ indicators only show up where they're truthful. Chips with multiple cross-termbase hits but no synonym records render with just the `+N` badge and (if applicable) the amber ℹ metadata dot, no false synonym promise.


## v1.10.84 – May 18, 2026

### Fixed (v1.10.83 regressions: chip background colours stopped rendering; corner indicators overlapped the source-word text above)

Two regressions from the v1.10.83 corner-indicator-overflow rework, both caught in the next round of user testing:

**1. Chip background colours disappeared.** All chips rendered with the panel's white background instead of pink (project termbase) / blue (regular) / amber (non-translatable) / red (forbidden) / purple (abbreviation match). Cause: the v1.10.83 removal of `_ChipContainer.paintEvent` skipped the corresponding `setAttribute(Qt.WA_StyledBackground, True)` call that custom QWidget subclasses need for Qt's stylesheet `background-color` rule to apply. Without the attribute, the QSS background is silently ignored — Qt only applies it to "natively styled" widget classes (QPushButton, QLineEdit, etc.) by default. Restored by adding the attribute in `_ChipContainer.__init__`.

**2. Corner indicators overlapped the source-word text above the chip.** The v1.10.83 overlay sits ~5 px above the chip's top edge (the half-overflow that makes the Trados-style "indicator at the corner" visual work), but with the existing zero-spacing TermBlock layout the chip sat directly below the source-word label — meaning the overflow landed on the bottom of the source text. User report: "the dots are painted over the text a little bit. If we could move the entire line down just a tad, that would no longer be a problem." Fixed by inserting a 4 px spacer between the source word label and the chip via `layout.addSpacing(4)`. Added unconditionally for every translation chip (not just chips with indicators) because mixing spaced and unspaced chips within the same TermLens row would re-break the chip-baseline alignment that the v1.10.83 work specifically fixed.

Net effect: chips render in their proper colour, the overlay indicators sit cleanly in the gap between the source word and the chip without touching either, and the per-chip baseline alignment from v1.10.83 is preserved.


## v1.10.83 – May 18, 2026

### Changed (TermLens chip indicators now overflow above the chip — Trados-style — so chips with indicators line up with chips without them)

User flagged a UX issue with the v1.10.80–v1.10.82 corner indicators: chips with metadata/synonym indicators sat ~12 px lower than chips without them in a TermLens row, because the indicator headroom was reserved INSIDE the chip layout (the `target_layout` got a 12 px top margin, pushing the chip's content down). The result was that "bond", "device", and "end" chips were visibly out of vertical alignment with "complete with" and "wheels" in the same row. User posted a side-by-side Trados screenshot showing how Trados handles it: the indicators sit half-inside half-outside the chip's top-right corner, *overflowing above the chip's top edge*, so the chip itself stays at its compact height.

Qt clips a widget's painting to its own bounds, so we can't replicate Trados's overflow by painting from inside the chip. The fix: move the indicator-painting out of `_ChipContainer` and onto a **separate sibling overlay widget** (`_CornerIndicators`) that's a child of the outer TermBlock. The overlay has its own bounds, so its paint isn't clipped to the chip's. It's positioned absolutely at the chip's top-right corner (overflowing above the chip's top edge) via `_position_corner_indicators()`, called from `TermBlock.resizeEvent` and `showEvent` so the overlay tracks the chip across layout passes.

Implementation:

 - **New `_CornerIndicators(QWidget)` class** — holds the indicator-drawing code that used to live in `_ChipContainer.paintEvent`. Translucent background (`WA_TranslucentBackground`) so only the painted circles are visible. Click-through (`WA_TransparentForMouseEvents`) so the chip's click-to-insert behaviour still works when the user clicks anywhere on the chip including the overlay area.
 - **`_ChipContainer` simplified** — no longer holds the indicator flags or overrides `paintEvent`. The hover-state stylesheet still works via Qt's default QWidget paint path (no more `QStyle.drawPrimitive(PE_Widget)` workaround needed).
 - **`TermBlock.target_layout.contentsMargins.top` back to 1 px unconditionally** — the v1.10.80 conditional bump to 12 px is gone, since the indicators no longer live inside the chip. Every chip in a TermLens row now aligns to the same vertical baseline regardless of whether it has indicators.
 - **`resizeEvent` + `showEvent` on TermBlock** — call `_position_corner_indicators()` so the overlay's position updates as the chip resizes or first appears. `showEvent` specifically matters for the initial layout pass — without it the overlay would sit at (0,0) until the first resize.
 - **`raise_()`** on the overlay so it paints above the chip in z-order when their bounds overlap (the bottom of the overlay sits inside the chip's top-right corner).

Removed the now-unused `QStyleOption` import.

Net effect: chips in a TermLens row are now perfectly aligned to the same vertical baseline. Indicators on chips that have them sit at the top-right corner, half-above half-inside, exactly matching the Trados TermLens. The whole panel feels visually quieter and more regular.


## v1.10.82 – May 18, 2026

### Changed (Amber metadata-dot indicator now carries a white "i" glyph inside it — the universal "info / more here" affordance)

A user wondered if the v1.10.75 amber dot could be made even clearer — original suggestion was a tiny eye glyph inside the circle. Honest answer: at the previous 8 px dot size, a recognisable eye is below the legibility threshold — you'd have ~5×5 effective drawing pixels for an outline plus a pupil, and even with anti-aliasing the result reads more like "scribble in a circle" than "eye". The "i" character is structurally simpler (a 1×1 dot + a 1×3 vertical line) so it stays legible at small sizes, AND it's the universal "more info here" affordance — Wikipedia infoboxes, browser address bars, every OS settings dialog use the same pattern. So a user who sees the amber dot for the first time can immediately read it as "hover for details" without having to learn that the colour alone encodes that.

This commit:

 - Bumps the metadata dot from **8 → 10 px** to give the glyph enough room to render cleanly.
 - Draws a **bold white "i"** centred inside via `QPainter.drawText` (Segoe UI, pixel-size 8). Cleaner than constructing the glyph from drawing primitives, and the system font's text-rendering hint produces a sharp anti-aliased result.
 - Bumps the chip's top headroom (`target_layout.contentsMargins.top`) from **10 → 12 px** to accommodate the larger dot without it brushing against the chip text or the shortcut badge.

The synonym indicator (indigo ≡ circle) is unchanged — three horizontal lines is already a recognisable "menu / list" affordance at 9 px.

Net effect: the corner indicator now reads as "ℹ" at a glance, not just "amber dot means something". Colour-only signal is preserved (amber = metadata) as a fallback for colourblind users who might miss the glyph but see the colour.


## v1.10.81 – May 18, 2026

### Fixed (target-synonym chips were rendering as pink and outranking their own parent — broken `ranking + 1` arithmetic after the v1.10.69 ranking-as-flag change)

User noticed an off-colour chip after v1.10.80: the "met" chip in their Dutch source segment displayed primary translation `comes with` in **pink** (project-termbase colour) even though `comes with` is a target synonym of a PATENTS entry (a background termbase, not project). Database evidence: PATENTS term 89706 (`complete with → met`) has source-side synonyms `['comes with', 'including']`, which after the v1.10.62 direction swap become target synonyms in the project's NL→EN direction.

**Root cause** — pre-v1.10.69 the synonym-append code in `update_with_matches` did `'ranking': match.get('ranking', 99) + 1` to push synonyms "slightly lower priority than their parent". That arithmetic was correct when `ranking` was a higher-is-lower-priority number (99 = default low, 1 = high). But the v1.10.69 index builder changed `ranking` to a **flag** (1 = project-termbase / priority-1 activation, 0 = background) without updating the synonym arithmetic. So a background-termbase entry's parent has `ranking = 0`; `0 + 1 = 1`; the synonym chip is now flagged as if it were a project entry. The v1.10.80 sort key prioritises `ranking == 1` entries → the synonym sorts AHEAD of its own parent → becomes the primary chip → `is_effective_project = (ranking == 1) = True` → pink.

End result the user saw: `comes with` (synonym) sorted before `complete with` (the actual primary translation) and rendered pink, when the whole entry should have been blue with `complete with` as the primary.

**Fix** — two changes in `termlens_widget.update_with_matches`:

 1. **Synonyms now inherit the parent entry's `ranking` unchanged** (was `ranking + 1`). A synonym of a PATENTS entry now has `ranking = 0`, matching its parent.
 2. **New `is_synonym` boolean flag** on every chip-translation entry — primary entries set `False`, synonym appendings set `True`. The sort key in this function has been extended with `is_synonym` as a tier between the ranking comparison and the termbase-name tiebreak, so primary entries always come before synonyms within the same `ranking` tier:

    ```python
    matches_dict[_key].sort(key=lambda t: (
        bool(t.get('forbidden', False)),     # non-forbidden first
        not (t.get('ranking') == 1),         # project-priority first
        bool(t.get('is_synonym', False)),    # primary before synonyms
        t.get('termbase_name', '') or '',    # alphabetical tiebreak
    ))
    ```

    That separates the two concerns the old `+ 1` was conflating: priority ordering (now purely from `ranking`) and primary-vs-synonym ordering (now from `is_synonym`). No semantic regression — alternative translations still sort below the primary in the `+N` popup.

Net effect: `met` chip now shows `complete with` (primary) in blue (PATENTS = background termbase), with `comes with` and `including` listed as `+2` alternatives. Restart Workbench to pick it up.


## v1.10.80 – May 18, 2026

### Fixed (TermLens chip: project termbase now actually renders pink + amber metadata dot and indigo synonym icon are now visible)

User screenshot showed three remaining gaps vs the Trados TermLens that v1.10.77 didn't actually close:

**1. Project termbase chip wasn't pink** even though the v1.10.77 sort tried to put it first. The user's project (BRANTS LTRI) marks its project termbase via `termbase_activation.priority = 1`, NOT via the `termbases.is_project_termbase` column. Workbench's `_build_termbase_index` correctly computes `ranking = 1` for such entries (the SQL `CASE` clause covers both signals), but my v1.10.77 sort key only checked `is_project_termbase` directly and ignored `ranking`. So with BRANTS (is_project_termbase=0, ranking=1) and PATENTS (is_project_termbase=0, ranking=0), both entries had the same `is_project_termbase` value and the sort fell back to `ranking` *ascending* — putting PATENTS (0) before BRANTS (1). Backwards. The chip displayed PATENTS' "apparatus" (blue) instead of BRANTS' "device" (pink).

Fix: sort key now uses `not (t.get('ranking') == 1)` so any entry with `ranking == 1` (project termbase OR priority-1 activation, whichever the index builder caught) sorts first. Tiebreak by termbase name for stable ordering across ties. Verified by inspection: with the fix, BRANTS sorts first → `is_effective_project = (ranking == 1) = True` → chip renders pink → displays "device" as primary translation.

**2. Amber metadata dot was hidden under the shortcut badge.** v1.10.75 painted it at the top-right corner of the chip via `_ChipContainer.paintEvent`. Trouble is, Qt's children paint AFTER the parent's paintEvent — and the shortcut badge (laid out at the right edge of `target_layout` with a 1 px top margin) was rendered *on top of* the indicator I'd just painted. User report: "the little orange dot is underneath the number, not on the corner like in Trados".

Fix: when the chip has corner indicators (metadata or synonyms), `target_layout` now uses a 10 px top margin instead of 1 px. That reserves a top strip inside the chip for the indicators only; children (text + `+N` + badge) sit cleanly below in the remaining content area. Qt clips paint to widget bounds so we can't overflow above the chip the way Trados does with its WinForms paint; reserving space inside is the next-best thing. Indicators also nudged to `y=1` (was `y=2`) and the synonym icon's size bumped to fit the new headroom precisely.

**3. Indigo ≡ synonym icon was hidden the same way** — same fix as the amber dot. Both indicators are painted by the same `_ChipContainer.paintEvent`; the layout-margin fix benefits both at once.

Net effect: project termbase entries now actually display in pink, and the corner indicators (amber for "this entry has Definition/Domain/Notes/URL — hover for details" and indigo ≡ for "this entry has synonyms") are visible at the chip's top-right corner where they belong. Trados-side parity for the chip presentation is now functionally complete — the remaining gap is purely visual fidelity (Trados's indicators overflow slightly above the chip; Workbench's sit entirely inside), which is a Qt platform constraint, not a feature gap.


## v1.10.79 – May 18, 2026

### Changed (v1.10.78 "Editing:" dropdown now filters to active termbases only — matches Trados, and necessary for users with dozens or hundreds of termbases)

The v1.10.78 dropdown deliberately skipped the activation filter so users could "see and edit related entries from inactive termbases too". A user with a large termbase collection (100+ termbases is realistic for a working translator with multiple long-term clients) pushed back immediately: the unfiltered query would dump every entry in the DB that happened to share the surface form, drowning the actually-relevant project termbase entries in noise. For the test entry "inrichting" the unfiltered query returned 4 entries (BEIJER + BRANTS-LTRI + BRANTS-PACC + PATENTS); only 2 of those are active for the current project (BRANTS-LTRI and PATENTS).

This commit adds the activation filter.

**The query:**

```sql
SELECT t.id, t.source_term, t.target_term, COALESCE(tb.name, '?'), tb.id
FROM termbase_terms t
LEFT JOIN termbases tb ON CAST(t.termbase_id AS INTEGER) = tb.id
LEFT JOIN termbase_activation ta ON ta.termbase_id = tb.id AND ta.project_id = ?
WHERE (LOWER(t.source_term) = LOWER(?) OR LOWER(t.target_term) = LOWER(?)
       OR LOWER(t.source_term) = LOWER(?) OR LOWER(t.target_term) = LOWER(?))
  AND (ta.is_active = 1 OR tb.is_project_termbase = 1 OR t.id = ?)
ORDER BY tb.name, t.id
```

The three-way OR in the activation clause is deliberate:

 - `ta.is_active = 1` — the termbase is activated for the current project (the normal case).
 - `tb.is_project_termbase = 1` — the termbase is the project's "project termbase" (always active by definition; doesn't need an explicit activation row).
 - `t.id = ?` — the loaded entry itself. Defensive: covers the rare case where the dialog was opened on an inactive entry via the Termbases tab "Edit Selected Term" button (TermLens itself never surfaces inactive-termbase entries, but the Termbases tab editor lets you click any termbase including inactive ones). Without this clause the loaded entry could disappear from its own dropdown.

**Project context** — the dialog walks the parent chain to find the main window's `current_project.id`, same pattern `setup_ui` uses for the caption query. If no project is active (e.g. dialog opened from a context without a loaded project — rare but defensible), the activation filter is silently skipped and the v1.10.78 unfiltered behaviour kicks in. Better to show extra entries than zero entries in that edge case.

**Verified against the user's real DB**: for project_id `542263504` (their BRANTS LTRI project) and loaded entry 93174, the dropdown now shows exactly 2 entries (BRANTS-LTRI + PATENTS), matching what their Trados Edit Term Entry dialog shows for the same term. The 2 inactive-termbase entries (BEIJER, BRANTS-PACC) are correctly filtered out.

Net effect: dropdown stays clean and project-scoped regardless of how many other termbases the user has accumulated.


## v1.10.78 – May 18, 2026

### Added (Edit Termbase Entry dialog: "Editing:" dropdown to switch between related entries in place — Trados parity)

A user comparing the Workbench Edit Termbase Entry dialog to the Trados equivalent pointed out a useful affordance Workbench was missing: in Trados, when the same source word has entries in multiple termbases (very common — a project termbase plus one or more background termbases all carry the same head word), the Edit Term Entry dialog has a dropdown at the top labelled "Editing in:" that lets you switch between those entries in place without closing the dialog and re-opening it on a different chip.

**New dropdown at the top of the Edit Termbase Entry dialog.** Hidden when only one entry exists; shown when two or more sibling entries share this entry's surface form. Each item is labelled `"{termbase_name}: {source_term} → {target_term}"` so the user can see what they're picking before they pick it. Selecting a different item:

 - Updates `self.term_id` to the new entry's id.
 - Re-queries the new entry's termbase direction and **flips the column captions + synonym section labels** if the new entry's termbase is oriented differently (so e.g. switching from a BRANTS NL→EN entry to a PATENTS EN→NL entry flips "Dutch:" / "English:" caption order).
 - Clears the synonym lists (they're append-only without this, so the new entry would inherit the old's synonyms cosmetically).
 - Calls `load_term_data` to repopulate every field from the DB — including the metadata block, the abbreviations, the NT / forbidden checkboxes, and the synonym lists.
 - Updates the window title to show the new entry's id + termbase name.

**Bidirectional match.** Two entries are "related" if either of their `source_term` or `target_term` (case-insensitive) matches either of the loaded entry's `source_term` or `target_term`. So for a loaded entry "inrichting → device":

 - Other termbases' normal-direction entries with `source_term="inrichting"` are included.
 - Reverse-direction termbases' entries where `target_term="inrichting"` (e.g. PATENTS' `"apparatus → inrichting"`) are included.
 - Entries containing "device" in either column are also included.

Matches the Trados behaviour of "show me everything related to this surface term concept", with the same caveat that "device" can sometimes pull in entries from unrelated translation pairs that happen to share that English word. Acceptable for a v1; can add tighter filtering (anchor strictly on source surface text) in a future iteration if users find it noisy.

**No activation filter** — entries from inactive termbases (other projects' termbases, archived termbases, etc.) are included in the dropdown. Trados filters by active termbases; Workbench is more permissive here on the theory that someone explicitly editing a term entry might also want to clean up related entries in inactive termbases. Easy to add a filter toggle later if users disagree.

**No "unsaved changes" warning** on switch — mirrors the Trados dialog, which also switches in place without prompting. If the user edited fields on entry A and then switches to entry B, the edits to A are silently discarded. The Save button only acts on the currently-loaded entry. Explicit warning can be added if it becomes a real footgun.

**Implementation entirely dialog-side** — no signal-chain changes (`TermLensWidget.edit_requested`, `_on_termlens_edit_entry`, etc. all unchanged). The dialog runs its own SQL query in `_populate_related_entries_combo()` at the end of `load_term_data`. So every existing call site — TermLens right-click, translation-results panel right-click, Termbases tab "Edit Selected Term" button — automatically gets the new dropdown without any caller-side changes.

Idempotent: `_populate_related_entries_combo` runs after every successful `load_term_data`, including after a switch, so the dropdown stays consistent if the surface terms of the newly-loaded entry differ from the previously-loaded one.

Net effect: switching between related entries goes from "close dialog → open new dialog on a different chip" (≈ 3 clicks) to one dropdown pick.


## v1.10.77 – May 18, 2026

### Changed (TermLens chip + popup polish to match the Trados TermLens — project-termbase entries now rank first, popup gets per-entry layout with Trados-style headings, "+N" indicator moves inline on the chip)

A user posted a side-by-side comparison of the Workbench TermLens and the Trados TermLens for the same segment. Three concrete differences fixed here:

**1. Project-termbase-first sort.** For the source term "inrichting" (which has entries in both the project termbase BRANTS and the background termbase PATENTS), Trados showed "device" (BRANTS, project) as the primary chip in pink; Workbench showed "apparatus" (PATENTS, background) in blue. Workbench was appending translations in iteration order — first match found wins — instead of sorting by project-termbase precedence the way Trados does.

`update_with_matches` now sorts every `matches_dict[key]` list before building chips:

 1. Non-forbidden first (forbidden last — don't let a "do not use" translation become the primary just because it happens to be from the project termbase).
 2. Project termbase first (so the chip background + primary translation reflect the user's canonical project choice).
 3. Then by `ranking` (priority: 1 = top, 99 = default).

Same precedence Trados uses in `TermBlock.cs` `sortedEntries.Sort(…)`. After the sort, the primary chip displays the project-canonical translation in pink — matching Trados.

**2. Popup format: per-entry Trados-style layout.** The previous popup showed only the primary entry's metadata in detail and collapsed alternatives to a bare numbered list — the alternatives' notes, definitions, URLs were dropped entirely. The Trados popup shows each entry separately with a heading + its own metadata block. Workbench now does the same:

```
source → target [TermbaseName] (ID 12345)
  Notes:
    <multi-line notes, line breaks preserved>
  Def: <definition>
  Domain: <domain>
  URL: <clickable link>
  Abbr: <src / tgt>
  Also: <source synonyms>
─────────────────────────────────────
source → target [TermbaseName2] (ID 67890)
  <metadata for the second entry>
```

Each entry from each termbase gets its own heading + metadata block, separated by a thin horizontal rule. The heading uses `[TermbaseName]` in mid-grey and `(ID N)` in light-grey so the source → target pair stays visually dominant. Multi-line notes preserve newlines (`\n` → `<br>`). User-authored prose is HTML-escaped (`<` / `>` → `&lt;` / `&gt;`) so a stray angle bracket can't break the popup layout. URLs are clickable (popup already enables `openExternalLinks`).

**3. "+N" indicator moves inline on the chip.** Previously rendered as a separate tiny gray QLabel below the chip in 7-pt font — easy to miss. Now lives inside the chip's horizontal layout, immediately right of the target text and before the shortcut badge — same position as Trados. Slightly bigger (9 pt, weight 600), slightly darker grey (`#555555` light mode / `#7A8B9F` dark mode) so it's actually noticeable. Tooltip on the +N tells you the exact count and that hovering the chip surfaces the details.

The unused `_meta_lines` inner helper that the popup used in v1.10.75 is now removed (the per-entry rewrite supersedes it). No other call sites — the function was scoped to `init_ui`.

Net effect: the two products' TermLens panels now read the same way. Side-by-side comparisons should be hard to tell apart on a per-chip basis.


## v1.10.76 – May 18, 2026

### Changed (HelpTopics URL slugs repointed at the new /workbench/termbases/ paths after the help-site folder rename)

The Supervertaler-Help repo renamed `workbench/glossaries/` → `workbench/termbases/` and `workbench/superlookup/glossary-search.md` → `termbase-search.md` so the live help URLs match the in-app terminology ("termbase" everywhere in the Workbench UI since v1.10.62). The 7 `HelpTopics` URL slugs in `modules/help_system.py` that targeted the old paths are repointed here.

 - `GLOSSARY_BASICS`     → `workbench/termbases/basics/`
 - `GLOSSARY_CREATING`   → `workbench/termbases/creating/`
 - `GLOSSARY_IMPORTING`  → `workbench/termbases/importing/`
 - `GLOSSARY_HIGHLIGHT`  → `workbench/termbases/highlighting/`
 - `GLOSSARY_TERMLENS`   → `workbench/termbases/termlens/`
 - `GLOSSARY_EXTRACTION` → `workbench/termbases/extraction/`
 - `SUPERLOOKUP_GLOSS`   → `workbench/superlookup/termbase-search/`

Constant *names* kept as `GLOSSARY_*` for now to avoid a mass-rename of every `set_help_topic(…, HelpTopics.GLOSSARY_*)` call site across the GUI (dozens of references). The constants are pure data; only their *values* (the URL slugs) need to change for the in-app Help button to land on the right page. The constant names can be renamed in a follow-up.

**Older installed Workbench versions are unaffected** — the help repo's `public/_redirects` adds Cloudflare Pages 301 redirects from `/workbench/glossaries/*` → `/workbench/termbases/:splat` and `/workbench/superlookup/glossary-search/` → `/workbench/superlookup/termbase-search/`. So any in-app help link from a pre-v1.10.76 install still lands on the right page (just via a redirect hop).


## v1.10.75 – May 17, 2026

### Added (TermLens Tier 3 visual parity: forbidden / non-translatable / abbreviation chip colours, corner indicators for metadata + synonyms, floating sticky popup replacing the Qt tooltip)

Closes the Trados TermLens feature-parity audit. v1.10.73 brought the metadata fields and synonyms through to the tooltip ("Tier 1 + 2"); v1.10.75 ports the **visual polish** that makes the same information discoverable at a glance without hovering.

**Tier 3a — chip colour precedence**

Chip backgrounds now follow a five-way precedence ladder matching the Trados TermBlock:

1. **Forbidden term** → red `#E53935` background, white text, strikethrough font — unmistakable visual stop sign for forbidden terms (memoQ and MultiTerm use the same convention).
2. **Non-translatable** → amber `#FFF3D0` background (Trados-matching), dark text — signals "copy through, don't translate".
3. **Abbreviation match** → light purple `#E8DAFF` background, dark purple text — tells the user "this chip is the GC → GC pair, not the full term".
4. **Project termbase** → pink (existing behaviour).
5. **Regular termbase** → blue (existing behaviour).

Highest-precedence flag wins. A forbidden NT chip displays as forbidden (red) because the user absolutely needs to know not to use it.

**Tier 3b — corner indicators on the chip**

New `_ChipContainer` widget (custom QWidget subclass) paints two small icons in the top-right corner of every chip, on top of the stylesheet background:

- **Amber dot** (`#F59E0B`) when the entry has at least one of: definition, domain, notes, URL. Tells the user "there's more here, hover for the tooltip" without making the chip itself bigger.
- **Indigo ≡ circle** (`#6366F1`) when the entry has synonyms (source or target) — drawn as three horizontal white lines inside a filled circle, matching Trados's visual.

The paint uses the standard Qt "stylesheet + custom paint" pattern (`QStyleOption` + `drawPrimitive(PE_Widget)`) so the QSS background-color still renders normally underneath the indicators. Both indicators are drawn fully inside the widget bounds (Qt clips paint to widget bounds; half-outside indicators just wouldn't render).

**Tier 3c — abbreviation-as-primary chip**

`_build_termbase_index` now compiles regex patterns for each pipe-separated `source_abbreviation` variant (e.g. `"GC|G.C.|gc"` → three patterns), stored as `abbreviation_variants` on each index entry. `_search_termbase_in_memory` tries every variant in addition to the main `source_term` pattern; matched variants emit a SECOND entry in the matches dict keyed by a synthetic `f"abbr_{term_id}_{i}"` string so it doesn't collide with the main `term_id` integer key.

The abbreviation-match entry carries `matched_via_abbreviation=True` and uses `target_abbreviation` as its `translation`. Downstream, TermLens renders it as its own chip with a purple background and target_abbreviation as the chip text — so "GC" in the source gets a "GC → GC" purple chip, while "gas chromatography" elsewhere in the same segment gets the regular "gas chromatography → gaschromatografie" blue chip. Clicking either inserts the appropriate form (full term or abbreviation) — the chip's `target_term` is what gets inserted on click.

Multi-variant supported: `source_abbreviation` like `"GC|G.C."` matches both "GC" and "G.C." in the source, each producing its own chip via separate synthetic keys.

**Tier 3d — floating sticky popup replaces the Qt tooltip**

New `TermPopup` singleton (one shared instance across the app) replaces the per-chip Qt tooltip. Behavioural advantages over the built-in QToolTip:

 - **Stays open while hovered**: mouse moves don't dismiss it. Multi-line Definition / Notes / synonym lists are actually readable.
 - **No size cap**: dense metadata renders without clipping. `maximumWidth = 420 px` for word-wrap legibility.
 - **Clickable URLs**: `setOpenExternalLinks(True)` so a URL in the popup is a proper link, not just display text.
 - **Sticky grace period**: 250 ms close timer cancels if the mouse re-enters either the chip or the popup itself, so a slight wobble while moving from chip to popup doesn't lose the content.
 - **Screen-edge clamping**: positions below the chip by default; flips above if no room; clamps to the right edge to avoid going off-screen.

Lifecycle matches Trados's `TermPopup`:
 - `TermBlock.enterEvent` → `TermPopup.show_for(chip, html)` (cancels any pending close, anchors below the chip).
 - `TermBlock.leaveEvent` → `TermPopup.schedule_close()` (250 ms grace).
 - `TermPopup.enterEvent` → cancels close (user is reading).
 - `TermPopup.leaveEvent` → schedules close.

Content shape is the same HTML the pre-v1.10.75 tooltip used — primary target + shortcut hint + notes + Abbr / Definition / Domain / URL / "Also: source synonyms" rows + (for multi-translation chips) an "Alternatives:" list with synonyms + cross-termbase alternatives.

Future: Markdown rendering on Definition / Notes (via the `markdown` library already in deps) is a small follow-up — for now the popup renders the raw text from those fields just like the tooltip did.

**Net effect** vs the Trados TermLens, after v1.10.75:

| Trados feature | Workbench status |
|---|---|
| Pink (project) / blue (regular) chip backgrounds | ✅ since forever |
| Shortcut number badges with tier numbering | ✅ since forever |
| `+N` indicator for extra translations | ✅ since forever |
| Synonyms in hover popup | ✅ v1.10.73 |
| Abbreviations / Definition / Domain / URL in hover | ✅ v1.10.73 |
| Amber corner dot for metadata-rich entries | ✅ v1.10.75 |
| ≡ indigo badge for synonyms | ✅ v1.10.75 |
| Abbreviation-as-primary chip (purple) | ✅ v1.10.75 |
| Forbidden visual (red + strikethrough) | ✅ v1.10.75 |
| Non-translatable visual (amber) | ✅ v1.10.75 |
| Sticky floating popup with Markdown | ✅ v1.10.75 (Markdown deferred) |
| MultiTerm green chip | N/A (Workbench has no MultiTerm support) |

Feature parity reached. The two products' TermLens panels now read the same way regardless of which one you're working in.


## v1.10.74 – May 17, 2026

### Fixed (v1.10.73 edit-dialog metadata changes weren't visible in TermLens until a manual restart, because UPDATEs to termbase_terms didn't bump modified_date and the v1.10.69/v1.10.72 snapshot gating skipped the index rebuild)

A user immediately tested v1.10.73 by adding a definition, notes, URL, and project to a term entry via the Edit Termbase Entry dialog — saved cleanly, but the TermLens chip tooltip on the same term still showed only the synonym ("Also: uiteindeSYN") with no Definition / Notes / URL / Project rows. The data was in the database; the in-memory termbase index just hadn't been rebuilt to pick it up.

**Root cause: snapshot gating + SQLite's UPDATE behaviour.** The v1.10.69 / v1.10.72 auto-refresh chain decides whether to rebuild the in-memory index by snapshotting four cheap aggregates: `COUNT` + `MAX(id)` on `termbases`, plus `COUNT` + `MAX(id)` + `MAX(modified_date)` on `termbase_terms`. An UPDATE-only change doesn't shift `COUNT` or `MAX(id)` — that only catches INSERT / DELETE. And SQLite's `DEFAULT CURRENT_TIMESTAMP` on the `modified_date` column **only fires on INSERT, not on UPDATE** — it never auto-bumps. So the column stays frozen at INSERT time, the snapshot looks identical pre- and post-edit, `force_refresh_matches` skips the index rebuild, and the TermLens chip keeps showing the pre-edit data forever (until a project reload or a separate INSERT/DELETE triggers the snapshot).

The synonym in the user's report came through because synonyms are a separate INSERT into `termbase_synonyms` (the save dialog does `DELETE FROM termbase_synonyms WHERE term_id = ?; INSERT …` to handle the wipe-and-reinsert pattern), and the v1.10.73 bulk loader picks up the new synonym on the next rebuild. Except… the rebuild doesn't fire here either, because the snapshot only watches `termbase_terms`, not `termbase_synonyms`. So even the synonym wouldn't show on a different session — it only worked for the user because *another* trigger (probably the optimisation-path index append when they added the term in the first place) had populated it earlier.

**Fix.** Every UPDATE statement that touches `termbase_terms` now explicitly bumps `modified_date = CURRENT_TIMESTAMP`. Four sites covered:

 - `modules/termbase_entry_editor.save_term` — the Edit Termbase Entry dialog's main UPDATE.
 - `Supervertaler.save_forbidden_state` — the inline Forbidden checkbox toggler in the Termbases tab.
 - `Supervertaler.save_term_edit` — the inline cell-edit handler in the Termbases tab.
 - `modules/termbase_manager.update_term` and `modules/glossary_manager.update_term` — both dynamic-SQL builders; `modified_date = CURRENT_TIMESTAMP` is appended to the `updates` list unconditionally before the SET clause is joined.

(`modules/termbase_manager.set_nontranslatable` was already bumping `modified_date` from before — no change needed there.)

Net effect: edit any termbase entry, save the dialog, and the snapshot gating in `force_refresh_matches` sees `MAX(modified_date)` shift, fires the index rebuild, and the TermLens chip tooltip updates within ~100 ms with the new metadata. No project reload, no manual F5, no manual 🔄 click.

Future-proofing: a later commit could also extend the snapshot to include `(COUNT, MAX(id))` on `termbase_synonyms` so synonym-only changes are detected on first edit too, but in practice every synonym edit happens as part of an entry save (the dialog wraps both together) so the term's `modified_date` bump already covers it.


## v1.10.73 – May 17, 2026

### Added (TermLens now shows synonyms, abbreviations, definitions, URLs in chip tooltips — porting Tiers 1 + 2 of the Trados TermLens feature parity audit)

A user pointed out that the Workbench TermLens was missing display features the Trados TermLens has had for ages — most obviously synonyms. An audit of the Trados `TermBlock` and `BulkLoadTargetSynonyms` code confirmed the data has always been there (both products share the same SQLite schema; the user's PATENTS termbase alone has 421 synonyms, 63 source abbreviations, 59 target abbreviations, 26 URLs, 15 definitions on 306+ terms) — Workbench just wasn't loading or displaying it. This commit ports Tiers 1 + 2 of the audit's three-tier plan:

**Tier 1: pull the metadata fields that were already in the schema but never SELECTed.** `_build_termbase_index`'s main query now includes `COALESCE(t.definition, '')`, `COALESCE(t.url, '')`, `COALESCE(t.source_abbreviation, '')`, and `COALESCE(t.target_abbreviation, '')`. These come back on every match dict from `_search_termbase_in_memory`. The TermBlock tooltip's pre-existing `_meta_lines` helper (which was wired up to render Abbreviations / Definition / Domain / URL but always got empty strings) immediately starts showing those rows.

**Tier 2: bulk-load `termbase_synonyms` and surface them.** New `_load_synonyms_bulk()` helper does a single query for every row in `termbase_synonyms` (filtered to non-forbidden, ordered by `display_order` when present so the "preferred synonym first" convention from the Edit dialog flows through to the display). Returns `{term_id: {'source': [text, …], 'target': [text, …]}}` so per-term synonym lookup during index build is pure dict access — same single-query pattern Trados uses in `BulkLoadTargetSynonyms`, orders of magnitude faster than the per-term query alternative. `_build_termbase_index` calls it once per rebuild, attaches the synonyms to each index entry (and, crucially, **swaps source/target synonym lists** alongside source/target text and abbreviations when the termbase runs opposite to the project — same direction-swap logic the v1.10.62 fix introduced for the main term pair).

**How synonyms surface in TermLens:**

 - **Target synonyms** become additional clickable translation chips. Each one appears as its own row in the +N popup, gets its own shortcut number badge, and can be inserted directly. The chip carries an inherited "(syn)" suffix on the termbase-name field so the alternatives list shows users where each translation came from. This piggybacks on the pre-existing append-as-alternative logic in `update_with_matches` — no new UI surface, just the data flowing through.
 - **Source synonyms** appear in the chip's hover tooltip as an `Also: hinge mechanism, alt-form-2` row, matching the Trados TermBlock popup format. Synonym text is defensively HTML-escaped (`<` / `>` → `&lt;` / `&gt;`) so a stray angle bracket can't break the tooltip layout.

**Cost:** the bulk synonym load adds one extra SELECT to `_build_termbase_index` (sub-second on a 421-row synonym table; scales linearly), and the SELECT itself widens by four COALESCE columns (negligible). The in-memory index entry grows by ~6 fields per term — for a 5,400-term termbase that's tens of KB of additional memory, well within budget.

**Tier 3 (corner-dot metadata indicator, ≡ synonym indicator, abbreviation-as-primary-display, forbidden / NT background colours, rich floating popup with Markdown rendering)** is the visual-polish lift and would need a Qt paint-event refactor on `TermBlock`. Deferred to a future session — the audit ETA was 4–6 hours and this commit's scope was already the "biggest visible improvement for least work" subset. Tooltip-level disclosure (this commit) gets you all the information that's in Trados; Tier 3 just makes some of it discoverable without hovering.

End-user impact for the next restart: hover any term chip in TermLens that has a synonym, abbreviation, definition, or URL — the tooltip now shows that information. Target synonyms become clickable alternatives with their own shortcut numbers, identical to how multi-translation chips have always worked.


## v1.10.72 – May 17, 2026

### Changed (F5 + TermLens 🔄 button + auto-refresh collapsed into one path: ``force_refresh_matches`` with smart snapshot-gated index rebuild)

A user pointed out that having two refresh systems (F5 keyboard shortcut and the TermLens 🔄 button) was confusing — which one to press? When does each apply? Before this commit there were genuinely two different code paths (``force_refresh_matches`` for F5, ``_post_termbase_delete_refresh`` for the button + auto-refresh + delete paths) that did slightly different things, and the asymmetry was both the cause of v1.10.71's display-under-refresh bug and a UX gotcha.

This commit collapses everything into one path:

**``force_refresh_matches`` is now the single refresh entry point** for the whole termbase + TM display chain. F5, the TermLens 🔄 button, the file-watcher auto-refresh, and every delete path all route through it. At the top of the function it runs a **smart snapshot check** — using the same `_snapshot_termbase_db_state` helper introduced in v1.10.69 for the watcher's debounce gating — to decide whether the in-memory termbase index needs to be rebuilt:

 - **Snapshot unchanged** (same row counts, same MAX(id), same MAX(modified_date) on both `termbases` and `termbase_terms`) → skip the rebuild step. The common case for F5 ("I just want to redraw the current segment") stays fast: <100 ms total, no extra DB roundtrip.
 - **Snapshot changed** (own-write that bypassed `_build_termbase_index`, cross-process edit from the Trados plugin, external SQL tool) → rebuild the in-memory index from disk first (~0.5–1 s on a typical termbase), then continue with the rest of the refresh.

After the index check, the rest of the function is unchanged from the pre-v1.10.72 F5 code: clear per-segment caches → re-search termbases / TMs / NT lists → redraw TermLens widgets → redraw the Translation Results panel → update source-cell highlights.

**Net effect for the user:**

 - **F5** and the **🔄 button** are now exactly the same operation. Press whichever is more convenient. The button's tooltip now mentions `(F5)` so the shortcut is discoverable.
 - **Auto-refresh** (file watcher) calls the same function — no duplication.
 - **Delete paths** call the same function — the snapshot will always show the delete and the rebuild fires (correctly evicting the deleted term from the in-memory index so it doesn't surface as a phantom match on the next search).
 - The v1.10.71 "F5 fixes what the 🔄 button leaves under-refreshed" symptom is gone by construction — there's no longer a "lighter" path to leave anything under-refreshed.

`_post_termbase_delete_refresh` is now a thin wrapper that just calls `force_refresh_matches` + refreshes the Termbases tab UI. Kept around so the existing call sites (translation_results_panel, modules/termbase_entry_editor's delete-from-dialog walk) don't need to be updated.

The tooltip on the 🔄 button is updated to surface all of this:

> Refresh termbase + TM matches  (F5)
>
> Same as pressing F5. Re-runs all searches for the current segment and redraws TermLens, the Match Panel, and the source-cell highlights.
>
> If the underlying database has been modified by another process (typically the Supervertaler for Trados plugin sharing the same database), the in-memory termbase index is rebuilt from disk first — so cross-process edits are picked up immediately.
>
> Auto-refresh runs in the background whenever the database file changes externally, so you rarely need to click this manually; it's here as an explicit 'do it now' trigger.


## v1.10.71 – May 17, 2026

### Fixed (🔄 TermLens refresh button + auto-refresh occasionally left pills missing for terms just added; routed through the F5 / force_refresh_matches code path which the user confirmed always works)

A user reported that after clicking the v1.10.68 🔄 TermLens refresh button (or after a v1.10.69 auto-refresh fired), pills for some terms — specifically ones that had just been added to a termbase in the same session — disappeared from the TermLens display for the current segment. The cell-select cache-hit log line on the next segment switch confirmed all 13 matches were still in the cache (`TB=13`), and the v1.10.69 snapshot showed the in-memory index had the right term count post-rebuild. **The data was correct; only the rendered widget was missing pills.** Pressing F5 (which calls `force_refresh_matches`) immediately restored every pill.

So the issue is in the display update path of `_refresh_termbase_display_for_current_segment` (called by `_post_termbase_delete_refresh`), not in the cache, the search, or the index. The lightweight refresh clears only the per-segment termbase cache and re-renders; F5's path additionally clears the translation-matches cache, re-runs the TM search, builds a richer `tb_list` that includes `target_synonyms`, and updates the Translation Results panel. Somewhere in that broader chain — most likely the FlowLayoutPanel re-layout when controls are added and cleared rapidly — the TermLens widget rebuilds cleanly where the lighter path leaves it under-refreshed.

Rather than chase the exact divergence (FlowLayout race conditions in rapid clear/add cycles are notoriously hard to pin down without a reliable reproducer), this commit just routes the post-delete refresh through the F5 path:

 - New `_force_termlens_display_redraw()` helper — calls `force_refresh_matches()` if available, falls back to the light path for defensive safety. Wrapped in try/except.
 - `_post_termbase_delete_refresh()` now calls this helper instead of `_refresh_termbase_display_for_current_segment()` directly. Same callers, same trigger surface — just a more thorough redraw at the end of the chain.

Cost: F5's path additionally re-runs the TM search (~ a few hundred ms, often instant on cached hits), so the 🔄 button now takes a hair longer. The user feedback is unambiguous that F5 works where the lightweight refresh didn't, so correctness > a few hundred milliseconds. Both manual button clicks and the auto-refresh file-watcher path go through the same helper, so cross-process edits from the Trados plugin (the v1.10.69 use case) now get the full F5 treatment too.

The user's reported sequence — add term → click 🔄 → pills vanish → press F5 → pills come back — should now collapse to: add term → click 🔄 → pills stay visible (or get redrawn cleanly), no F5 needed.


## v1.10.70 – May 17, 2026

### Fixed (Edit Termbase Entry dialog opened with empty fields after the v1.10.67 LOAD MISS branch was added with wrong indentation)

Hot on the heels of v1.10.67, a user reported that even with the new term_id-JOIN caption fix in place, right-clicking a freshly-added term and choosing Edit still opened a dialog with **empty** Source and Target fields. The diagnostic line at the bottom of their screenshot confirmed the SELECT had fetched the row correctly:

```
[TermbaseEntryEditor] LOAD term_id=93203 tb_id=13 src='pipe.' tgt='pijp'
```

So `cursor.fetchone()` returned the row, `self.term_data` was populated, the LOAD diagnostic fired — and yet the fields displayed nothing. The `tb_id=13` even showed v1.10.67's termbase-id self-correction was working (the caller passed garbage; the JOIN found the real PATENTS row at id=13 and backfilled).

**Root cause: a regression I introduced in v1.10.67.** When adding the `else:` LOAD-MISS branch, I moved the entire "Populate fields" block (the dozen `setText` / `setPlainText` / `setChecked` calls that actually fill the dialog) **inside** the new `else:` branch and **after** its `return`. That made it unreachable in **both** branches:

 - `if row:` branch — set `term_data`, fired the LOAD diagnostic, fell out of the `if` block. No `setText` calls. → Empty fields, no error.
 - `else:` branch — fired the LOAD MISS warning, scheduled `reject()`, returned. The populate code that followed was dead code after the return.

End result: every successful term load left the dialog visually empty despite the data being present in memory. The values were even sitting in `self.term_data` ready to use — they just never got pushed to the widgets. No exception thrown, no warning fired, just an empty form.

This commit moves the populate block back where it belongs (inside the `if row:` branch, before the `else`), which is what v1.10.67 was *supposed* to look like. The `else:` LOAD MISS path remains unchanged — it still shows the user a clear warning and closes the dialog when the term has genuinely gone missing from the database (the case that was the actual reason for adding the `else` in the first place).

Lesson: the `# Populate fields` block was deeply indented to begin with (sitting inside both a `try:` and an `if row:`). When grafting in a sibling `else:`, the visual indentation made it look like the populate code was a sibling-of-the-`else` rather than a child of the `if` it had always belonged to. The smoke tests (which exercise the orient helper, not the dialog's display path) couldn't catch this — only a human running the dialog would notice that pressing Edit on a real term opened a blank form. A future regression test should construct the dialog with a real db_manager + term_id and assert the source/target QLineEdit widgets contain the expected text after construction.

Apologies for the round-trip — v1.10.67 fixed the caption misalignment but broke field population in the process; v1.10.70 restores field population while keeping the caption fix and the LOAD MISS warning.


## v1.10.69 – May 17, 2026

### Added (Automatic termbase index refresh when the shared SQLite DB is modified by another process — e.g. the Supervertaler for Trados plugin — with snapshot gating to avoid spurious rebuilds)

Follow-up to the v1.10.68 manual 🔄 refresh button: the rebuild now also happens automatically, with careful gating to address the user's explicit "I don't want it misfiring or making trouble" requirement.

**How it works.** `QFileSystemWatcher` installs on the active SQLite DB file at project-load time (`_setup_termbase_db_watcher`, called from the end of the project-load chain right after `_start_termbase_batch_worker`). Whenever the file's mtime ticks, a single-shot 2-second debounce timer (re)starts. When the timer eventually fires — meaning the file has been quiet for the last 2 seconds — the handler **snapshot-gates** the rebuild:

 - Before doing anything, it queries four cheap aggregates from the database: `COUNT(*)` and `MAX(id)` from `termbases`, plus `COUNT(*)`, `MAX(id)`, and `MAX(modified_date)` from `termbase_terms`.
 - Compares the fresh tuple to the snapshot taken at the end of the last `_build_termbase_index()` call.
 - **Skips the rebuild entirely** if nothing in the termbase tables actually changed. The file mtime ticking is normal for any DB write — TM saves, project metadata, segment confirmations — and we don't want a rebuild firing 50× per minute during normal editing.
 - Only when the snapshot genuinely differs does it call `_post_termbase_delete_refresh()` (same helper the manual button uses).

**Why this is safe.** Three layers of "don't misfire":

 1. **Debounce (2 s)** collapses bursts of writes into one fire. A batch of own-INSERTs followed by activation-table updates → one timer event, not five.
 2. **Snapshot gating** filters out non-termbase DB writes (TM, project state) so only actual termbase-table changes trigger a rebuild.
 3. **Own-write integration**: every code path that modifies termbases already routes through `_post_termbase_delete_refresh` → `_build_termbase_index`, which now also re-snapshots at the end. So an own-write updates the snapshot synchronously; when the watcher debounce fires ~2 s later for that same write, the comparison sees no change and the rebuild is correctly skipped. **Zero spurious rebuilds from own-writes.**

**Cross-process behaviour** — the actual point of the feature:

 - User adds / deletes a term in the Supervertaler for Trados plugin.
 - Trados commits to the same SQLite DB → file mtime ticks → Workbench's watcher fires.
 - 2 s quiet window passes (so a batch of related Trados writes finishes first).
 - Snapshot diff detects the change → `_post_termbase_delete_refresh()` runs.
 - TermLens display updates automatically; no user action needed.

**Defensive layout.** Watcher setup is wrapped in `try/except`; failure (network drive, OneDrive, exotic filesystem, missing PyQt6 component) logs a warning and the manual 🔄 button from v1.10.68 still works as a fallback. On project switch, the old watcher is torn down and a new one installed. Windows-specific quirk handled too: SQLite WAL checkpoints sometimes drop a file from `QFileSystemWatcher`'s tracking list when the file gets rewritten; the change handler re-adds the path before restarting the debounce timer so subsequent edits still fire events.

Status-line summary on project load: `👁️  Termbase DB auto-refresh: watching <filename> (2s debounce)`. Each successful auto-refresh logs `🔄 Termbase DB changed externally — auto-refreshing index (termbases:X→Y, terms:N→M)` so you can see exactly what changed and when.


## v1.10.68 – May 17, 2026

### Added (🔄 refresh button on the TermLens widget — rebuilds the in-memory termbase index from disk, for after cross-process edits via the Supervertaler for Trados plugin)

When the same SQLite database is open in both Workbench and the Supervertaler for Trados plugin, and the user edits terms in one while the other is running, the receiving side's display goes stale. Workbench's TermLens is driven by an **in-memory** `termbase_index` (built once on project load by `_build_termbase_index` — replaces thousands of per-word DB queries with a single bulk load, see the v1.9.182 performance optimisation), so when the Trados plugin deletes a term, Workbench's TermLens keeps showing the pill until the index is rebuilt. Pressing F5 or re-clicking the segment doesn't help — those re-run the *search* against the (stale) index; they don't rebuild the index itself.

This was the root cause behind a user report of "TermLens shows deleted terms" and "right-click → Edit opens an empty dialog" (the latter happening because the dialog receives a stale `term_id` whose backing row was deleted in Trados — v1.10.67 surfaces this case with a clear warning, but doesn't fix the underlying staleness).

**The button.** A small 🔄 icon button next to the existing `A-` / `A+` font zoomer at the top-right of the TermLens widget. Clicking it:
 - Emits a new `refresh_requested` signal on `TermLensWidget`.
 - Host (`Supervertaler.py._on_termlens_refresh_requested`) handles it by calling `_post_termbase_delete_refresh()` — the same helper every delete path runs through. Drops the per-segment match cache, rebuilds the in-memory `termbase_index` from the database, refreshes the current segment's match display, refreshes the Termbases tab.
 - Brief visual feedback: button flips to `✓` and disables for 500ms; info label shows "Refreshing termbases from disk…" for the duration of the rebuild. On typical termbase sizes the rebuild is sub-second, but the feedback makes the click register even when work is instant.
 - Tooltip: *"Refresh termbases from disk — rebuilds the in-memory termbase index from the database. Use this after editing terms in another tool (e.g. the Supervertaler for Trados plugin) — without it, TermLens may keep showing deleted entries or miss newly-added ones."*

Both TermLens instances (the bottom-of-window tab and the right-side Match panel embed) share the same `TermLensWidget` class, so both get the button automatically. Both hosts wire `refresh_requested` → same handler. Cheap (sub-second) enough that users can hit it whenever they suspect drift; no harm if pressed unnecessarily.

A future iteration could auto-detect the staleness via SQLite hooks or filesystem mtime watching and rebuild without user input, but the explicit button is the cleanest first step — visible affordance, predictable cost, no chance of firing during a write the user is in the middle of.


## v1.10.67 – May 17, 2026

### Fixed (Edit Termbase Entry dialog opened empty / showed terms under wrong-language captions; root cause was a corrupted termbase_id flowing through the TermLens emit pipeline)

A user reported two confusing bugs after the v1.10.63 / v1.10.64 / v1.10.66 round of fixes, with a diagnostic log line that pinpointed the cause exactly:

```
[TermbaseEntryEditor] LOAD term_id=93195 tb_id=-1343206784 src='cable' tgt='kabel'
```

That `tb_id=-1343206784` is meaningless — the actual `termbases.id` for the PATENTS termbase is the small autoincrement `13`. Verified by inspecting the user's database: term `93195` correctly stores `termbase_id='13'` with `source_lang='en', target_lang='nl'`, and termbase row `13` is PATENTS declared `en→nl`. The data is fine. The dialog was just receiving garbage in its `termbase_id` argument — most likely a 32-bit narrowing of a Python int through some `pyqtSignal(int, int)` hop in the TermLens display pipeline.

When the v1.10.63 caption-resolution code did `SELECT source_lang, target_lang FROM termbases WHERE id = -1343206784`, it got nothing back, silently fell through to **project direction**, and ended up captioning the columns "Dutch:" / "English:" (NL→EN project) while populating them from the database's `source_term`/`target_term` in correct **termbase storage order** (en→nl). So the English word landed under a "Dutch:" caption and vice versa — exactly the v1.10.63 bug we thought we'd fixed.

**Fix.** Rather than chase whatever narrowed the int upstream, this commit makes the dialog **robust** to a wrong `termbase_id` argument. In `setup_ui`, the caption-resolution lookup now joins through `termbase_terms` using the reliable `self.term_id`:

```sql
SELECT tb.source_lang, tb.target_lang, tb.id
FROM termbase_terms t
JOIN termbases tb ON CAST(t.termbase_id AS INTEGER) = tb.id
WHERE t.id = ?
```

`self.term_id` is reliable because `load_term_data` immediately uses it to fetch the row — if it were wrong, the dialog would have empty fields, which is the *other* bug we're fixing in this commit. Whatever `termbase_id` the caller passed in, this SQL finds the actual termbase row via the foreign-key cast and returns the row's declared direction. Defensively also backfills `self.termbase_id` with the resolved value so the dialog's later save / delete operations use the correct ID.

**Bug 2.** The user's other screenshot showed the dialog opening with **empty** Dutch / English / abbreviation fields. That means `load_term_data` ran but `cursor.fetchone()` returned `None` — the `term_id` itself was wrong (likely from a stale TermLens block whose backing term has been deleted between display and click, or a wrong term_id reaching the in-memory index). Previously the dialog silently presented an empty form, leaving the user staring at an editor that couldn't possibly save back to a non-existent row. v1.10.67 now:
 - Logs a `LOAD MISS` diagnostic line with the term_id / tb_id that was passed.
 - Shows a clear warning ("This termbase entry could not be loaded — it may have been deleted from the database already. The TermLens display is probably showing a stale reference.") and tells the user to refresh the segment.
 - Closes the dialog via `QTimer.singleShot(0, self.reject)` so the caller's `.exec()` actually runs and immediately returns.

**Diagnostic.** Added a `[edit-from-termlens] received term_id=… termbase_id=…` log line on the receiving side (`_on_termlens_edit_entry`) recording both values plus their Python types. Cross-referencing this with the dialog's `LOAD` / `LOAD MISS` line and the database's actual `termbase_terms.termbase_id` for that term_id will pinpoint exactly where any future corrupted-id transmission enters the pipeline.

Net effect: even with the upstream pipeline still occasionally emitting a corrupted `termbase_id`, the dialog now captions and saves correctly, and tells you clearly when a term has gone missing rather than presenting an empty form you'll spend time wondering about.


## v1.10.66 – May 17, 2026

### Added (Termbases tab: click any column header to sort the whole termbase by that column; sort indicator stays in sync with the Sort dropdown)

Follow-up to the v1.10.65 Created column. Sorting now also works by clicking a column header — and not just the new Created column, every data column: Source Term, Target Term, Domain, Notes, Project, Client, Forbidden, Created. Click once for ascending; click again to flip to descending. The up/down arrow indicator on the header makes the current direction obvious.

Important subtlety: we deliberately do **not** call Qt's `setSortingEnabled(True)` on the table. That would tell Qt to sort the currently-loaded *page* in place — 50 or 100 rows out of potentially tens of thousands — which is the wrong answer. Instead, header clicks re-issue the underlying SQL with a new `ORDER BY` clause and reset to page 1, so the sort applies across the whole termbase regardless of pagination. (Same approach as the v1.10.65 Sort dropdown — the header click is just a second entry point into the same machinery.)

Implementation details:

 - **Column → SQL fragment map** (`_COL_SORT`): each data column has a fixed whitelist entry mapping it to the right SQL column name (with `COLLATE NOCASE` on text columns to match the dropdown's case-insensitive alphabetical sort). Column 8 (Delete button) is intentionally omitted — clicks on it are ignored.
 - **Direction toggle**: clicking the same header twice flips ASC ↔ DESC. Clicking a different header starts at ASC for text/forbidden columns and **DESC** for date columns (so a Created click immediately gives you newest-first — the case you actually want 95% of the time).
 - **Deterministic tiebreak**: every ORDER BY is suffixed with `, id ASC/DESC` so rows with identical sort values (very common for `created_date` on bulk imports) come back in a stable order across pages — no flickering when paginating.
 - **Indicator sync**: header clicks update the arrow; dropdown changes also reposition the arrow when the dropdown choice maps to a sortable column (Source A→Z, Source Z→A, Created newest, Created oldest). Modified options leave the arrow alone (no Modified column visible). On termbase load, the arrow starts on column 0 ascending to match the default sort.

Net effect: pick whichever entry point you prefer — header click for direct, dropdown for explicit named presets. Both drive the same underlying SQL and stay visually in sync.


## v1.10.65 – May 17, 2026

### Added (Termbases tab: new "Created" column + "Sort by" dropdown so you can flip the grid to "newest first" and triage terms you just added)

A user noted there was no way to find recently-added terms in the Termbases tab grid: the grid was always sorted alphabetically by source term, with no created-timestamp visible — so a batch of incorrectly-added terms couldn't be located short of remembering them by spelling and filtering one at a time.

The `termbase_terms` table has always stored a `created_date` column (populated automatically via `DEFAULT CURRENT_TIMESTAMP` since the schema was first laid down — see `database_manager.py` line 504); nothing surfaced it. This commit does:

 - **New "Created" column** (read-only) between `Forbidden` and the Delete button, rendered as `YYYY-MM-DD HH:MM` (UTC, seconds trimmed for compactness; the full timestamp shows on hover). The cell is marked non-editable so the existing `cellChanged` save handler ignores edits there, and the Delete-button column moves from index 7 → 8 to make room.
 - **New "Sort:" dropdown** next to the existing "Show:" page-size selector, with six options: Source term (A→Z, default), Source term (Z→A), **Created (newest first)**, Created (oldest first), Modified (newest first), Modified (oldest first). Changing the sort resets to page 1 so the newest rows jump straight into view.
 - **SQL changes**: the four `SELECT` queries inside `load_terms_page` now also pull `created_date` and use a configurable `ORDER BY` clause driven by the dropdown. The clauses come from a fixed `_SORT_OPTIONS` whitelist (never user-supplied), so inlining them into the SQL is safe.
 - **Tooltip on the dropdown** points at the use case directly: *"Tip: 'Created (newest first)' is the quickest way to find (and fix or delete) a batch of terms you just added."*

Backward-compatible — older Workbench versions that opened the same SQLite database have always populated `created_date`, so existing termbases populate the column immediately without any migration.


## v1.10.64 – May 17, 2026

### Fixed (TermLens kept showing deleted termbase entries until next project reload + diagnostic instrumentation for the open "synonym vanishes after save" report)

**Deleted terms hanging around in TermLens.** A user reported that deleting a term — either via the right-click "Delete Termbase Entry" on a TermLens pill, or via the red Delete button inside the Edit Termbase Entry dialog — left the term still visible in TermLens as a "match" on the current segment. Reopening the termbase confirmed the term really was gone from the database; the bug was purely in the in-memory state.

Investigation: there are **five** separate code paths that delete a term — the Termbases tab "Delete Term" button (`Supervertaler.py` line 15754), the Termbase Editor's "Delete Selected Term" (line 17289), the dialog's red Delete button (`modules/termbase_entry_editor.py` `TermbaseEntryEditor.delete_term`), the right-click "Delete Termbase Entry" on translation-results match cards (`modules/translation_results_panel.py`), and the right-click "Delete Termbase Entry" on TermLens blocks (`_on_termlens_delete_entry`). Each one did a partial refresh: typically `self.termbase_cache.clear()` plus a re-search of the current segment. None rebuilt **`self.termbase_index`** — the in-memory lookup index that `find_termbase_matches_in_source` actually reads. So the search re-ran against a stale index, hit the deleted entry, and TermLens dutifully displayed it again.

Fix: new `_post_termbase_delete_refresh()` helper on the main window that consolidates the full refresh chain: cache clear → index rebuild (`_build_termbase_index`) → segment refresh (`_refresh_termbase_display_for_current_segment`) → Termbases tab refresh (term-count badge). All five delete paths now route through it. The TermLens path replaces its old `_refresh_current_segment_matches` call. The translation-results-panel path replaces its inline cache-clear-and-re-search. The dialog's delete path also walks the parent chain to find the helper on the main window, so dialogs opened from any module surface still get a full refresh. The edit-via-dialog handler (`_on_termlens_edit_entry`) now calls the same helper after `dialog.exec()` returns Accepted — that's necessary for the delete-via-dialog path (the dialog's red Delete button calls `accept()` so the caller can't tell save from delete), and a sub-second harmless overhead for the edit-via-dialog path.

Net effect: deleting a term anywhere in the UI now removes it from TermLens immediately, no project reload needed.

**Diagnostic instrumentation for the synonym-vanishes report.** A second user report — adding a synonym to a reverse-direction term entry, closing the dialog, and finding the synonym missing (and the terms apparently reversed) on reopen — could not be reproduced from code inspection: the v1.10.63 caption fix is one-way (captions only, values flow unchanged); the save path writes `source_edit.text()` straight to `source_term` with no swap; `save_synonyms` wipes-and-reinserts the synonym table cleanly. Rather than ship a speculative fix that might mask the real cause, this commit adds session-log instrumentation to capture ground truth the next time the bug fires:

 - `load_term_data` logs `[TermbaseEntryEditor] LOAD term_id=… tb_id=… src='…' tgt='…'` so the dialog's starting state is on record.
 - `save_term` logs three lines per save: `BEFORE-DB` (what the DB held when the user clicked Save), `WRITING` (what the dialog is about to UPDATE), and `AFTER-DB` (what the DB holds immediately after commit). If `AFTER-DB` doesn't match `WRITING`, a loud `⚠️ SAVE MISMATCH` line fires — pointing at cross-process / trigger / shared-DB interference rather than a dialog bug.
 - `save_synonyms` logs `SAVE-SYNONYMS term_id=… src(N)=[…] tgt(M)=[…]` after commit so the wipe-and-reinsert pattern leaves a trail.

All log lines route through the host window's `log()` if reachable (walks the parent chain — same pattern as the v1.10.64 delete helper hook), falling back to `print()` otherwise. No behaviour change; if the symptom recurs, the session log will have the data to diagnose it.


## v1.10.63 – May 17, 2026

### Fixed (Edit Termbase Entry dialog: language labels now reflect termbase direction, not project direction, and use full names not ISO codes)

Two related cleanups in the Edit Termbase Entry dialog after the v1.10.62 fix landed:

**Direction-aware captions.** When the termbase ran opposite to the project (e.g. NL→EN project, EN→NL termbase), the dialog's two columns were captioned with the *project's* source/target language but populated from the database's `source_term`/`target_term` columns in *storage* (termbase) order. Result: the left column read e.g. `Dutch:` but contained the English term, and vice versa — making it look as if the entry was reversed even when the underlying data was correct. The Termbase Editor grid view above the dialog has always labelled its columns by the termbase's declared direction (it reads `tb.source_lang` / `tb.target_lang` directly), so the two views disagreed; only the dialog had the issue.

In edit mode, the dialog now queries the `termbases` table for the row's declared `source_lang` / `target_lang` and uses those for the column captions. The left column always shows whatever the termbase calls "source"; the right column shows whatever it calls "target". Values from `source_term` / `target_term` flow into left/right unchanged — captions and values now agree, regardless of project direction.

In **add mode** (Ctrl+Alt+T, where the dialog isn't tied to a specific termbase yet — the v1.10.62 per-termbase orient at INSERT time picks the right direction for each destination), captions stay in project direction. That's the right behaviour there too — the user enters values in the language they're thinking in (project direction), and the writer figures out per-termbase orientation when it saves.

**Full language names.** The captions previously showed ISO codes (`nl:`, `en:`) — readable but not friendly. They now expand to the human form: `Dutch:`, `English:`, etc. A small in-module ``_LANGUAGE_NAMES`` table covers the languages this dialog has historically labelled (English, Dutch, German, French, Spanish, Italian, Portuguese, Polish, Russian, Chinese, Japanese, Korean) plus their common locale variants (`en-US`, `nl-BE`, etc.); anything else falls back to title-casing the input rather than vanishing. Idempotent — passing in `"Dutch"` returns `"Dutch"`, passing in `"nl"` also returns `"Dutch"`.

The synonym section headers (`Source Synonyms (Optional)` / `Target Synonyms (Optional)`) now use the same direction-aware caption — so a reversed termbase shows `English Synonyms (Optional)` on the left and `Dutch Synonyms (Optional)` on the right, matching the term columns above. Left vs right semantics stay consistent throughout the dialog regardless of termbase direction.

Helper lives on the dialog class (`_language_display_name`) rather than calling into `Supervertaler.py._convert_language_to_code` via parent-walking, so the dialog stays standalone-importable from any module without pulling the main file.


## v1.10.62 – May 17, 2026

### Fixed (Termbase direction bug: terms added via Ctrl+Q, Ctrl+Alt+T, or Alt+Up now go into the correct columns regardless of project↔termbase direction)

A user reported that terms quick-added during a Dutch→English translation were saving "reversed" into an English→Dutch termbase: the Dutch term ended up in the column the termbase declared as English, and TermLens never surfaced them in the editor. Investigation found this was the same class of bug that the Trados side hit pre-v4.19.22 (commit `cf8d70b` — "termbase direction overhaul") and fixed there. Since Workbench and Trados share the same `termbase_terms` table layout via the shared SQLite database, the same fix shape applies.

**Bug.** Three add-term call sites in `Supervertaler.py` — `quick_add_term_pair_to_termbase` (Ctrl+Q), `add_term_pair_to_termbase` (Ctrl+Alt+T dialog), and the Alt+Up project-termbase quick-add — all passed the **project's** source/target text and lang codes straight to `TermbaseManager.add_term`, which does a raw INSERT with whatever it's given. No comparison against the destination termbase's *declared* direction (stored in `termbases.source_lang` / `termbases.target_lang`). When the two directions matched, no problem; when they were opposites (NL→EN project, EN→NL termbase), the Dutch text landed in the column the termbase declared as English, and the per-row lang codes pointed the wrong way.

This was particularly invisible because the **read side** has always been direction-aware: `_build_termbase_index` reads each termbase's declared direction and swaps source/target on lookup when the termbase is reverse to the project. So the indexer "corrected" the already-wrong write by swapping it again — meaning TermLens then searched the Dutch segment for the English text and found nothing. The user's empirical observation ("terms don't show up in TermLens") was the downstream symptom of this double-swap.

**Fix.** New `_orient_term_for_termbase(source_text, target_text, termbase)` helper that returns the four values (`source_term`, `target_term`, `source_lang_code`, `target_lang_code`) correctly oriented for the termbase's declared direction. Mirrors Trados's `TermbaseReader.InsertTermBatch` per-termbase swap logic. The decision is:
 - **Termbase has no declared languages** → trust the caller; write project lang codes (harmless, populates the columns).
 - **Termbase aligned with project** → return inputs as-is, normalised to ISO codes.
 - **Termbase declares opposite direction to project** → swap source ↔ target text AND source ↔ target lang codes so the INSERT lands them in the columns the termbase actually expects. Log the swap so it's visible in the session log.
 - **Unrelated language pair** → don't swap; just write project codes (avoids spurious flips for cross-language termbases).

All three call sites now route through this helper per destination termbase, so a single Ctrl+Q can correctly populate a mix of forward and reverse termbases without corrupting any of them. (Trados's original v4.19.22 commit notes the exact same per-termbase requirement.) The Ctrl+Alt+T flow additionally flips synonym language tags (`source` ↔ `target`) when its orient swap fires, so source/target synonym lists from the dialog land in the columns the termbase expects.

Re-uses normalisation logic already present in `_convert_language_to_code` (used by `_check_termbase_status` for the same direction-comparison), so "English" / "en" / "en-US" / "en-GB" all compare equal — matching the v4.18.39 Trados fix for the same-language-pair false-inversion case.

**Repair UI for existing bad data.** Pre-v1.10.62 terms that were already written reversed need a one-time fix-up. Added:
 - **"🔄 Reverse Source/Target"** button in the Termbase Editor's action bar.
 - **Right-click "Reverse source / target on selected"** context menu on the terms table (extended selection mode is now on, so you can select multiple rows and reverse them all in one transaction).
 - Confirmation dialog shows a preview of the first few rows about to flip.
 - The transaction swaps `source_term`, `target_term`, `source_lang`, `target_lang`, `source_abbreviation`, `target_abbreviation` in the term row, and flips every linked synonym's `language` tag (`source` ↔ `target`) — done via a temporary `__tmp__` sentinel to avoid mid-update collisions. Single `BEGIN ... COMMIT`; rolls back on any error.
 - After the swap, the in-memory termbase index is rebuilt so TermLens immediately reflects the change.

Smoke tests at `.dev/test_termbase_direction_orient.py` — 9 scenarios all pass, covering: aligned + reverse in both directions, no-direction termbases, full-name vs ISO normalisation, unrelated language pairs, same-language locale pairs, and an end-to-end SQL exercise of the reverse-direction repair transaction (column swap + synonym language flip).


## v1.10.61 – May 17, 2026

### Changed (Comment shortcut moved to Ctrl+M to match Trados/memoQ; stale QuickTrans "(Ctrl+M)" labels corrected to point at the real Ctrl+Alt+Q global hotkey)

The "add comment" shortcut shipped in v1.10.57 was bound to **Ctrl+Shift+M** because the QuickTrans menu items had `"(Ctrl+M)"` in their display text, suggesting Ctrl+M was already taken. On investigation: it wasn't. The QuickTrans menu items were just labels — no `setShortcut()` was ever wired, and QuickTrans is in fact triggered exclusively by the **global Ctrl+Alt+Q pynput hotkey** (Meta+Ctrl+Q on macOS), which works system-wide including inside Workbench. The "(Ctrl+M)" labels were stale documentation artifacts from an earlier version where Ctrl+M *was* bound in-app.

This commit:
 - Reclaims **Ctrl+M for "add comment to selected text"**, matching Trados and memoQ exactly. One shortcut for one concept across CAT-tool muscle memory.
 - Corrects the two `QAction(f"⚡ QuickTrans ({format_shortcut_for_display('Ctrl+M')})")` labels at lines 3485 and 4306 to reference the actual global hotkey: `Ctrl+Alt+Q` on Windows/Linux, `Meta+Ctrl+Q` on macOS. Both labels now read e.g. `⚡ QuickTrans (Ctrl+Alt+Q)`.
 - Updates the QuickTrans configuration page subtitle in Settings to mention only the global hotkey and notes that it works inside Workbench too (was previously listing both Ctrl+M and Ctrl+Alt+Q, suggesting two different bindings).
 - Updates the status-bar hint message ("Click into a source or target cell first, then select text and press **Ctrl+M** to add a comment") to match.
 - Updates the deprecated `Translation Results panel` stub's docstring + comment to reference Ctrl+Alt+Q instead of Ctrl+M.

Net effect for the user: **Ctrl+M = add comment** (matches Trados/memoQ). **Ctrl+Alt+Q = QuickTrans** (works system-wide, including inside Workbench — same chord whether you're in Workbench, in Trados Studio, or in any other application). One key for each operation, consistent across every context.

The help-site Comments page is updated in a separate commit to use Ctrl+M throughout.


## v1.10.60 – May 17, 2026

### Added (Phase E — DOCX export now anchors Word comments to character ranges, with run-splitting where needed; range-anchored comments feature complete)

Final instalment in the v1.10.57+ series. Phases A–D built up the data model, UI for creating anchored comments, the multi-comment list, and the in-editor visual indicator. This phase delivers the end-user payoff: **the exported `.docx` now has Word comments whose range highlight covers exactly the anchored words**, mirroring Trados / memoQ behaviour.

Implementation:

`_attach_segment_notes_as_docx_comments` rewritten to iterate `segment.comments` (one Word comment per Comment) instead of treating each segment's notes string as a single comment. Behaviour per Comment:

 - **Unanchored (segment-level)**: unchanged from v1.10.55 — Word comment anchors to the full paragraph (all runs).
 - **Target-anchored**: maps the comment's `anchor_start/end` from the segment's target-text coordinates into the matched paragraph's coordinates, then calls a new `_find_or_split_runs_for_range(para, start, end)` helper to return the runs covering exactly that character range. Runs are split via `_split_docx_run_at_offset` where the range cuts mid-run; formatting (bold, italic, font, colour, etc.) is preserved via deep-copy of the run's XML element and the `rPr` inside it.
 - **Source-anchored**: source text isn't present in the exported (target-only) DOCX, so the comment can't visually highlight a source range. Falls back to whole-paragraph anchoring with the source snippet prefixed in the comment body: `[Re: "schroef" (source)] use "screw" not "bolt"`. Reviewer reading the Word file sees the comment with the relevant source quote inline.

Run-splitting (`_split_docx_run_at_offset`) is the trickiest piece. Given a Run and a local character offset, it truncates the original run's text to `text[:offset]` and inserts a NEW run immediately after it (via lxml `addnext`) containing `text[offset:]` with the same `rPr` (deep-copied). The new run's `w:t` element has `xml:space="preserve"` so Word doesn't strip leading/trailing whitespace.

The walker (`_find_or_split_runs_for_range`) iterates the paragraph's runs, identifies runs that overlap the target range, splits them at the boundary points, and collects the resulting runs that cover exactly `para.text[start:end]`. Because each split invalidates the iteration order, the walker re-snapshots after each mutation (bounded at 64 iterations — in practice the range produces at most two splits).

Smoke tests at `.dev/test_docx_run_split.py` exercise nine scenarios — anchor aligned to run boundary, anchor cutting mid-run at start / end / both, anchor spanning multiple runs with mid-run cuts, formatting preservation (bold survives split), edge-case offset handling, and end-to-end Word comment save+reopen. All pass.

Counter changes in the log message:
 - Old: `Attached N segment note(s) as Word comments`
 - New: `Attached N segment comment(s) as Word comments (K range-anchored)` — the parenthetical only appears when at least one comment got a non-whole-paragraph anchor.

Author resolution moved to a small helper `_comment_author_and_initials()` so the inline body stays readable and the same logic is available for any future code path that needs to write a Word comment.

### Series wrap

Range-anchored comments now work end-to-end:

1. Select text in source or target → **Ctrl+Shift+M** → enter comment → it's anchored to that range. (Phase B)
2. Anchored text gets a soft amber background in the editor cell — you can see at a glance where comments are attached. (Phase C)
3. The all-comments list at the top of the Comments tab shows one entry per Comment with anchor info, snippet, author, and a right-click context menu for edit / delete. (Phase D)
4. Bottom editor auto-locks when a segment has anchored or multiple comments, preventing the legacy single-string flow from clobbering them. (Phase D)
5. Export to DOCX → Word comments anchor to exactly the right character range, with formatting preserved through any necessary run splits. (Phase E)

Backward compatibility throughout: old `.svproj` files with single-string `segment.notes` load fine (migration on read), simple one-segment-level-comment segments work exactly as before, and the legacy bottom editor is unchanged for the simple case.


## v1.10.59 – May 17, 2026

### Added (Phase C — anchored comment ranges get a background highlight in the editor cells)

Third instalment in the range-anchored comments series. Phases A (data) and B+D (UI + list) made anchored comments createable and manageable. This phase makes them **visible at a glance in the source/target cells** so the translator can see at a glance which words have comments attached, the same way Trados and memoQ show comment-anchored ranges with a coloured background.

Implementation:
 - New helper `_apply_comment_anchors_to_cell(row, segment)` walks the segment's comments[], picks out anchored ones (`is_anchored` True) by their `anchor_field` (source = col 2, target = col 3), and applies a Tailwind-amber-200 (`#FEF08A`) background to the anchored character range in the corresponding cell editor via `QTextCursor.mergeCharFormat`.
 - New `_apply_comment_anchors_to_all_cells()` iterates every visible row at once. Called at the end of `load_segments_to_grid` so the highlights appear immediately after a project load, a page-switch (pagination repaints), or any grid reload.
 - Targeted single-row refresh after Ctrl+Shift+M creates a comment, and after `_edit_comment_dialog` / context-menu delete. Avoids unnecessary work on the rest of the grid.
 - `_refresh_cell_text_for_anchors(row, segment)` provides a "cheap clear" before re-applying highlights when a comment is removed: setPlainText() with the cell's current text strips all character formats (documented Qt behaviour), and the subsequent anchor re-apply paints only the still-present anchors. Signals are blocked during the reset so the legacy textChanged handlers don't interpret it as a user edit.
 - Anchor offsets are defensively clamped to the actual cell text length on each apply. If the user has edited the target text since the comment was anchored, the highlight may land on slightly-different wording, but it won't crash or wander into invalid territory.

Interaction with existing formatting:
 - The existing `TagHighlighter` (QSyntaxHighlighter) only sets formats on tag matches, invisible-character symbols, and misspelled words via `setFormat(start, length, format)`. It leaves the format on plain-text ranges untouched, so the amber background applied by `mergeCharFormat` survives subsequent syntax-highlight passes on the same cell.
 - Order of operations: cell `setPlainText` (clears formats) → syntax highlighter runs (paints tag/spellcheck formats) → end of `load_segments_to_grid` → our `_apply_comment_anchors_to_all_cells` runs (paints amber background on plain text). Our layer goes on top.

Known limitation (will revisit in a later refinement, not this phase):
 - When the user edits the cell text, the syntax highlighter re-runs on the affected block, which doesn't disturb our background highlight per se — but the underlying character positions may now refer to wrong text. Anchor positions are *not* live-tracked through edits; they're stored as absolute offsets. A future enhancement could either auto-invalidate anchors when their range is modified, or use Qt's `QTextCursor` position tracking to follow edits.

Still coming:
 - Phase E: DOCX export — Word comment anchors to the specific character range (with run-splitting where the anchor cuts mid-run) instead of the whole paragraph. This is the end-user payoff in the exported file.


## v1.10.58 – May 17, 2026

### Added (Phase B + D — Ctrl+Shift+M to add anchored comments; all-comments list rebuilt for multiple per segment with edit/delete)

Second instalment in the v1.10.57+ range-anchored-comments work. Phase A (v1.10.57) shipped the data model. This commit makes it user-visible.

**Ctrl+Shift+M — add a comment anchored to selected text.** New keyboard shortcut. Click into a source or target cell, select the text the comment is about (e.g. "schroef"), press Ctrl+Shift+M, type the comment in the dialog. The Comment is stored with `anchor_field='source'|'target'` and `anchor_start/end` matching the selection. If there's no selection (just a cursor), the Comment is segment-level (no anchor). Mirrors the Trados / memoQ workflow — their Ctrl+M was the natural pick but it's already taken in Workbench by QuickTrans, hence Ctrl+Shift+M.

The dialog shows the segment id + whether the comment is anchored or segment-level, a snippet of the anchored text (truncated to 140 chars with an ellipsis if longer, properly HTML-escaped) for confirmation, and a multi-line text input for the comment body. Author defaults to the Translator Name from Settings → User Identity, falling back to the system username — same source as Word-comment export, SDLXLIFF comments, and TMX writes.

**All-comments list rebuilt for multiple comments per segment.** The list at the top of the Comments → Segment sub-tab now shows **one entry per Comment** instead of one entry per segment. A segment with three comments shows three entries, each with its own anchor snippet (when anchored), author, timestamp, and a right-click context menu for **Edit comment…** and **Delete comment** (the latter with a confirmation prompt). Edit opens a small dialog pre-populated with the existing comment text; saving with empty text deletes the comment.

Each entry's header now reads `Segment #N  ⚓ source` (or `⚓ target`) when the comment is anchored, so you can see at a glance which comments are anchored vs segment-level. Clicking the header still jumps the grid to that segment (cross-page-aware, unchanged from v1.10.56).

**Bottom editor auto-lock for "complex" segments.** The bottom text editor in the Segment sub-tab was designed for the simple one-segment-level-comment case (writes to `segment.notes` via the `replace_all_comments_with_text` bridge, which collapses multiple comments into one). When the currently-selected segment has more than one comment OR any anchored comment, the editor goes read-only with a placeholder telling the user to manage individual comments via the list above. This prevents silent clobbering. Simple segments (zero or one segment-level comment) keep the existing edit-as-you-type behaviour exactly as before.

`_update_bottom_notes_for_segment` was rewritten to read from `segment.comments` directly: shows the single comment's text if there's exactly one unanchored comment, shows empty otherwise. The legacy `segment.notes` path is kept as a fallback for any edge case where the comments[] list is absent (shouldn't happen after v1.10.57 migration but defensive).

**Still coming in this version family:**
 - Phase C: visual indicator (coloured underline / highlight) on anchored text inside the editor cells, so you can see where comments are attached without opening the list.
 - Phase E: DOCX export — Word comment anchors to the specific character range (run-splitting where needed) instead of the whole paragraph. This is the visible end-user payoff in the exported file.


## v1.10.57 – May 17, 2026

### Added (Foundational data model for multiple range-anchored comments per segment)

This is the first commit in the multi-phase work to bring Workbench up to feature parity with Trados / memoQ on comment anchoring (the ability to select a few words in a segment and attach a comment to that specific range, which renders in Word export as a yellow comment bubble anchored to those exact words rather than to the whole paragraph). Phase A is the foundation — data model + migration only, no visible UI changes.

- New `Comment` dataclass with fields: `id` (auto-uuid), `text`, `author`, `created` (auto-ISO-timestamp), `anchor_field` (`"source"`, `"target"`, or `""` for segment-level), `anchor_start`, `anchor_end` (character offsets, Python-slice semantics — start inclusive, end exclusive). Property `is_anchored` returns True iff `anchor_field` is set and `anchor_end > anchor_start`.
- `Segment` gains a `comments: List[Comment]` field alongside the existing `notes: str`. The notes field is now considered the *legacy single-string mirror* of comments[]; comments[] is the authoritative source going forward. The two are kept in sync automatically: `__post_init__` migrates old notes-only segments to one Comment, and regenerates notes from comments on every load.
- Legacy `.svproj` migration: any segment whose project file has `notes` but no `comments` field gets migrated on load to a single segment-level Comment carrying the original notes text. The legacy `⚠️ PROOFREAD:` migration from earlier versions still runs before this; cleaned-up user notes after that step land in the migrated Comment.
- Five new helper methods on `Segment`:
  - `add_comment(text, author='', anchor_field='', anchor_start=0, anchor_end=0)` → returns the new Comment, keeps notes in sync.
  - `update_comment(comment_id, text)` → True if found.
  - `remove_comment(comment_id)` → True if removed.
  - `get_comment(comment_id)` → Comment or None.
  - `replace_all_comments_with_text(text, author='')` → the legacy bridge used by `_on_bottom_notes_changed` (the existing single-string editor). Preserves an existing comment's id when editing in place (no churn on metadata); replaces the list with a single new Comment otherwise; clears the list if text is empty.
- `_on_bottom_notes_changed` (the editor's textChanged handler) now routes through `replace_all_comments_with_text` instead of writing directly to `seg.notes`, so a comment edited through the legacy editor keeps the same id and the comments[] list stays consistent.
- Serialisation: `to_dict` works unchanged (`asdict` recurses into nested dataclasses). `from_dict` rehydrates the list of dicts back into `Comment` objects via `Comment.from_dict`.
- Smoke tests at `.dev/test_comment_dataclass.py` exercise nine scenarios (new segment, legacy migration, add/update/remove/get, in-place edit preserves id, multi → one collapse, empty-string clear, round-trip, PROOFREAD migration unaffected). All pass.

Coming in subsequent commits (same version family, separate commits for git readability):
 - **Phase B**: Editor selection capture — Ctrl+Shift+M creates a new Comment anchored to the currently selected text, via a small dialog. (Ctrl+M was the natural shortcut but it's taken by QuickTrans; Ctrl+Shift+M is free and reads as "specialised M action.")
 - **Phase C**: Visual indicator — anchored text gets a coloured underline in the editor cells so you can see at a glance where comments are attached.
 - **Phase D**: All-comments list rebuilt for multiple comments per segment, with anchored-snippet display in each entry.
 - **Phase E**: DOCX export — Word comment anchors to the specific character range (run-splitting if needed) instead of the whole paragraph. This is the visible end-user payoff.


## v1.10.56 – May 17, 2026

### Changed (Comments overhaul: "Notes" terminology retired, two tabs merged into one "💬 Comments" tab with sub-tabs, plus an all-project comments list with click-to-jump)

Three coupled changes shipped together to fix three related UX paper cuts on the Comments side of the right panel.

**Terminology: "Notes" → "Comments"** — every user-visible "note(s)" string referring to segment-level or proofreading-level annotations is now "comment(s)". Tab labels, tooltips, status-bar messages, dialog texts, placeholder text in the editor — all renamed. Two surfaces are deliberately *not* renamed: termbase columns (a "Notes" column on terminology is a different concept and stays "Notes"), and the Scratchpad tab (project-level private notes, distinct from segment comments). Internal Python data-field names (`segment.notes`, `segment.proofreading_notes`) are unchanged so existing `.svproj` files load without migration. The help site is being updated in a separate commit to use "comments" wording everywhere too.

**Tab restructure: separate "📝 Segment note" and "✅ Proofreading note" tabs collapsed into one "💬 Comments" parent tab with two sub-tabs.** Previously the right panel had two adjacent tabs for related-but-different annotations (one user-authored, one AI-generated). Merging into a parent tab with sub-tabs keeps them visually grouped and frees up a top-level tab slot. The sub-tab widget uses `setDocumentMode(True)` so the inner tab bar reads as subordinate to the parent. The Ctrl+N keyboard shortcut (focus comments editor) navigates the new two-level structure: parent tab first, then sub-tab, then focus the editor.

**Segment sub-tab: split view with all-project comments list on top, current-segment editor on bottom.** Previously the Segment-note tab only showed the editor for the currently-selected segment. The new layout shows every segment in the project that has a comment, in document order, with clickable "Segment #N" headers. Clicking a header jumps the grid to that segment (cross-page-aware — switches pagination first if the target segment is on a different page). The editor for the current segment is still there, just relocated to the bottom half of the sub-tab; users can drag the splitter to give more or less space to either half.

New helper `_navigate_to_segment_by_id(segment_id)` implements the cross-page navigation: it looks up the segment's index in the *full* project segment list (not the visible-rows list), computes which page it's on, calls `go_to_page()` to switch, then selects the row in the now-rebuilt table. The existing `_navigate_to_segment_in_grid` was page-blind (iterated `self.table.rowCount()` which only sees the current page) and only worked when the target was already visible.

The all-comments list rebuilds via `_refresh_segment_comments_list()`. Called from `_on_bottom_notes_changed` (so live edits appear/disappear in the list immediately) and from both project-load paths (new-project creation and existing-project open) alongside the scratchpad refresh. Cheap O(N) walk over `current_project.segments` where N is the number of segments; the rebuild is well within budget for typical projects (a few thousand segments at most, of which a few dozen typically have comments).

Pattern is inspired by the AI Proofreader Reports tab in Supervertaler for Trados — same "list of findings with clickable navigation" UX.


## v1.10.55 – May 17, 2026

### Changed (Segment-note Word comments are now attributed to the configured Translator Name)

- v1.10.54 added segment-notes-as-Word-comments export but hardcoded the comment author as "Supervertaler" / initials "SV". A user (rightly) pointed out that Workbench already has a Settings → User Identity → Translator Name field that's used for SDLXLIFF comments, Trados return packages, TMX writes, and other output — so the new Word comments should be attributed to whoever's actually translating, not the app.
- `_attach_segment_notes_as_docx_comments` now calls the existing `get_translator_name()` helper (which reads `translator_name` from general settings and falls back to the system username if the field is empty). Initials are derived from the resolved name: multi-word names ("Michael Beijer") take the first letter of each word ("MB"); single-word names ("mbeijer") take the first two characters uppercased ("MB"). Capped at 4 characters to fit Word's narrow initials column.
- No new setting needed; the same name that flows into every other "translator identity" surface in the app now flows into Word comments too.


## v1.10.54 – May 17, 2026

### Added (Segment notes are now exported to the final DOCX as Word comments)

- A user reported that they'd authored a few notes on individual segments in the right-panel "📝 Segment note" tab during a translation session, then exported the project to DOCX and was surprised the notes didn't appear in the Word file. They were right to be surprised: segment notes were stored on the segment object and exported as a column for bilingual-table formats (memoQ, CafeTran, etc.) but the monolingual DOCX export path (target-only / Okapi merge) had no awareness of them. The notes lived on in the `.svproj` but never reached the deliverable.
- New helper method `_attach_segment_notes_as_docx_comments(docx_path, segments)` post-processes a saved DOCX: walks every paragraph in document order, finds the first unmatched segment whose target text is a substring of the paragraph's text, and attaches a Word comment to that paragraph using python-docx's `Document.add_comment(runs=..., text=..., author=..., initials=...)` API (added in python-docx 1.0.0; verified working on the installed 1.2.0). Comments are anchored to all runs in the paragraph so the comment range covers the full sentence in Word.
- Wired into both DOCX export paths:
  - `_try_okapi_merge_export` — after a successful Okapi merge, if the output is `.docx` (Okapi can also output IDML / HTML / XLIFF / PO / XLSX / PPTX, which python-docx can't open, so the post-process is skipped for those).
  - `export_target_only_docx` — after the python-docx fallback path saves the file, for both the "copy original as template" sub-branch and the "no original document, build from scratch" sub-branch.
- Matching strategy: by-substring against the target text, first unmatched paragraph in document order wins. This handles the common case correctly. Failures (segment target not found in any paragraph; paragraph has no runs to anchor to; add_comment throws for whatever reason) are logged but never abort the export — the underlying DOCX is already saved by the time the post-processor runs, so this is purely additive enhancement. Worst case the user gets the export they would have had before, plus a log line explaining why N comments couldn't be attached.
- Author defaults to "Supervertaler" with initials "SV". Configurable from a setting is a possible future enhancement if anyone asks.
- Notes-already-existing-in-the-source-DOCX (Word comment bubbles the original author put on the file) are independent of this and stay subject to whatever Okapi's merge does with them (usually preserved – they're outside the translatable text flow). This change only adds NEW comments derived from Supervertaler segment notes.
- The previously-discussed `⟦TC: …⟧` Translator's-Comment markers continue to sit inline in target text. Extracting THOSE into Word comments (and stripping the markers from visible text) is a separate piece of work, currently parked – see the related GitHub issue on the Trados side. The current change ships the simpler segment-notes path; the TC pipeline can be added later as another call into the same helper.


## v1.10.53 – May 17, 2026

### Changed (File → Import and File → Export each collapsed from two menu items to one)

- The File → Import menu had two top-level items: "Monolingual Document (DOCX)…" (Ctrl+O) and "Other format via Okapi (IDML, HTML, XLIFF, PO, XLSX, PPTX)…". The split implied the two paths used different engines – DOCX with python-docx, the rest with Okapi – but that hasn't been true since the engine-choice dialog was removed. Both menu items called code that delegated to the same `import_docx_from_path`, which in turn called `_ensure_okapi_sidecar` and then the Okapi extractor. A code comment at line 27669 of Supervertaler.py says it explicitly: *"DOCX import always goes through the Okapi sidecar – the old engine-choice dialog is gone."* The two-item split was a stale UX leftover that genuinely confused users.
- Replaced both items with a single **Import Document…** entry (still on Ctrl+O). The file picker shows an "All supported formats" filter by default (DOCX + IDML + HTML + XLIFF + PO + XLSX + PPTX), with per-format filters available for users who want them. The language-pair dialog and the rest of the import pipeline are unchanged. New helper method `import_document` lives at modules/help-system-adjacent position in `Supervertaler.py`; the old `import_docx` and `import_okapi_format` methods are left in place but are no longer reachable from the menu (kept for any future programmatic use, harmless dead code otherwise – will get a separate cleanup commit later).
- The File → Export menu had a similar two-item split at the top: "Target Only (DOCX)…" and "Original format via Okapi (IDML, HTML, XLIFF, PO, XLSX, PPTX)…". This one was slightly less of a fiction – the two methods did genuinely use slightly different code paths – but on a project imported via Okapi (which is now every imported project) the "Target Only (DOCX)" path ALSO tries the Okapi merge first and only falls back to python-docx if the merge fails. So the user-visible behaviour was mostly the same; the labels just made it look like two unrelated operations.
- Replaced both export items with a single **Export Translated Document…** entry. A small dispatcher (`export_document`) picks the right back-end: DOCX-origin projects (and projects with no known origin file) go through the DOCX exporter (Okapi merge primary, python-docx fallback); non-DOCX Okapi-origin projects (IDML, HTML, XLIFF, PO, XLSX, PPTX) go through the Okapi-merge-only exporter, which outputs in the original file's format. Output extension defaults to the original's. Same engine, same round-trip quality, fewer menu items.
- The rest of the Import and Export menus (Text / Markdown, Folder multi-file, memoQ, CafeTran, Trados Studio, Phrase, Déjà Vu, Bilingual tables, TMX exports) are unchanged. Those are genuinely different operations and stay as separate entries.
- The Help URL for Supported file formats (`HelpTopics.IMPORT_FORMATS`) continues to point at the help site, accessible from the "?" badge in the import dialog and from the "Supported file formats (online help)…" entry at the foot of the Import menu.


## v1.10.52 – May 17, 2026

### Fixed (the "⇄ reversed" tooltip rendered as unreadable dark-on-dark on Windows 11)

- The chip from v1.10.50 and the flag plumbing from v1.10.51 worked correctly – the chip showed and the tooltip box appeared on hover – but on Windows 11 the tooltip text rendered as near-invisible (dark text on a near-black background). Theme manager already sets light tooltip colours via both `QToolTip` stylesheet and the `ToolTipBase`/`ToolTipText` palette roles, but Windows-11 dark-mode tooltip rendering ignores both: it short-circuits Qt's QToolTip painting and uses the OS-level dark tooltip palette directly, leaving the application no way to override colours through normal stylesheet/palette channels.
- Workaround: wrap the tooltip text in inline HTML with explicit `background-color` and `color` style attributes. Qt always honours inline HTML in the tooltip text itself regardless of the OS-level rendering path, because the colours are baked into the rich-text document the tooltip widget paints from. The wrap is a `<div style='background-color:#1f2937; color:#f9fafb; padding:6px 8px; max-width:360px;'>...</div>` – dark slate background, near-white text, a small padding for breathing room, and a width cap so the box wraps at a comfortable line length instead of stretching across the screen.
- Applied at both tooltip sites: `_update_match_panel_tm_display` (single-segment Match Panel TM Target label) and `_update_compare_panel_tm_display` (Compare Panel nav + target labels). The tooltip is still cleared (`setToolTip("")`) when navigating to a non-reversed match so the box doesn't stick around.
- This is purely a styling fix; the chip itself, the underlying bidirectional TM support (v1.10.49), the flag plumbing (v1.10.50–v1.10.51), and all match data continue to work exactly as before. Users on macOS / Linux / older Windows / light-mode-on-Windows-11 will see the same readable tooltip they already saw; users on Windows-11 dark mode now also see readable text instead of dark-on-dark.


## v1.10.51 – May 17, 2026

### Fixed (the "⇄ reversed" chip from v1.10.50 didn't actually appear because four upstream layers were stripping the flag before it reached the renderer)

- After v1.10.50 shipped, a real-world test showed that batch-translating against a reversed-direction TM brought back matches correctly (the v1.10.49 bidirectional fix works) but the "⇄ reversed" chip from v1.10.50 never appeared in the Match Panel. The data layer was setting `reverse_match: True`, but four separate intermediate code paths were stripping the flag before it reached the renderer that drew the chip.
- The Match Panel's "fresh single-segment lookup" path runs through a background `TMSearchWorker` (a `QThread` that owns its own SQLite connection), then through `_on_tm_search_results` (which converts dicts into `TranslationMatch` objects), then through a panel-bridge dict construction (which converts the `TranslationMatch` objects back into dicts for the panel). Each of those three steps reconstructed the dict from scratch and only copied a small allowlist of fields – `tm_name`, `tm_id`, `match_pct`, `source`, `target` – dropping everything else including `reverse_match`. The "cache hit" path (line ~38875) does its own panel-bridge reconstruction and was also stripping the flag.
- Additionally, the two convenience wrappers `TranslationMemory.search_all` and `DatabaseManager.search_all` both rebuilt result dicts to a fixed shape that didn't include the flag. So _any_ caller using these wrappers (not just the Match Panel path) had no way to know a match came from a reversed-direction lookup.
- All four strippers are now patched to forward `reverse_match` along:
  - `TMSearchWorker.run()` (Supervertaler.py ~6069 + ~6098): exact + fuzzy result-dict construction.
  - `_on_tm_search_results` (~53060): puts `reverse_match` on the `TranslationMatch.metadata` dict (alongside the existing `direction: 'reverse' | 'primary'` field, which has been rewired to use the explicit flag instead of a fragile `"Reverse" in tm_name` substring heuristic).
  - The panel-bridge dict at the same handler (~53128) and the cache-hit re-render at ~38875: both now read `tm.metadata.get('reverse_match', False)` and include it in the panel dict.
  - `TranslationMemory.search_all` (modules/translation_memory.py ~227, ~256): exact + fuzzy result formatter.
  - `DatabaseManager.search_all` (modules/database_manager.py ~1780, ~1798): exact + fuzzy result formatter.
- One more side-effect: there's a legacy bidirectional-search block at Supervertaler.py ~40790 that pre-dates the db-layer bidirectional support. It runs its own custom SQL with a degraded score (95% instead of 100%) and tags results with a `"(Reverse)"` suffix on the TM name. The block is functionally redundant with the new db-layer support but still wired, so it now also emits `reverse_match: True` for chip consistency. A separate cleanup commit can drop the block once we're confident nothing depends on the degraded-score behaviour.
- End-to-end smoke path that should now work: user has an `en→nl` TM attached to an `nl→en` project → batch translate or single-segment navigation → TM Target metadata label reads `tm_name (100%) ⇄ reversed` instead of just `tm_name (100%)` → hovering the label surfaces the explanatory tooltip.


## v1.10.50 – May 17, 2026

### Added (Match Panel and Compare Panel now show a "⇄ reversed" chip when a TM match came from a reversed-direction TM)

- v1.10.49 added bidirectional TM lookup to the batch path so that an `en→nl` TM attached to an `nl→en` project serves matches correctly. The match data carries a `reverse_match: True` flag end-to-end, but the UI ignored it – users got the matches but had no way to tell at a glance that a particular hit came via the reverse-direction fallback rather than a forward match. That's worth knowing: it tells the user the TM is mis-oriented for this project (so they may want to re-import the TMX into a correctly-oriented TM at some point), and it explains why the source displayed in the TM Source pane was originally stored as the TM's target column.
- The single-segment **Match Panel**'s TM Target label now appends a small amber chip `⇄ reversed` after the TM name/percentage when `reverse_match` is True (e.g. the label reads `scratch (100%) ⇄ reversed` instead of just `scratch (100%)`). The chip uses a muted amber colour (`#b45309` text on `rgba(245,158,11,0.18)` background, 6px pill radius, 8px font) so it reads as informational, not alarming. Hovering the label surfaces a tooltip: *"This match comes from a TM whose source/target languages are the inverse of this project. The match is from the TM's target column; Workbench has swapped fields so it displays normally."*
- The **Compare Panel** (used by the batch translate flow and the side-by-side comparison view) gets the same chip in both its nav label and target label, with the same tooltip. The chip is conditionally inserted so navigating between matches updates it correctly when some matches are reversed and others aren't.
- The flag now flows from the database layer (`reverse_match: True` set by the bidirectional fallback in `get_exact_match`, `get_exact_matches_batch`, `search_fuzzy_matches`, `search_fuzzy_matches_batch`) through the translation_memory wrappers, through the per-segment `_batch_tm_match` cache (all three storage sites in `Supervertaler.py` updated to include the flag), through the display-dict construction in the navigation handler, and into the renderer. No path is dropping the flag any more.
- The renderer covers both code-path conventions for match data – dict-shaped matches read `match.get('reverse_match', False)` directly, and TranslationMatch objects read `md.get('reverse_match', False)` from the metadata dict. Defaults to `False` everywhere so existing forward matches render unchanged.
- Implementation note: the chip is added as inline HTML inside the existing rich-text label rather than as a separate Qt widget, so layout reflow and theme colours continue to work without dedicated widget plumbing. The tooltip is set on the label itself so the entire metadata line acts as a hover target.

- A user reported that single-segment navigation correctly auto-inserted 100% TM matches from a TM they'd attached, but running Batch Translate against the same TM produced zero hits. After investigation, the cause turned out to be a feature-parity gap between the single-segment and batch TM lookup code paths, not a "TM is one-directional" limitation as initially suspected.
- The TM database layer (`modules/database_manager.py`) supports bidirectional matching: when a project is `nl→en` and an attached TM is the inverse `en→nl`, the lookup transparently tries the forward direction first and, on miss, falls back to a reverse-direction search against `target_text` with swapped language filters, then swaps the source/target in the result so downstream consumers see a normal forward match (tagged `reverse_match: True`). This had been implemented for the single-segment lookups (`get_exact_match` and `search_fuzzy_matches`) but had never been added to the batch counterparts (`get_exact_matches_batch` and `search_fuzzy_matches_batch`). Users with a reversed-direction TM saw single-segment auto-insert work fine but every batch operation come back empty.
- `get_exact_matches_batch` now accepts a `bidirectional` parameter (defaults to `True`). After the forward hash-based sweep completes, any sources that didn't match get a second pass against `target_text` with the language filters swapped (TM source_lang = our target_lang, TM target_lang = our source_lang). Matched rows are swapped to look like normal forward results and tagged `reverse_match: True`. The implementation mirrors the single-segment version's literal-equality semantics (no hash variants in reverse) for parity – the trade-off being that the reverse path is slightly less forgiving of whitespace/tag differences than the forward path, identical to the existing single-segment behaviour.
- `search_fuzzy_matches_batch` now also accepts `bidirectional` (default `True`) and loads reverse-direction candidates as a second batch alongside forward candidates. Each reverse candidate is pre-swapped (the TM's `target_text` is what we score against, and `source_text` is what we return), and tagged with `reverse_match: True`. Phase B (the scorer) is unchanged because reverse candidates are pre-swapped to look like forward candidates – fuzzy scoring, threshold filtering, and best-match selection all work without branching.
- The wrapper methods in `modules/translation_memory.py` (`get_exact_matches_batch`, `search_all_batch`) automatically pick up the new behaviour because they rely on the default `bidirectional=True`. The `reverse_match` flag is now also propagated through `search_all_batch`'s formatted output so future UI work (e.g. a "reversed TM" chip in the Match Panel) has the data available.
- All four call-sites of these methods in `Supervertaler.py` (TM-only batch translate, TM pre-check before AI batch translation, TM-only mode inside the AI batch flow, and the dedicated pre-translate-from-TM dialog) go through the wrappers, so the fix lights up everywhere automatically. No code changes were needed in `Supervertaler.py` itself.
- Practical effect: users no longer need to re-import a TMX into a correctly-oriented TM as a workaround. An `en→nl` TM attached to an `nl→en` project now serves matches in both single-segment and batch flows identically. The bug was a five-month-old gap; bidirectional support for the single-segment paths was added when the TM database was first built but the batch optimisations that came later never inherited the same logic.


## v1.10.48 – May 16, 2026

### Changed (Text Conversions are now file-backed and user-extensible)

- The Clipboard Manager's "Text Conversions" menu was a hardcoded list of eleven `str → str` transformations inside `clipboard_manager_widget.py`. Adding a new conversion (e.g. a client-specific wrap, a regex cleanup recipe, a custom strip set) meant editing Python source. This was the one entry in the Menu column that didn't live on disk – every other Menu source (Personal Snippets, Special Characters, QuickLauncher Prompts) is a folder of `.md` files the user can edit freely.
- New module `modules/text_conversion_library.py` mirrors `snippet_library.py`'s pattern: one `.md` file per conversion, YAML frontmatter declares what it does, folder structure is for organisation. Lives at `<user_data>/text_conversion_library/` and is seeded with eleven defaults on first launch matching the previously-hardcoded list one for one.
- Four supported `type` values cover every existing conversion plus most user use cases without an arbitrary-code escape hatch:
  - `case` — `mode` is one of `upper` / `lower` / `title` / `sentence` / `swap` / `camel` / `snake` / `kebab`.
  - `wrap` — `prefix` and `suffix` glue around the clipboard text.
  - `regex_replace` — `find` + `replace`, with `regex` (default `true`) and `case_sensitive` (default `true`) flags.
  - `strip_chars` — `chars` lists every character to remove.
- Common metadata: optional `label` (defaults to filename stem), optional `category` (defaults to parent folder name), optional `enabled` (defaults to `true`; set `false` to hide without deleting).
- Parsed with `yaml.safe_load`, so escape sequences inside double-quoted strings work properly – `chars: "\\u00AD\\u00A0"` strips both soft hyphens and non-breaking spaces in one go, `find: "—"` matches an em dash literally.
- The Clipboard Manager's `_populate_text_conversions` now constructs a `TextConversionLibrary`, calls `ensure_defaults(DEFAULT_CONVERSIONS)` to seed missing defaults, then `load_all()` to scan the folder. The existing 🔄 Refresh button picks up disk edits for free – no new reload plumbing.
- Broken conversion files (invalid `type`, bad regex, missing required field) are silently skipped and logged. The clipboard flow never breaks on a typo – the conversion just no-ops and returns the input text unchanged until the user fixes the YAML and hits Refresh.
- Each shipped default exercises one of the four types so the files serve as worked examples for users adding their own. The Soft hyphens default also includes a `notes:` body with a one-line hint for extending it.
- No Python escape hatch in this first cut. If a real user need surfaces that can't be expressed in the four declarative types, a `python_file` type can be added later. Deferred to avoid arbitrary-code-execution risk in user-data files.


## v1.10.47 – May 16, 2026

### Changed (Clipboard Manager: Refresh button + tab renamed for clarity)

- The Clipboard Manager's Menu column lists items from two file-backed sources: Snippets (`.md` files under `<user_data>/snippet_library/`, including the default "Special Characters" and "Personal Snippets" folders plus any user-created folders) and QuickLauncher Prompts (from the shared `prompt_library/` folder). When the user edited those files directly on disk (rename a snippet, change a snippet body, add a new snippet, edit a QuickLauncher prompt), the changes were not reflected in the Menu lists – the snippet library was re-scanned on each rebuild but the unified prompt library uses a cached `self.prompts` dict that is only refreshed by explicit reload calls, and there was no UI affordance to trigger a rebuild at all.
- A 🔄 Refresh button has been added to the Menu column header, right-aligned next to the "📑 Menu" label. Clicking it: (a) calls `UnifiedPromptLibrary.load_all_prompts()` to reload the prompt library from disk, (b) constructs a fresh `SnippetLibrary` and re-scans `<user_data>/snippet_library/`, (c) rebuilds the entire action tree from the freshly-loaded data. The button's tooltip explains the use case.
- The main tab "📋 Clipboard" has been renamed to "📋 Clipboard Manager" to match the widget's actual scope – it has been more than a clipboard history for several versions (Snippets, Text Conversions, QuickLauncher Prompts, plus the clipboard history columns), and the shorter "Clipboard" label undersold what the tab does. The tab index attribute (`self.clipboard_tab_index`) is unchanged, so all keyboard-jump shortcuts and tab-index references continue to work.


## v1.10.46 – May 16, 2026

### Changed (AutoPrompt-generated prompts now embed the Translator's Comment methodology by default)

- Real translation work routinely runs into mechanical defects in the source: typos, broken words across whitespace, hanging mid-sentence breaks, doubled spaces, stray punctuation, reference-numeral mismatches that are unambiguous in context, missing diacritics. The cleanest workflow for handling these – established in a gold-standard project prompt and validated against real Trados Studio translation work – is for the translator AI to silently correct obvious defects and append a single concise translator's comment at the end of the segment in the form `⟦TC: short description of the fix⟧` (using the mathematical white square brackets U+27E6 / U+27E7, which do not occur in source documents and so are safe as out-of-band markers extractable in post-processing).
- The Workbench AutoPrompt meta-prompt now contains an explicit TRANSLATOR-COMMENT METHODOLOGY block that requires the LLM to embed this silent-correction-with-flagged-comment methodology in every prompt it generates, regardless of source language or domain. The block specifies the exact bracket characters, the rules for one-marker-per-segment, the no-empty-markers rule, the inline `[bracketed text]` convention for translator-supplied words, the comment-body style guidance (5–20 words, noun-phrase, no first-person), the placement (final content of the segment, single space separator), and a list of hard exclusions (numerical values, dates, dosages, claim language, statutory references, headings, identifiers, ambiguous cases).
- The generated prompt is also required to include: (a) the silent-correction methodology in its TRANSLATION MANDATE section, with defect categories adapted to the actual source language (e.g. Dutch -d/-t typos, German missing umlauts, French accent slips, Spanish/Italian conjugation typos), (b) a dedicated TRANSLATOR COMMENT FORMAT section near the end with the exact spec and 4–6 example comment bodies adapted to the language and domain, (c) a check in PREFLIGHT SELF-CHECK and POST-TRANSLATION INTEGRITY that every silent correction has its corresponding marker and no segment without corrections has a marker, (d) a note in OUTPUT FORMAT that ⟦ and ⟧ are the deliberate out-of-band comment delimiter and the sole exception to the "ASCII output only" rule.
- The methodology is always-on for every AutoPrompt-generated prompt in every domain – users who prefer not to use it can edit the generated prompt to remove the TC sections. Per-project opt-out via a UI toggle is a possible future enhancement.
- The markers currently appear inline in the target text (as they did in the original gold-standard project's test runs). Extraction into Workbench segment comments or downstream Trados Studio comments is not yet wired up; this is a separate follow-up. The current change ships the methodology in the prompts so the translator AI produces the markers reliably, ready for whichever extraction pipeline gets built next.
- Shipped in parallel with Supervertaler for Trados v4.19.111 which makes the same change to its C# `PromptGenerator` so both products' AutoPrompts agree on the methodology and the bracket format.


## v1.10.45 – May 16, 2026

### Changed (AutoPrompt source-aware passes are now domain-agnostic and language-agnostic)

- v1.10.44 added five source-aware pre-generation passes (collision detection, defect detection, cascade extraction, etc.) but the heavy lifting was hardcoded for Dutch source language and mechanical / patent terminology. A medical translator working from German, a marketing translator working from French, or a legal translator working from Spanish got nothing useful out of those passes – they silently no-op'd because none of their source-language patterns were in the hardcoded helper lists. Workbench users translate across many language pairs and domains; baking one specific case into the helpers was the wrong default.
- The meta-prompt now contains an explicit SOURCE-AWARE ANALYSIS REQUIRED block that instructs the LLM to perform the three scans itself (collision detection, defect detection, cascade extraction) using patterns appropriate for the actual source language and detected domain. The instruction includes concrete examples spanning Dutch, German, French, Spanish, Italian, and Portuguese for each scan type – the LLM picks the right patterns based on the detected source language. Domain coverage spans mechanical / patent, medical, legal, marketing, financial, and technical, with examples of typical collision patterns for each.
- The hardcoded Dutch helpers from v1.10.44 are kept but reframed as `=== PRE-FLAGGED COLLISION HINTS (Dutch mechanical / patent helper) ===` etc. with an explicit "ALSO perform your own scan" instruction. The Dutch user still gets the high-signal hints; every other user gets the LLM-driven scan working against their own source.
- The `=== PATENT MARKERS DETECTED ===` block now explicitly notes that the patent override is patent-specific and that other domains rely on the detected domain plus template guidance, so the LLM doesn't infer that the absence of patent markers means the domain detection was uncertain.
- Net effect for a non-Dutch, non-patent user (e.g. French→English marketing, German→English medical, Spanish→English legal): they now get the same quality lift the brief asked for – real collision detection, real defect extraction, real cascade preservation – but driven by the LLM working against their own source instead of by hardcoded patterns that don't match their work.
- No version of the pipeline guesses at hypothetical findings: every scan is explicitly instructed to OMIT its corresponding subsection from the generated prompt when nothing real is found, rather than padding it with hypothetical examples.


## v1.10.44 – May 16, 2026

### Changed (AutoPrompt now generates source-aware, anchor-rich prompts)

- AutoPrompt previously produced prompts that read as if generated from the document title + filename + a generic domain template, with the source content underused. Side-by-side comparison against a Supervertaler for Trados AutoPrompt of the same source (an NL→EN-GB mechanical patent) confirmed that the Trados side was reliably catching real source defects, locking the correct EPO-conventional terminology, and quoting real preference cascades, while the Workbench side was inventing hypothetical defects, locking the wrong terminology (`mantel` → "sleeve" when the source also used `huls` and `beschermhuls`), and giving generic anti-truncation language.
- Root cause investigation: Workbench's `_build_project_context` already sends up to 50,000 chars of the source markdown to the LLM, so source ingestion is not the bug. The actual gaps were: (a) no pre-generation terminology-collision check, (b) confirmed translations in `project.segments[].target` were never surfaced as TM anchors (the loader's "no TM data" branch fired whenever no separate `.tm` file was attached, even when the project-confirmed title was sitting in segment 0), (c) `DocumentAnalyzer` had English-only domain keywords, so Dutch patents tended to fall through to 'general' or 'legal', (d) no defect-detection pass — the "preserve defects faithfully" rule was abstract, (e) no extraction of real preference cascades from the source.
- Five source-aware pre-generation passes have been added, each producing a short Markdown block that gets injected into the meta-prompt only when it finds real signal in the source:
  - **TM-anchor wiring**: `_gather_tm_reference_pairs` now pulls confirmed source→target pairs from `project.segments` (highest authority — these are locked decisions for THIS document) in addition to entries from separately-loaded TM databases. Each pair is annotated with its provenance so the LLM can weight project-confirmed pairs higher than corpus-wide TM samples.
  - **Terminology-collision detection** (`_detect_terminology_collisions`): scans the source for groups of Dutch terms whose natural English candidates would collide — the `mantel` / `huls` / `mantelbuis` / `beschermhuls` cluster, the `pijp` / `buis` / `flexibele buis` cluster, the `voorzijde` / `voorvlak` / `achterzijde` distinction, and the `as` (axle vs geometrical axis) homograph. Each detected collision is presented with the EPO-conventional resolution.
  - **Defect-detection pass** (`_detect_source_defects`): extracts up to five verbatim defect examples from the source — hanging mid-sentence breaks ending in Dutch subordinating conjunctions (`doordat`, `waarbij`, `dewelke`, etc.), doubled spaces inside running text, plausible `-d/-t` Dutch verb-ending typos, and broken-compound double-space patterns. Quoting real examples is far more effective than abstract instructions.
  - **Anti-truncation specificity** (`_extract_source_cascades`): extracts up to three real `bij voorkeur ... bij nog meer voorkeur` cascades from the source so the generated prompt's anti-truncation rule cites a concrete example from this document instead of generic prose.
  - **Patent-vs-legal disambiguation**: `DocumentAnalyzer.DOMAIN_KEYWORDS` extended with Dutch keywords for every domain (patent, legal, medical, technical, financial, marketing), plus a new `PATENT_MARKER_PATTERNS` list of high-signal markers (claim numbering, `volgens conclusie N`, `uitvoeringsvorm`, `stand der techniek`, `FIG.` references, EP/US/WO patent number citations). When 3+ distinct patent markers are present, domain is locked to 'patent' regardless of which domain has the highest keyword score — eliminates the failure mode where Dutch patents get misclassified as 'legal' and trigger BV/NV/Meester scaffolding.
- The meta-prompt's OUTPUT INSTRUCTIONS section now also requires proper Markdown formatting in the generated prompt (`##` H2 headings per major section, `-` bullet lists, `**bold**`, proper `| ... |` table for the glossary), matching the format Supervertaler for Trados v4.19.110 just adopted. Both products' AutoPrompt outputs are now consistently formatted in the shared `prompt_library/` folder.
- When the source contains no legal-entity markers (BV, NV, Meester, notaris, etc.), the meta-prompt now instructs the LLM to OMIT the LEGAL ENTITY AND TITLE HANDLING and STATUTORY REFERENCE PRESERVATION sections from the generated prompt — those sections were noise for a mechanical patent body where no entity names appear in running text.
- Smoke-tested against the BRANTS LTRI-001-BE-EP source: all four collision clusters detected and resolved with EPO conventions; domain locked to 'patent' with 15 markers, confidence 111 (vs legal 6.5); doubled-space + hanging-conjunction defect caught verbatim; legal-entity scaffolding correctly identified as unneeded.


## v1.10.43 – May 16, 2026

### Changed (Prompt library: filename is now the authoritative display name)

- The prompt manager tree displayed the YAML `name:` field from each prompt file's frontmatter, not the filename on disk. Renaming a .md file in Explorer (without editing the YAML) left the tree showing the old name on refresh, even though the file was correctly re-read – a confusing UX trap with two sources of truth for what to call a prompt.
- The on-disk filename is now the single source of truth. The loader (`UnifiedPromptLibrary._parse_markdown`) always sets `name` to `filepath.stem`, ignoring any YAML `name:` field that may exist for backward compatibility with older files. The saver (`UnifiedPromptLibrary.save_prompt`) no longer writes `name:` to the YAML frontmatter at all.
- Effect for users: renaming `MyPrompt.md` to `Better Name.md` in Explorer and clicking refresh now updates the display label immediately. No need to edit YAML inside the file too.
- Backward compatibility: existing prompt files with a YAML `name:` field still load fine – the field is silently ignored on read, and is dropped from the file the next time the prompt is saved through the UI. No mass migration runs.
- Shipped in parallel with Supervertaler for Trados v4.19.109 which makes the same change to its C# `PromptLibrary` so both products stay in sync on the shared `prompt_library/` folder.


## v1.10.42 – May 16, 2026

### Fixed (Trados bridge poll no longer hogs the UI thread)

- A py-spy profiling pass on a live editing session (issue #203) found `_poll_trados_bridge` consuming ~35% of MainThread CPU in every interactive scenario – scroll, typing, navigation, batch find. Each `ChatViewWidget` (AI tab, grid view, on-demand chat windows) ran its own 3 s `QTimer` that called `TradosBridgeClient.is_available()` synchronously, which in turn opened a fresh TCP connection via `requests.get()` with a 500 ms timeout. When the bridge was unreachable (Trados closed, plugin not running) two widgets × 500 ms timeout / 3 s ≈ 33% of MainThread CPU, matching the observed cost.
- `TradosBridgeClient` now owns a single shared `requests.Session()` for connection pooling, exposes a process-wide singleton via `TradosBridgeClient.shared()`, and caches the last observed availability so `is_available()` returns instantly.
- The actual HTTP probe runs on a `QThreadPool` worker driven by a new `TradosBridgePoller(QObject)` singleton. The poller backs off when the bridge is unreachable (3 s → 10 s → 30 s → 60 s) and resets the moment the bridge becomes reachable again, so a Trados-less session costs essentially nothing.
- `ChatViewWidget` no longer runs its own poll timer; instead it subscribes to the shared poller's `availability_changed` and `pref_changed` signals. Toggling the Trados chip in one chat view propagates to siblings instantly via a signal, instead of waiting up to 3 s for the next poll tick.
- `UnifiedPromptManager._context_aware_send` now uses `TradosBridgeClient.shared()` too, so the chat-send context fetch also gets connection pooling and the cached availability flag.

### Fixed (Find & Replace no longer rebuilds the entire grid per click)

- The same profiling pass showed `find_next_match` calling `self.load_segments_to_grid()` just to toggle row visibility, with per-cell `_rebuild_grid_stylesheet` re-firing through `load_segments_to_grid`'s widget recreation path. For a 289-segment project, this consumed ~64% of MainThread CPU during a single batch-find run; on larger projects the cost grows super-linearly.
- Added an `_search_highlighted_cells` set that tracks where yellow search highlights live. `find_next_match`, `find_all_matches`, and `highlight_all_matches` now drop the `load_segments_to_grid()` call and instead clear only the cells from the previous search, then apply new highlights inside a single `setUpdatesEnabled(False)` repaint pass. `clear_search_highlights` uses the same targeted clear instead of a grid rebuild.

### Changed (Find & Replace dialog buttons reorganised to reduce mis-clicks)

- The action-button row was Find next | Find all | Replace this | Replace all | Highlight all | Clear highlights – six buttons in a single line with the destructive Replace pair sandwiched in the middle. A profiling session that was meant to record a Replace All landed on Find next instead because the buttons sit shoulder to shoulder with no visual distinction. Re-grouped as Find next | Find all | Highlight all | Clear highlights | (vertical separator) | Replace this | Replace all, with the Replace buttons given a distinct amber background so they read as destructive at a glance.
- Wired Enter in the Find field to trigger Find next, and Enter in the Replace field to trigger Replace all (which already shows its own confirmation prompt before doing anything destructive). Previously neither input had a keyboard binding.


## v1.10.41 – May 14, 2026

### Changed (SuperLookup: From/To language filters moved onto the search row)

- The "From: … To: …" language filter dropdowns had their own row below the search box. Moved them onto the same row as the search box and the Search / Clear buttons, sitting to the right of Clear. The search box keeps its stretch so it still expands to fill the width and pushes the language controls to the right edge. One less row to win back vertical space on smaller laptop screens.


## v1.10.40 – May 14, 2026

### Fixed (SuperLookup: re-searching from the Web Resources tab didn't update the web views)

- When the Web Resources sub-tab was already the active tab, editing the search query and clicking Search left every web resource (IATE, Linguee, ProZ, etc.) showing the *previous* query. The web search is deliberately deferred – `perform_lookup()` only marks it pending, and `on_results_tab_changed()` fires it when the user navigates to the Web Resources tab. But navigating doesn't happen on a re-search from that same tab, so the pending search was never triggered.
- `perform_lookup()` now detects when the Web Resources sub-tab is already the active tab and fires the web search immediately (for both embedded and external browser modes), instead of only marking it pending. The deferred-on-navigation behaviour is unchanged for every other tab.


## v1.10.39 – May 14, 2026

### Changed (SuperLookup header takes less vertical space)

- The SuperLookup tab's header was wasting vertical space on smaller laptop screens: a 16pt title plus a three-line description box. Shrunk the title to 11pt and cut the description to its one useful line – "Press Ctrl+Alt+L or paste text to search your TMs and Termbases" – with tighter padding. The macOS / Linux permission notes (Accessibility / pynput) are kept since they're genuine setup requirements. The header is still hidden entirely in the floating-assistant compact mode, as before.


## v1.10.38 – May 14, 2026

### Changed (PDF Rescue model picker modernised)

- The PDF Rescue tool (Tools → 📄 PDF Rescue) had its own AI model picker that the v1.10.36 model-list cleanup deliberately skipped – it was still offering GPT-4o / GPT-4 Turbo / GPT-4, Claude 3.5 / 3 Opus, and Gemini 1.5 / 2.0. Updated it to the current vision-capable models: GPT-5.5, GPT-5.4 Mini; Claude Sonnet 4.6, Claude Haiku 4.5, Claude Opus 4.7; Gemini 3.1 Flash-Lite, Gemini 2.5 Pro, Gemini 3.1 Pro Preview. Default is now GPT-5.5, and the "Model Capabilities" help text was rewritten to match.
- Gemma 4 26B MoE is intentionally left out of the PDF Rescue picker (it appears in the main translation lists): the OCR provider-detection keys off the `gemini-` prefix, and Gemma's vision reliability for demanding OCR work is unproven. The picker stays scoped to proven vision models.
- The provider-detection and token-parameter logic needed no changes – GPT-5.5 / GPT-5.4 Mini both resolve to OpenAI and correctly use `max_completion_tokens`.
- `modules/pdf_rescue_tkinter.py` was left untouched: it is only used by the frozen legacy tkinter app under `legacy_versions/`, not the current PyQt6 Workbench.


## v1.10.37 – May 14, 2026

### Removed (Settings → AI Models tab)

- Removed the "🤖 AI Models" Settings tab (Model Management). It existed to manage the models discovered by the old Model Version Checker (removed in v1.10.35), so with the checker gone its main job was gone too – and on machines that had run the checker it had filled up with dozens of auto-discovered junk entries (`o3-mini-2025-01-31`, `gpt-4.1-nano`, `gpt-5-chat-latest`, …) marked "discovered model". The curated provider lists are now the single source of truth.
- Removed `_create_model_management_tab()`, `_save_model_management_settings()`, and the `_ALL_KNOWN_MODELS` / `_DEFAULT_ENABLED_MODELS` class tables that fed the tab (~150 lines).
- The QuickTrans provider dropdowns no longer filter against the saved `model_management` setting – they now build straight from the curated `llm_providers` list, so existing users whose settings carried discovered-model junk get clean dropdowns immediately. The now-inert `model_management` key left behind in older settings files is harmless and simply stops being read or written.


## v1.10.36 – May 14, 2026

### Changed (AI model lists modernised to match Supervertaler for Trados)

- Cleaned up the AI model lists across the Workbench so the five providers it shares with Supervertaler for Trados (OpenAI, Claude, Gemini, Mistral, DeepSeek) now offer the same curated, current models. The lists had drifted badly stale – OpenAI still listed GPT-4o / o1 / o3, Claude carried Sonnet 4.5 / Opus 4.1 / Opus 4.6, Gemini was on 2.5 Flash / 2.0 Flash, and Mistral still had the retired Mistral Nemo.
- New curated lists:
  - **OpenAI**: GPT-5.5 (flagship), GPT-5.4 Mini (fast & economical). Default is now GPT-5.5.
  - **Claude**: Claude Sonnet 4.6 (recommended), Claude Haiku 4.5 (fast), Claude Opus 4.7 (latest premium).
  - **Gemini**: Gemini 3.1 Flash-Lite (recommended), Gemini 2.5 Pro (premium), Gemini 3.1 Pro Preview (latest), Gemma 4 26B MoE (open model). Default is now Gemini 3.1 Flash-Lite.
  - **Mistral**: Mistral Large, Mistral Small (dropped the retired Mistral Nemo).
  - **DeepSeek**: V4 Pro, V4 Flash (already current – unchanged).
- Updated everywhere the lists appear: the AI Settings dropdowns, the Model Management tab (`_ALL_KNOWN_MODELS` / `_DEFAULT_ENABLED_MODELS`), the match-pane provider list, the chat panel's model picker, the LLM client defaults, the vision-capable model list, and the token-cost pricing table (`claude-opus-4-7` corrected to $5/$25, `gpt-5.5` at $5/$30, `gemini-3.1-flash-lite` at $0.25/$1.50). Also fixed a stale hard-coded `claude-opus-4-1` model ID in the prompt assistant.
- **OpenRouter** and **Ollama** lists are left as-is – OpenRouter is a 200+-model gateway where you type any model ID, and Ollama is local. The PDF-rescue tool keeps its own separate model picker (a vision-OCR utility with no Trados counterpart).


## v1.10.35 – May 14, 2026

### Removed (Model Version Checker startup popup)

- Removed the "Model Version Checker" feature: the background check that ran ~2 seconds after startup and could pop up a "new models available" dialog, plus its Settings panel (the "🔄 Model Version Checker" group with the auto-check toggle and "Check for New Models Now" button). The popup interrupted startup and the model lists it discovered are now curated by hand to stay in line with the Supervertaler for Trados lists.
- Code removed from `Supervertaler.py`: the startup `QTimer.singleShot` trigger, the `_check_for_new_models()` and `_on_new_models_selected()` methods, the Settings UI section, the `auto_check_models` preference save, and the stale `model_version_cache.json` entry in the satellite-file migration list. Deleted `modules/model_version_checker.py` and `modules/model_update_dialog.py`.
- The **Model Management** tab and its underlying settings (`user_added_models`, `enabled_models`) are untouched – you can still curate which models appear in the dropdowns there.


## v1.10.34 – May 14, 2026

### Fixed (Gemini translation broken on the v1.10.33 Windows EXE)

- A user who downloaded the v1.10.33 Windows ZIP minutes after release reported that **Gemini translation failed silently** (`Translated: 0 | Failed: 1` with no diagnostic) while OpenAI worked fine. The on-screen error said `Google AI library not installed. Install with: pip install google-generativeai pillow` – misleading on a frozen EXE where pip isn't applicable and the package was correctly bundled in the first place.
- Real cause buried in the session log: `ModuleNotFoundError: No module named 'unittest'` while loading `pyparsing.testing`. `pyparsing` is a transitive dependency of `google-generativeai` (via `google-api-core`), and `pyparsing.testing` imports `unittest` at module load. v1.10.33's `Supervertaler.spec` had `'unittest'` in its excludes list (grouped with `'pytest'` under "Testing frameworks"), so PyInstaller stripped it from the bundle. The moment the Gemini code path tried to `import google.generativeai`, the chain blew up – and the `try / except ImportError` block converted that into the misleading "not installed" message.
- Two fixes:
  - **`Supervertaler.spec`**: dropped `'unittest'` from excludes. ~200 KB bigger bundle, but Gemini works again (and presumably so does anything else that transitively pulls `pyparsing.testing`).
  - **`modules/llm_clients.py` `_call_gemini`**: now preserves the original exception in the ImportError message so future users get *"Could not load Google Gemini SDK: ModuleNotFoundError: No module named 'unittest'. If 'pip list' shows google-generativeai is installed, this is likely a transitive-import failure (often a stdlib module excluded from a frozen bundle)."* instead of the misleading boilerplate. Also adds defensive handling around `response.text` (which raises ValueError when Gemini's safety filter blocks the response) – future failures of *that* class will report the actual `finish_reason` / `safety_ratings` instead of a generic "no text" error.
- Live-tested with the user's actual Gemini API key + the `gemini-2.5-flash-lite` model. The translation that failed on v1.10.33's Windows EXE now returns correctly.


## v1.10.33 – May 14, 2026

### Added (Phrase bilingual DOCX: auto-detect source / target language pair on import)

- A user pointed out that bilingual format files carry the language pair as metadata in the file – Phrase explicitly labels its column-header row as `Source (cs) | Target (de-de)`, and run-level `w:lang` attributes corroborate it – yet the Phrase import dialog still asked the user to pick the source and target language manually. Pure friction; no detection was ever wired.
- v1.10.33 closes that gap with a three-tier auto-detection strategy on the Phrase handler:
  - **Strategy A** (primary, ~always succeeds on Phrase output): parse the column-header table for the `Source (XX) | Target (YY)` pattern. Returns BCP-47 codes like `("cs", "de-de")`.
  - **Strategy B** (fallback for non-standard files): count `w:lang` attribute occurrences in the raw `document.xml`; most common non-English code = source, second = target. Drops English unless it's the only signal, because Phrase emits `en-US` for unrelated UI string runs.
  - **Strategy C** (last resort): parse the filename for a `_cs-de_` style pattern. Less reliable because user / Phrase-config variation, so it's behind A and B.
- New `modules/language_codes.py` provides `iso_to_english_name(code)` which maps `cs` / `cs-CZ` / `cs-cz` / `CS-CZ` → `"Czech"`, `de-DE` → `"German"`, `zh-Hant` / `zh-TW` → `"Chinese (Traditional)"`, `zh-CN` → `"Chinese (Simplified)"`, etc. Covers the full Workbench picker set. Future bilingual-handler detection paths can share this module instead of inlining ad-hoc maps.
- The Phrase import dialog now opens **pre-filled with the detected pair** and a callout line: *"Auto-detected from file: **Czech** → **German**. Confirm or change below."* The user can still override if the detection is wrong (combos remain editable, dialog still requires explicit OK). If detection is partial (only one side found), the callout is honest about that and only pre-fills the side it knows.
- The same `detect_language_pair()` method lives on `PhraseDOCXHandler` for any future call-site that wants the info programmatically (e.g. round-trip export validation, summary stats).
- Verified on a real user file: `Praxe_PC-cs-de_de-TR.docx` correctly detects `cs / de-DE` and resolves to `Czech / German` for the picker. Same file's `_translated.docx` export was also verified to be byte-for-byte structurally identical to the source apart from the 44 target cells – preserves the 4-table layout, the 7-visual-column / 8-grid-column gridSpan variant, headers, footers, and all run-level metadata.


## v1.10.32 – May 14, 2026

### Fixed (Phrase bilingual DOCX export crashed with `'QWidget' object has no attribute 'toPlainText'`)

- A user reported that **import** of Phrase bilingual DOCX worked correctly (the chain of fixes in v1.9.487 / v1.9.488 / v1.9.490 did their job) but **export** back to Phrase bilingual DOCX crashed with `Failed to export Phrase bilingual DOCX: 'QWidget' object has no attribute 'toPlainText'`. Net effect: the user could load a Phrase file, translate it inside Workbench, but had no way to get the translations back out.
- Root cause: `export_phrase_bilingual()` was written for a grid layout that had a Notes column at index 4. That layout was simplified down to five columns (`#, Type, Source, Target, Status`) some time ago, and column 4 is now the Status indicator – a plain `QWidget` badge, not a text-edit widget. The export's stale call `self.table.cellWidget(row, 4).toPlainText()` blew up the moment it hit that Status widget on the first row. (The import path had no equivalent bug because it builds the project from `phrase_handler.extract_source_segments()`, not from grid widgets.)
- Fix: bypass the grid entirely. The Phrase segment ID is already on each segment's ``notes`` field (written at import time as `Phrase ID: xxx:nnn | Status: XX`) and the translated text is on `segment.target` (updated by the grid's `textChanged` handler whenever the user edits a cell). The export now iterates `self.current_project.segments` directly, reads `segment.notes` and `segment.target`, and collects translations into the dict that `phrase_handler.update_target_segments()` expects. Works regardless of grid state – even if the user filtered the grid down to a few rows, or the editor isn't currently visible.


## v1.10.31 – May 14, 2026

### Added (Ticking "Also bias from your termbases" navigates to Termbases tab)

- Since v1.10.29 the per-termbase 🎤 Voice flag is opt-in: ticking the master "Also bias from your termbases" toggle in the Voice tab does nothing visible until the user also goes to Termbases tab and ticks the 🎤 Voice column for at least one termbase. That's a fine model in principle, but it relies on the user discovering the second step on their own.
- v1.10.31 closes that gap: ticking the Voice-tab toggle now automatically navigates Workbench to the 🏷️ Termbases top tab. The user lands exactly where they need to be to make the toggle do anything, with the 🎤 Voice column visible. Untick is a no-op (no navigation) – there's nowhere obvious to land in that case, and unticking is usually a deliberate end-of-task gesture, not the start of a setup flow.
- Side touch-ups:
  - The toggle's new state is persisted on the rising edge (calls `_vocab_save_from_ui()` before navigating away), so the user can't lose the change by navigating off the Voice tab without clicking the explicit Save button.
  - `_vocab_load_into_ui()` wraps `setChecked(use_tb)` in `blockSignals(True/False)` so the navigate-on-tick handler doesn't spuriously fire at app startup or any time the Voice tab is re-built.
  - New `SupervertalerQt.open_termbases_tab()` method – jumps `main_tabs` to the Termbases top tab by text-lookup (survives any future re-ordering). Returns True if it navigated, False if the tab wasn't present. Available for any other call-site that wants the same hand-off.


## v1.10.30 – May 14, 2026

### Changed (Voice tab "Also bias from your termbases" checkbox: green → purple to match)

- The toggle was using the default green `CheckmarkCheckBox`, which on a white panel background looked washed out – the unchecked state is just a thin grey border and the checked state is the same green used for "Read" in Termbase Manager, so the colour didn't communicate any semantic link to the voice-dictation feature.
- Swapped for `PurpleCheckmarkCheckBox` so the toggle now visually matches the 🎤 Voice column in Termbase Manager – same colour, same feature, makes the connection obvious. Unchecked-state border bumped from `#999` to `#888` so the box reads as a deliberate UI element rather than fading into the panel.
- Refactoring side-effect: `PurpleCheckmarkCheckBox` (added in v1.10.28 as a local class in `Supervertaler.py`) is now in `modules/styled_widgets.py` next to its `CheckmarkCheckBox` parent class. Single source of truth, no copy-pasted style block, and the styling change (border colour fix) lands once and applies to both the Voice-tab toggle and the Termbase Manager column.


## v1.10.29 – May 14, 2026

### Changed (Termbase voice-bias is now opt-in instead of opt-out)

- v1.10.28 added the per-termbase 🎤 Voice flag with `DEFAULT 1` — meaning every existing termbase (and every new one) automatically contributed to dictation vocabulary biasing. A user with "literally dozens" of termbases flagged this as wrong: many of those termbases cover different language pairs / different clients / different projects, and pulling target terms from all of them dilutes the prompt rather than focusing it on what you're actually translating right now.
- v1.10.29 flips the model to opt-in:
  - **`database_manager.py`** schema migration now defaults the column to `0` for fresh databases.
  - **One-shot reset migration** in `Supervertaler.py.__init__` runs `UPDATE termbases SET voice_dictation_enabled = 0` once per database, gated by a `voice_dictation_opt_in_reset_applied` sentinel in the unified settings JSON. This catches users who already upgraded through v1.10.28 (a few minutes' window) so their existing rows don't stay flagged-on. Manual selections made after v1.10.29 are not re-touched — the sentinel ensures the reset only runs once.
  - **`termbase_manager.py`** API tightened: `get_termbase_voice_enabled()` now defaults to `False` for NULL / missing values (was `True`); `get_voice_enabled_termbase_ids()` matches `voice_dictation_enabled = 1` strictly (no NULL fallback).
  - **Tooltips updated** in both the Voice tab's "Also bias from your termbases" checkbox and the Termbase Manager's 🎤 Voice column header — explicitly states "opt-in: pick only the few termbases relevant to your current work".
- Net effect: users upgrade to v1.10.29, all their termbase 🎤 Voice cells flip back to unchecked, the dictation vocabulary contains only the built-in defaults + the user's custom dictionary terms. Users who want termbase biasing tick the 2 or 3 termbases relevant to their current work in Termbase Manager → 🎤 Voice column. Whisper's prompt stays focused; the feature finally does the right thing in the typical "many-termbases" workflow.


## v1.10.28 – May 14, 2026

### Fixed (Voice dictation: termbase biasing is now project-free, target-language, and per-termbase opt-in)

- The v1.10.26 "Also bias from the active project's termbase (source-language entries)" toggle had three real problems flagged by a user: (1) it silently did nothing if no Workbench project was open – which is the typical workflow for translators using Trados as the primary editor with Workbench as a companion dictation surface; (2) "active" meant the project's per-termbase Read/Write toggles, which doesn't translate to a project-free setup; (3) it pulled the *source-language* column, but translators dictate the *target* side. None of those were "polish" issues – the feature did nothing useful in the common case.
- v1.10.28 reworks all three:
  - **New per-termbase 🎤 Voice flag** in the database. Each termbase has its own `voice_dictation_enabled` boolean column (DB migration applied at startup, defaults to 1 for existing termbases so the feature works out of the box). This flag is independent of project context and survives between Trados and Workbench because it lives in the shared termbase database.
  - **New 🎤 Voice column in Termbase Manager**, using a new purple-themed `PurpleCheckmarkCheckBox` to fit the existing styled-checkbox palette (green Read, blue Write, pink Project, orange AI, purple Voice). Tick / untick per termbase to scope the dictation vocabulary.
  - **`_collect_active_termbase_source_terms` → `_collect_voice_dictation_termbase_terms`**: uses `get_voice_enabled_termbase_ids()` (no project required) and pulls the `target_term` column (what the translator dictates). The 200-term cap and `build_initial_prompt`'s 800-char ceiling still apply.
  - **Voice settings checkbox relabelled**: "Also bias from the active project's termbase (source-language entries)" → "Also bias from your termbases (target-language terms)". Tooltip explains the per-termbase 🎤 Voice flag controls scope.
- New `TermbaseManager` API: `get_termbase_voice_enabled(id)`, `set_termbase_voice_enabled(id, bool)`, `get_voice_enabled_termbase_ids()`. The flag defaults to True on rows predating the column, so users who never touch the Termbase Manager still get the bias out of the box.
- Net effect for the typical "Workbench open as dictation companion alongside Trados" workflow: dictating "Supervertaler" now bias-corrects to "Supervertaler" via the built-in default vocab, and dictating any term that lives in the user's termbases (in the target language) gets biased too – without needing a Workbench project, without needing to fiddle with Read/Write toggles, and without confusion about source vs. target.


## v1.10.27 – May 13, 2026

### Changed (Voice tab: termbase-bias checkbox now uses the project's styled checkmark)

- v1.10.26 added a "Also bias from the active project's termbase" checkbox to the new Dictation vocabulary group, but rendered it as a bare `QCheckBox` (the OS-native rounded-square widget). The voice-commands table directly next to it uses the project's `CheckmarkCheckBox` styled widget (the green-tick column), so the mismatch was visually jarring.
- v1.10.27 swaps the bare `QCheckBox` for `CheckmarkCheckBox` so the termbase-bias toggle matches the styled-checkmark convention used elsewhere in Workbench (voice commands table, glossary toggles, etc.). Pure cosmetic change – same behaviour, same persistence path.


## v1.10.26 – May 13, 2026

### Added (Voice dictation: vocabulary biasing + custom replacements + termbase awareness)

- Whisper has a well-known weakness with brand names and technical jargon – a user reported the always-on + push-to-talk paths repeatedly mistranscribing **"Supervertaler"** as **"Supervertile"**. v1.10.26 attacks this with three complementary techniques, all in the new `modules/voice_vocabulary.py` module.
- **1. Built-in `initial_prompt` biasing (no setup required).** Whisper's decoder accepts a short `initial_prompt` string that biases transcription toward the vocabulary it contains. We pass a hard-coded list of Supervertaler-ecosystem terms ("Supervertaler, Workbench, Trados Studio, memoQ, CafeTran, Phrase, TermLens, SuperLookup, QuickTrans, OpenAI, ChatGPT, Claude, Anthropic, Gemini, Mistral, …") to both the cloud OpenAI Whisper API path and the local faster-whisper path. Words appearing in the prompt get a small probability boost throughout transcription, so brand names mistranscribe substantially less often. Vosk's grammar mode ignores the prompt (it only ever emits phrases from its fixed command list).
- **2. Built-in post-processing replacements.** A second pass after Whisper returns catches stubborn cases the biasing didn't fix. Pre-seeded with common mishears: `supervertile`/`supervertaller`/`super vertaler` → `Supervertaler`, `work bench` → `Workbench`, `memo q`/`memo cue` → `memoQ`, `cafe tran` → `CafeTran`, `chat gpt` → `ChatGPT`, `gpt 4` → `GPT-4`, etc. Whole-word, case-insensitive; multi-word matches handle slow-speaker word breaks.
- **3. User-extensible.** A new "📚 Dictation vocabulary" group in the Voice tab adds:
  - A **Custom dictionary** textarea: brand / technical terms appended to the `initial_prompt`. Comma- or newline-separated. ("Acme Corp, Beijerterm, polyurethane, embodiment, …")
  - A **Replacements** table (Heard → Meant): user-editable `heard → meant` pairs, applied on top of the built-in defaults. User entries can override defaults by repeating the `heard` key with a different `meant`.
  - A **"Also bias from the active project's termbase"** checkbox: when on, the source-language `source_term` of every entry in every active termbase on the current project is appended to the `initial_prompt`. Free vocabulary boost for translators — terms from your actual project glossary auto-bias the dictation without you maintaining a separate list. Capped at 200 termbase entries per dictation invocation; `build_initial_prompt` further trims to ~800 chars to stay inside Whisper's ~224-token prompt budget.
- Plumbing: `ContinuousVoiceListener` and `QuickDictationThread` both gained `initial_prompt` and `replacements` constructor parameters; `Supervertaler.py` builds a fresh prompt + reads the replacement list every time a new dictation surface is constructed, so changes to vocabulary settings take effect on the next utterance without restarting Workbench. Settings persist under the new `voice_vocabulary` key in the unified settings JSON.
- Effect: pressing push-to-talk and saying "Supervertaler" now reliably transcribes as **Supervertaler** instead of "Supervertile". Same for the other Supervertaler-ecosystem terms. Translators using domain-specific termbases will see their own technical vocabulary land correctly without any per-project setup.


## v1.10.25 – May 13, 2026

### Fixed (Clipboard tab: pressing Enter on a list item didn't paste back into the source app)

- Picking a text or image clip from the Clipboard tab (arrow-key to it, press Enter; or click) was supposed to: (1) set the clipboard to that item, (2) refocus the app the user came from via Ctrl+Alt+C, (3) hide Workbench, (4) send Ctrl+V. Steps 2–4 were silently skipped – the clip landed on the clipboard, but the user had to Alt+Tab back to the source app and paste manually.
- Root cause: when the Clipboard tab was migrated out of Sidekick and into Workbench in v1.10.0/v1.10.1, the `_on_item_activated` handler in the widget kept calling its `paste_text_callback` / `paste_image_callback` constructor parameters – but the new Workbench-supplied callbacks (`_ensure_clipboard_top_tab`'s `_paste_text` / `_paste_image` lambdas) only put the item on the clipboard and did *nothing else*. The widget already had a fully-working `_paste_to_source` method (used by Snippets, Special Characters, and Text Conversions in the action-menu column, which is why those paths *did* paste back correctly), but the primary text/image list activation path never called it.
- Fix is a small widget-internal refactor:
  - **New `_paste_pixmap_to_source(pixmap)`** method mirrors `_paste_to_source(text)` but for image clips. Both now delegate the post-clipboard "activate source → hide Workbench → send Ctrl+V" sequence to a shared `_activate_source_then_paste()` helper.
  - **`_on_item_activated`** now calls `_paste_to_source` / `_paste_pixmap_to_source` directly, bypassing the stale callback path.
  - The `paste_text_callback` / `paste_image_callback` constructor parameters are kept for backward compatibility but are now a notification-only hook (fires after the paste-back, errors swallowed). The Workbench-supplied stub `_paste_text` / `_paste_image` lambdas continue to exist but are no longer load-bearing.
- Net effect: the Clipboard tab's primary use case finally does what its tooltip and help docs always claimed: pick a clip → it pastes into whichever app you summoned the tab from. Snippets / Special Characters / Text Conversions in the third column already worked and are unaffected.


## v1.10.24 – May 13, 2026

### Changed (Trados QuickLauncher bridge: land on AI tab → Chat, not right-panel Chat)

- There are two Chat surfaces in Workbench since v1.10.4 retired Sidekick: one in the right panel next to the editor (compact, intended for segment-level translation workflow), and one as a full-width sub-tab inside the AI top tab (intended for general AI conversation). v1.10.10's `_on_bridge_prompt_request` (the handler for QuickLauncher prompts forwarded by Trados Studio) was routing to the right-panel Chat. User feedback: QuickLauncher prompts from Trados are explicitly the *general* kind – they deserve the full-width sub-tab where the response has more room to render.
- `_on_bridge_prompt_request` now switches `main_tabs` to the ✨ AI top tab and `ai_subtabs` to the 💬 Chat sub-tab. Looked up by text (`"AI"` + `"✨"` for the top tab; `"Chat"` for the sub-tab) rather than stored indices, so it stays correct if future builds shift tab order.
- Both Chat surfaces share the same `ChatBackend`, so `backend.add_message()` and `backend.send_ai_request()` calls render in *both* views automatically – this change is purely about which Chat surface gets brought to the foreground when a prompt arrives. The user message + LLM response appear in the right-panel Chat too, ready to scroll back to if you switch over.
- Pairs with **Supervertaler for Trados v4.19.99**, which renames the user-facing labels accordingly (AI Settings → "QuickLauncher prompts go to: Workbench Chat", right-click → "&Send to Supervertaler Workbench Chat", and the Trados edit-history actionName "Supervertaler Workbench"). The internal wire-protocol identifiers (`sidekick_bridge_server.py` module, `SidekickBridge` C# class, `WorkbenchSidekickClient` C# class, `sidekick-bridge.json` handshake filename, `QuickLauncherTarget == "WorkbenchSidekick"` setting value) are intentionally unchanged – they're historical names that pre-date v1.10.4, kept stable so deployed Trados plugin versions and Workbench installs continue to interoperate without a migration step.


## v1.10.23 – May 13, 2026

### Removed (Supermemory help docs – feature is no longer a Workbench concept)

- Supermemory (semantic TM search) was functionally removed from Workbench back in v1.9.105 – `search_supermemory()` returns 0 and the feature does nothing. The help docs hadn't caught up: there was still a dedicated `workbench/translation-memory/supermemory.md` page plus 7 stale mentions across other Workbench help pages.
- v1.10.23 deletes the dedicated page, removes the SUMMARY.md entry, and strips the stale references from: `workbench/README.md`, `workbench/cat-tools/overview.md`, `workbench/get-started/installation.md`, `workbench/translation-memory/basics.md`, `workbench/superlookup/overview.md`, `workbench/troubleshooting/{common-issues,performance,linux}.md`.
- `modules/help_system.py` Topics enum: `TM_SUPERMEMORY` constant removed (was pointing at the now-deleted slug).
- Supermemory continues to live in **Supervertaler for Trados** as a paid Assistant feature – the existing docs at `trados/ai-assistant/super-memory.md` and its sub-pages stay untouched.
- Out-of-scope follow-up: `modules/tm_manager_qt.py` still has dead Supermemory tab widgets (lines 364-418) that should be removed too. Filing under "code-cleanup TODO" rather than rolling into this version bump – this release is help-docs only.


## v1.10.22 – May 13, 2026

### Changed (QuickTrans Popup help page moved from AI Translation → Companion Tabs)

- The QuickTrans Popup is conceptually a companion-tab utility (a popup summoned by hotkey, dismissed by Esc), not an AI-translation feature like Batch Translation / Prompt Manager / Ollama. The help page was misfiled under the "AI Translation" SUMMARY section.
- Moved the file in the help repo from `workbench/ai-translation/quicktrans-popup.md` to `workbench/sidekick/quicktrans-popup.md`, and the SUMMARY.md entry from the AI Translation section to the Companion Tabs section. New GitBook URL: `/companion-tabs/quicktrans-popup`.
- `modules/help_system.py` Topics enum updated: new canonical `Topics.QUICKTRANS_POPUP = "companion-tabs/quicktrans-popup"`. The two legacy aliases (`AI_QUICKTRANS_POPUP`, `AI_QUICKLAUNCHER`) are kept pointing at the same value so external code that imports the old names still resolves correctly.


## v1.10.21 – May 13, 2026

### Changed (Help system Topics: AI_QUICKLAUNCHER slug + alias)

- The help-repo file `workbench/ai-translation/quicklauncher.md` was renamed to `workbench/ai-translation/quicktrans-popup.md` so the GitBook URL slug matches the page title (which became "QuickTrans Popup" when v1.10.10 rewrote the page; the filename had still pointed at the retired Sidekick QuickLauncher).
- `modules/help_system.py` Topics gets a new canonical constant `Topics.AI_QUICKTRANS_POPUP = "ai-translation/quicktrans-popup"`. The old constant `Topics.AI_QUICKLAUNCHER` is kept as a legacy alias pointing at the same slug, so any existing call-sites (none in-tree, but defensive) keep working.


## v1.10.20 – May 13, 2026

### Changed (Help system Topics enum tracks the Sidekick → Companion Tabs rename)

- The help repo was rewritten today to match the v1.10.4–v1.10.19 changes (Sidekick retired, companion tabs promoted into Workbench, Esc-to-tray, QuickTrans hand-off, etc.). The on-disk file paths in the help repo are unchanged (still `workbench/sidekick/*.md`) for backlink stability, but the SUMMARY.md section header was renamed from `## 🖥️ Sidekick` to `## 🖥️ Companion Tabs`, which changes GitBook's URL slugs from `/sidekick/*` to `/companion-tabs/*`.
- `modules/help_system.py` Topics enum updated to match. F1 / Help-menu links from inside Workbench now route to the new URLs:
  - `Topics.SIDEKICK` → `companion-tabs/overview`
  - `Topics.VOICE` → `companion-tabs/voice`
  - `Topics.CLIPBOARD` → `companion-tabs/clipboard`
  - New: `Topics.TRADOS_AWARE_CHAT` → `companion-tabs/trados-aware-chat`
- Legacy external links to `/help/sidekick/*` will 404 after GitBook re-syncs. Visitors arriving from old URLs need GitBook's redirect settings (configured separately, outside the repo).


## v1.10.19 – May 13, 2026

### Changed (Esc on SuperLookup always hides; Clipboard / Voice stay focus-aware)

- SuperLookup is a single-purpose query surface – there's no state to preserve, the search field re-seeds on the next summon from the originating app's selection, and the only thing Esc could reasonably mean is "get me out of here". v1.10.18's focus-aware gate (which kept Esc deferred when typing in the search box) was over-cautious for this surface.
- v1.10.19 drops the focus check on SuperLookup: Esc unconditionally hides Workbench to the tray regardless of which child widget has focus. Mirrors the dismissal pattern of popup-launchers (Spotlight, Alfred, Windows Search) where the search input is the dominant focus target and Esc-from-there is the standard dismiss key.
- Clipboard and Voice keep v1.10.18's focus-aware behaviour. They have richer input surfaces (clipboard filter text box, voice-command edit fields, dictation form), and clearing-the-field-with-Esc is a more useful default there.
- Implementation: per-tab branch inside `_on_esc_quick_lookup_dismiss`. SuperLookup hits an unconditional `self.hide()`; Clipboard / Voice still run the QLineEdit / QTextEdit / QPlainTextEdit / QAbstractSpinBox / editable-QComboBox introspection from v1.10.18.


## v1.10.18 – May 13, 2026

### Changed (Esc-to-tray is now focus-aware: skipped when typing in a text input)

- v1.10.17 added Esc-to-tray on the SuperLookup / Clipboard / Voice top tabs. On reflection it had two avoidable failure modes:
  - **Mid-typing accident**: user in the SuperLookup search box typing a query, hits Esc by accident (key is right above Shift), entire window vanishes. Heavy penalty for a one-keystroke mistake.
  - **Convention mismatch**: most apps use Esc-in-a-text-input to clear the field or close a dropdown, not to hide the whole app. Users pressing Esc expecting to clear their query instead lost the window.
- v1.10.18 adds a third gate to the existing two (current tab is quick-lookup; tray icon exists): Esc only dismisses when the focused widget is *not* a text-input widget. Recognised text-input types are `QLineEdit`, `QTextEdit`, `QPlainTextEdit`, `QAbstractSpinBox`, and an editable `QComboBox`. A 5-hop parent walk catches text inputs nested inside custom composite widgets too.
- Net behaviour:
  - Focus on the SuperLookup search field, Clipboard filter text box, an editable combo, etc. → Esc keeps its default Qt behaviour (clear field, close popup, dismiss completion list, etc.).
  - Focus on the Clipboard list, action tree, Voice tab buttons, empty space, etc. → Esc hides Workbench to the tray.
- The introspection is wrapped in a try/except so any unexpected widget configuration falls through to v1.10.17 behaviour (hide). Preserves the previous-release semantics as a safe default.


## v1.10.17 – May 13, 2026

### Added (Esc-to-tray on the quick-lookup tabs)

- Pressing Esc while on the SuperLookup / Clipboard / Voice top tabs now hides Workbench to the system tray. These three tabs are typically summoned via global hotkeys (Ctrl+Alt+L / Ctrl+Alt+C / push-to-talk) and used as popup utilities rather than full-screen work surfaces, so Esc-as-dismiss is the natural keyboard counterpart to the global hotkey-as-summon. Mirrors the dismissal flow that retired Sidekick used to have (Esc dismissed Sidekick), now scoped to the Workbench tabs that inherited Sidekick's role.
- On the Editor / TMs / Termbases / AI / Settings tabs, Esc is a no-op so it keeps its natural editor / dialog / combo-box semantics (cancel-edit, close-popup, etc.). The check is done at fire time against the current tab index, so the binding adapts automatically if tab indices shift in future builds.
- Falls back to do-nothing if the system tray isn't available (e.g. a Linux environment without a system-tray daemon). Hiding the window without a tray would leave the user with no way to bring Workbench back, so the binding deliberately fails closed in that case.


## v1.10.16 – May 13, 2026

### Changed (Clipboard tab: rename "Text snippets" → "Text" and highlight the active column)

- The first column header was "📝 Text snippets", which overlapped with the "Personal Snippets" entry in the 3rd-column action menu and confused users about which one held the clipboard history. Renamed to plain "📝 Text" – the column header just labels the kind of clipboard item, no "snippet" ambiguity.

### Fixed (Clipboard tab: no visual feedback which of the three columns has keyboard focus)

- Arrow-key navigation between the three columns (Text / Images / Menu) worked but gave no visual cue which column the user had landed in – they'd press ← / → expecting the cursor to be in one column and find arrow-down was actually navigating a different one. Two reasons:
  1. The column-header styling helper (`_refresh_focus_styles`) was orphaned. It used to be called by Sidekick's focus-change handler; with Sidekick gone since v1.10.4, nothing fires it.
  2. The 3rd column ("📑 Menu") was never wired into the highlight rotation – its header had a one-off inline stylesheet that never picked up the active state.
- v1.10.16 fixes both:
  - `_refresh_focus_styles` is connected to `QApplication.focusChanged` in `__init__`, so it fires automatically as the user navigates between columns – no external trigger needed.
  - The method now also flips `_action_header` between `_COL_HEADER_INACTIVE` and `_COL_HEADER_ACTIVE`, matching the other two columns.
  - The active column gets a 2-pixel blue underline + blue text on its header label; the inactive columns stay grey. All three columns light up consistently now.
  - The walk-up logic was tightened so that focusing a child widget (e.g. an internal QLineEdit inside the action tree's inline editor, if one ever appears) still lights up the *parent* column header rather than dropping the highlight.


## v1.10.15 – May 13, 2026

### Fixed (v1.10.14 didn't actually fix the menu-activation issue; menu still activates after Ctrl+Alt+C/L)

- v1.10.14's `menuBar().setActiveAction(None)` + `setFocus()` calls ran *before* Qt processed the queued synthetic Alt event from the input queue, so they cleared menu state that hadn't been set yet. By the time the Alt event was finally processed, the fix had already executed and Qt activated the menu bar with nothing further to dismiss it.
- v1.10.15 prevents the menu activation in the first place by changing the foreground-grab key sequence from a bare Alt tap (Alt down → Alt up) to an Alt+F24 chord (Alt down → F24 down → F24 up → Alt up). Qt's `QMenuBar` only activates on a *naked* Alt tap; `Alt+anything` is treated as a chord and skipped. VK_F24 (0x87) is a real Windows virtual-key code that exists in the VK enum for legacy extended function-key keyboards – no shipping app binds it, so the synthetic F24 has no observable effect anywhere. The Alt is still pressed at the moment `SetForegroundWindow` is called, so the "Alt key pressed" exception still applies and the foreground grab still succeeds reliably.
- The post-hoc `_dismiss_menu_activation` helper and the explicit `setFocus()` calls in `_continue_clipboard` / `_continue_superlookup` from v1.10.14 are kept as belt-and-braces. They no longer have any menu-activation to clear (the chord prevents it upstream), but the focus calls are good UX in their own right: the cursor lands in the search field / on the clipboard list so arrow keys work straight away without any extra Tab/click.


## v1.10.14 – May 13, 2026

### Fixed (Ctrl+Alt+C / Ctrl+Alt+L land in the File menu instead of the target widget)

- Side effect of the v1.10.9 foreground-grab fix. The synthetic Alt-down/Alt-up that satisfies `SetForegroundWindow`'s "Alt key pressed" exception is *also* what Windows interprets as "user tapped Alt to activate the menu bar". Even though our Alt was meant for Trados (the originating foreground app), `AttachThreadInput` shares input queues for the duration of the dance, so the Alt event leaks into Workbench's input stream just as we become foreground. Qt's `QMenuBar` then activates File-menu navigation mode and arrow keys steer the menu instead of the Clipboard list / SuperLookup results. The user saw Workbench come forward correctly – they just couldn't navigate the tab they landed on.
- Fix is three-pronged:
  1. **New `_dismiss_menu_activation()` helper** called at the end of `_bring_workbench_forward()`: explicitly calls `menuBar().setActiveAction(None)` to clear any leaked menu activation. Safe no-op when no menu is active.
  2. **Explicit focus in `_continue_clipboard`**: after the tab switch, `_text_list.setFocus()` via the widget's `_focus_list` helper (which also auto-selects row 0 when the list has no current selection). Arrow keys now navigate the clipboard history straight away.
  3. **Explicit focus in `_continue_superlookup`**: after the tab switch, focus lands on `source_text` (the search field). When the hotkey carried a query, the user can type a refinement immediately; when it didn't, the cursor is in the right place to start typing a fresh search.
- The setActiveAction call is the load-bearing part – it works even if the focus calls fail for some reason. The explicit focus calls are additionally good UX (the user lands in the "right" widget straight away regardless of the menu-leak issue) so they're kept as belt-and-braces.


## v1.10.13 – May 13, 2026

### Changed (QuickTrans SuperLookup hand-off button shows icon + label)

- The 🔍 hand-off button added in v1.10.12 now reads "🔍 Run in SuperLookup" instead of being icon-only. The bare 🔍 next to ⚙ wasn't self-explanatory enough at a glance – users couldn't tell what it did without hovering for the tooltip. Adding the label keeps the QuickTrans popup header compact (one short line of text) while making the action obvious. Settings cog remains icon-only since ⚙ is universally understood.


## v1.10.12 – May 13, 2026

### Added (🔍 hand-off button in QuickTrans popup: "Search this in SuperLookup")

- A new icon button next to the ⚙ settings cog in the QuickTrans popup header. When clicked, it closes the popup and opens Workbench's SuperLookup top tab with the same query pre-filled and the search auto-fired – same plumbing as the Ctrl+Alt+L global hotkey, just triggered from inside the popup. Useful when the user runs QuickTrans on a phrase and then thinks "I'd actually like to look this up in my TMs / termbases / web resources too" – one click instead of dismissing the popup, switching to Workbench, and pasting the query.
- Defers the parent-app call via `QTimer.singleShot(0, …)` for the same reason as v1.10.11's settings cog fix: the popup's `close()` events need a Qt event-loop turn to unwind before the foreground transition starts, otherwise the hammer chain inside `open_workbench_to_superlookup` races against still-queued popup destruction events.


## v1.10.11 – May 13, 2026

### Fixed (QuickTrans popup's gear icon didn't reliably bring Workbench to the foreground)

- Pressing the ⚙ settings cog inside the QuickTrans always-on-top popup correctly navigated Workbench to Settings → ⚡ QuickTrans, but Workbench itself didn't consistently come to the foreground – it stayed behind whichever app the user had been working in (typically Trados, since that's where QuickTrans is most often summoned from). The user could see the popup close but then had to Alt+Tab to find Workbench manually.
- Two changes:
  1. `open_mt_quick_lookup_settings` now calls `_bring_workbench_forward()` before navigating tabs. The pre-v1.10.11 implementation assumed Workbench was already foreground (which the always-on-top popup made it look like at a glance) – but the OS-level foreground was actually the originating app, so the navigation succeeded silently behind it. Routing through `_bring_workbench_forward()` applies the same full hammer chain (Alt-key trick + AttachThreadInput + BringWindowToTop + SetForegroundWindow + SwitchToThisWindow) that made Ctrl+Alt+C / Ctrl+Alt+L reliable in v1.10.9.
  2. `MTQuickPopup._open_settings` now defers the parent-app call via `QTimer.singleShot(0, …)`. Without the defer, the popup's `close()` events are still queued in Qt's event loop when the hammer chain runs, which can leave Workbench painted behind the OS-level foreground process even with the full chain applied. The 0-ms defer gives Qt one event-loop turn to fully unwind the popup destruction before the foreground transition starts.


## v1.10.10 – May 13, 2026

### Removed (Sidekick dead-code sweep: floating_assistant.py deleted and all defensive paths pruned)

- v1.10.4 retired Sidekick at the user-visible level – the floating-assistant window stopped being constructed and the Ctrl+Alt+K hotkey was unbound – but kept the module file (`modules/floating_assistant.py`) and 23 `getattr(self, '_floating_assistant', None)` defensive call-sites in the tree for a follow-up sweep. This release is that sweep.
- **Files deleted**: `modules/floating_assistant.py` (~2400 lines) is gone outright. Nothing imports it any more; the last reference inside `Supervertaler.py` (the `self._floating_assistant = None` stub) has also been removed along with all 23 `getattr` checks that referenced the attribute.
- **Methods deleted from `Supervertaler.py`**:
  - `_create_quicktrans_stub_tab` and `_open_quicktrans_in_sidekick`: the Workbench Settings → ⚡ QuickTrans tab used to be a stub that told users "settings live inside Sidekick" with a button to navigate there. With Sidekick gone the real `_create_mt_quick_lookup_settings_tab()` widget is now mounted directly in Workbench Settings → QuickTrans where users would have looked for it in the first place.
  - `show_quicklauncher_external`, `_show_quicklauncher_external_legacy`, `_run_external_quicklauncher_prompt`, `_launch_superlookup_external`, `_handle_sidekick_hotkey`, `_read_clipboard_for_sidekick`, `_on_pynput_sidekick`: the Ctrl+Alt+K hotkey handler chain plus a QMenu-based fallback popup that ran when Sidekick was unavailable. All unreachable since v1.10.4.
- **`SuperlookupTab.is_sidekick` parameter removed**: it gated mounting of the QuickTrans settings widget as a sub-sub-tab inside Sidekick's SuperLookup pane. QuickTrans settings now live in Workbench Settings (see above), so the flag and its branch are unnecessary.
- **`shortcut_manager.py` cleanup**: the `sidekick_open` and `sidekick_open_clipboard` shortcut IDs are kept (so users' customisations survive) but their `category` is now "Hotkeys" instead of "Sidekick" and the descriptions are rewritten to describe the actual target (SuperLookup tab / Clipboard tab) rather than the retired Sidekick window. The default-value upgrade path for `sidekick_open` (Alt+K → Ctrl+Alt+K, originally a Mac dead-key workaround) is gone; Alt+K is back as the default and is in-app-only.
- **Docstring sweep**: the file-level docstrings in `modules/clipboard_manager_widget.py`, `modules/voice_tab.py`, and `modules/sidekick_bridge_server.py` were rewritten to describe the post-Sidekick reality. References that just mention Sidekick historically (e.g. `chat_message_delegate.py:33` "You / Supervertaler Sidekick" avatar height comment) are left alone – they're accurate as historical comments and not user-visible.

### Restored (Trados bridge wiring – orphaned in v1.10.4, re-attached to the Chat tab)

- The HTTP bridge that lets the Supervertaler-for-Trados plugin push QuickLauncher prompts into Workbench (`modules/sidekick_bridge_server.py`) was previously started inside `FloatingAssistant.__init__`. v1.10.4 stopped constructing FloatingAssistant, which left the bridge silently un-started despite the v1.10.4 changelog claiming "Trados bridge is preserved" – which it wasn't, in fact. (No user reported this because Trados plugin → Workbench QuickLauncher prompts are a niche feature and apparently no one used them between v1.10.4 and v1.10.10.)
- `SidekickBridgeServer` is now constructed directly from `SupervertalerQt.__init__` and its `run_prompt_requested` signal is wired to the new main-window method `_on_bridge_prompt_request`, which mirrors the old `FloatingAssistant._on_bridge_prompt_request` behaviour: bring Workbench forward (full hammer chain), switch right panel to Chat, post the user message, send the expanded prompt via `chat_backend.send_ai_request`, post the response.
- The class name `SidekickBridgeServer`, the module name `sidekick_bridge_server.py`, and the handshake filename `sidekick-bridge.json` are deliberately *not* renamed. The Trados side (`Core/SidekickBridge.cs`) looks up the bridge by exactly those names; renaming would break deployed plugin versions. Only the consumer changed (from `FloatingAssistant` to Workbench's Chat tab), which the protocol doesn't expose.


## v1.10.9 – May 13, 2026

### Fixed (Workbench paints behind Trados on first Ctrl+Alt+C – belt + braces + suspenders)

- After three rounds of timing fixes (v1.10.6/.7/.8), Workbench was still painted *behind* Trados on the first Ctrl+Alt+C after launch. The activation calls succeeded (Clipboard tab switched correctly, source window was captured, send_copy ran), but Workbench's actual window stayed underneath Trados — classic Windows foreground-stealing prevention. The OS was accepting `SetForegroundWindow` (return value non-zero) and then quietly refusing to bring the window forward, leaving it flashing in the taskbar with no visible activation. This is a documented Windows behaviour: even with the `AttachThreadInput` dance, some host apps (Trados being one) interact with the OS in ways that defeat the standard workaround on the first transition.
- Fix escalates `_bring_workbench_forward()` to apply every documented foreground-grab technique in sequence:
  1. **Synthetic Alt down + Alt up** (`keybd_event(VK_MENU, …)` pair). The MSDN page for `SetForegroundWindow` documents an exception: if the Alt key is currently pressed when the call is made, the OS grants the request unconditionally. By synthesising a brief Alt down / Alt up immediately before the call, we satisfy that exception unconditionally. The Alt key is released within microseconds so the user never sees side effects (no menu activation, no Alt+Tab dialog).
  2. **AttachThreadInput** dance (unchanged from previous versions).
  3. **`BringWindowToTop(hwnd)`** to force our window to the top of the Z-order *within the same input thread group*.
  4. **`SetForegroundWindow(hwnd)`** to request the actual foreground change. With Alt-held + thread-input-attached, the OS now accepts.
  5. **`SwitchToThisWindow(hwnd, fAltTab=True)`** as a last-resort hammer. This is a deprecated-but-functional API that bypasses some foreground rules entirely; the second argument makes it behave like a synthetic Alt+Tab switch, which is more reliable than the bare foreground-request path on Win10/11.
- Combined with v1.10.8's event-loop split (so the OS has time to process the activation between `_bring_workbench_forward` and the heavy ensure), this is everything documented Windows offers. If first-run Ctrl+Alt+C / Ctrl+Alt+L *still* fails after v1.10.9, the issue is either a Trados-specific compatibility shim we don't yet know about, or something outside Windows' documented foreground APIs entirely.


## v1.10.8 – May 13, 2026

### Fixed (First Ctrl+Alt+C / Ctrl+Alt+L still unreliable after v1.10.7's reorder)

- v1.10.7 reordered the open-tab methods to call `_bring_workbench_forward()` *before* the heavy lazy-tab ensure, on the theory that the Windows ~2-second "you may steal foreground" grace window was the bottleneck. That helped but wasn't enough: a user reported the first Ctrl+Alt+C after a fresh launch still left Workbench behind Trados about half the time.
- Deeper diagnosis: `SetForegroundWindow` is *synchronous* (returns immediately with success/failure) but its effect is *deferred*. The OS queues `WM_ACTIVATE` and `WM_PAINT` messages for our window; Workbench doesn't visibly become active until Qt's event loop processes those messages. The v1.10.7 code called `_bring_workbench_forward()` and then *immediately* called `_ensure_clipboard_top_tab()` / `_ensure_superlookup_top_tab()` on the same call frame – which blocks the Qt main thread for 1–2 seconds the first time it runs (the ensures construct heavy widgets: snippet/prompt library load, 3-column tree build, TM/termbase/MT/web-resource setup). While the GUI thread is blocked, the queued activation messages sit in the OS message queue. By the time Qt finally drains them, Windows has often moved on or the user has glanced away.
- Fix splits the work across two Qt event-loop turns:
  - **Turn 1 (synchronous, returns immediately):** `_bring_workbench_forward()`.
  - **Turn 2 (next event-loop tick, via `QTimer.singleShot(0, …)`):** `_ensure_*_top_tab()` + `setCurrentIndex` + (for SuperLookup) seed text + deferred click.
- Between the two turns, Qt's event loop runs and processes the `WM_ACTIVATE` / `WM_PAINT` messages the OS posted in response to our `SetForegroundWindow` call. Workbench actually appears on top *before* we start blocking the GUI thread on widget construction. By the time the user sees the heavy-construction freeze (briefly visible as a Workbench window that's "thinking"), the foreground transition has already completed cleanly.
- This is the third iteration on the same general bug (v1.10.6 deferred the search button click; v1.10.7 reordered ensure vs. bring; v1.10.8 splits across event-loop turns). Together they fully address the activation race – there's nothing left in the path that can be deferred or reordered. If first-run hotkeys are still unreliable after v1.10.8, the bug is elsewhere.


## v1.10.7 – May 13, 2026

### Fixed (First Ctrl+Alt+C after launch still didn't bring Workbench forward)

- v1.10.6 fixed the Ctrl+Alt+L foreground race by deferring `search_btn.click()` past the SetForegroundWindow call. But Ctrl+Alt+C had a different, more fundamental version of the same problem that surfaced only on the *very first* invocation after a Workbench restart: the user pressed Ctrl+Alt+C from Trados, the Clipboard tab activated correctly, but Workbench itself stayed behind Trados. Pressing Ctrl+Alt+C a second time worked fine.
- Root cause was in `open_workbench_to_superlookup` / `open_workbench_to_clipboard`: the original ordering called the lazy-tab ensure (`_ensure_clipboard_top_tab` / `_ensure_superlookup_top_tab`) *before* `_bring_workbench_forward()`. On a cold restart the very first ensure call constructs the widget – `ClipboardManagerWidget` loads the snippet library, builds the 3-column tree, scans the prompt library; `SuperlookupTab` brings up TM / termbase / MT / web-resource state – which can easily eat 1–2 seconds of Qt main-thread time. By the time we then reached `_bring_workbench_forward()` and its SetForegroundWindow call, we'd burned through the Windows ~2-second "you may steal foreground because you just received input" grace period and the OS silently refused our foreground request. SetForegroundWindow returned 0, no exception, no log – just a refused activation. On subsequent invocations the widget was already built so the ensure was a no-op, the foreground-grab fell inside the grace window, and everything worked.
- Fix reorders both methods so `_bring_workbench_forward()` runs *before* the ensure. The user sees Workbench pop forward instantly on whatever tab it was on; the heavy widget construction then runs against an already-foreground process (no grace-period dependency); the tab switch to Clipboard / SuperLookup happens last. The 2500ms launch-time warm-up (`_warm_up_top_tabs`) still tries to build everything proactively, but the reorder makes correctness no longer dependent on warm-up beating the user's hotkey press.


## v1.10.6 – May 13, 2026

### Fixed (Ctrl+Alt+L: search runs but Workbench window stays hidden)

- After v1.10.4 retired Sidekick, pressing Ctrl+Alt+L from another app sent the selected text to SuperLookup and the search ran correctly – but Workbench's window itself stayed behind whatever app the user came from. The user saw their old foreground app, and could only see the search result by Alt+Tab-ing to Workbench.
- Root cause: `open_workbench_to_superlookup` called `_bring_workbench_forward()` (which does the SetForegroundWindow + AttachThreadInput dance correctly) and *then immediately* called `widget.search_btn.click()` to fire the lookup. The search itself is expensive – TM hits, termbase scans, MT API calls, web-resource queries – and it pegged Qt's GUI thread for several hundred milliseconds. Windows technically marked Workbench as the new foreground (SetForegroundWindow returned success), but the OS could only queue the foreground-change repaint; the actual paint event sat behind the busy search results updating, so the user kept seeing their old foreground app. While Sidekick existed, `_bring_workbench_forward()` also dismissed Sidekick first, and that forced a Windows foreground recompute that masked the timing issue. With Sidekick gone, the bare race is visible.
- Fix defers `search_btn.click()` via `QTimer.singleShot(150, …)` instead of calling it synchronously. The text seeding (`source_text.setEditText(text)`) still happens immediately so the user sees their query in the field, but the search itself fires on the next event-loop tick after a 150ms breathing room. By that point Windows has finished the foreground transition and Workbench is visibly on top before the search starts hogging the GUI thread.

### Changed (Ctrl+Alt+C now auto-copies the user's current selection)

- Previously Ctrl+Alt+C required users to do two keystrokes: Ctrl+C to copy their selection, then Ctrl+Alt+C to open the Clipboard manager. The Ctrl+Alt+L hotkey already did this with a single keystroke (it synthesises Ctrl+C internally before opening SuperLookup), so the Clipboard hotkey inconsistency was a usability papercut.
- `_handle_clipboard_hotkey` now mirrors SuperLookup's pattern:
  1. Capture the source window (for paste-and-return).
  2. Synthesise Ctrl+C via `CrossPlatformKeySender.send_copy()` so any current selection lands on the clipboard.
  3. Wait 250ms for the OS to dispatch the keystroke and `QClipboard.dataChanged` to fire (prepending the new item to the Clipboard widget's history).
  4. Open Workbench's Clipboard tab with the just-copied text at the top of the history.
- Users who had already copied something manually before pressing Ctrl+Alt+C lose nothing: the synthetic Ctrl+C is a no-op if nothing is selected, and a harmless re-copy of the same text if something is. Users who *forgot* to copy first get the one-keystroke flow they expected.


## v1.10.5 – May 13, 2026

### Changed (Rename "AI Assistant" / "Supervertaler Sidekick" labels to "Chat")

- With Sidekick retired, the surviving in-Workbench chat surface no longer needs the qualifier. Three user-facing labels renamed for consistency:
  - Right panel tab `"💬 AI Assistant"` → `"💬 Chat"` (`Supervertaler.py`).
  - Editor right-click QuickLauncher menu entry `"💬 Supervertaler Sidekick"` → `"💬 Chat"` (two sites in `Supervertaler.py`).
  - Prompt Manager sub-tab `"💬 Assistant"` → `"💬 Chat"` (`modules/unified_prompt_manager_qt.py`).
- Internal identifiers (`assistant_tab`, `_assistant_tab_index`, `show_supervertaler_assistant`, `ai_assistant/` data directory, log prefixes like `[AI Assistant]`) are left alone in this release – they're not user-visible and renaming them would touch many call-sites for no functional gain. They can be cleaned up later if the inconsistency becomes annoying.


## v1.10.4 – May 13, 2026

### Changed (Issue #199 phase 4: Sidekick retired – its four tabs now live in Workbench)

- Sidekick (the always-on-top floating assistant window) is no longer constructed at launch. All four of its tabs were already (or are now) available inside Workbench itself:
  - **Chat** → Workbench's right panel "💬 AI Assistant" tab (`prompt_manager_qt.assistant_tab`, present since well before this rejig)
  - **Clipboard** → Workbench's top-level "📋 Clipboard" tab (migrated in v1.10.0)
  - **Voice** → Workbench's top-level "🎤 Voice" tab (migrated in v1.10.0)
  - **SuperLookup** → Workbench's top-level "🔍 SuperLookup" tab (migrated in v1.10.0)
- The `Ctrl+Alt+K` global hotkey (formerly "open Sidekick with selected text") is no longer registered – the chord is now free for any other app's use. The Sidekick-as-floating-window concept is gone; the unified Workbench is the only surface. Use `Ctrl+Alt+L` (SuperLookup), `Ctrl+Alt+C` (Clipboard), or the system tray quick-jump entries to summon the relevant Workbench tab.
- Defensive cleanup is minimal-impact: this release sets `self._floating_assistant = None` at construction time so the 23 surviving `getattr(self, '_floating_assistant', None)` call-sites silently no-op rather than crash. The dead module file (`modules/floating_assistant.py`) and the now-unreachable code paths in `modules/snippet_library.py`, `modules/chat_view_widget.py`, etc. (the `is_sidekick=True` branches) are scheduled for a follow-up sweep. Keeping them in the tree this release is intentional – they can't be invoked, so they cost nothing, and pulling them in the same commit as the user-visible change would have made the diff harder to bisect if something broke.
- Trados bridge (`sidekick_bridge_server.py`) is preserved: its HTTP server is what carries QuickLauncher prompts from Trados Studio to Workbench, and the module name is historical. It now hands off to the Workbench Chat panel instead of the Sidekick window. The module will be renamed to `workbench_bridge_server.py` in the follow-up sweep.


## v1.10.3 – May 13, 2026

### Fixed (First clipboard-conversion paste-back still failed – AHK pre-warm wasn't the real culprit)

- v1.10.2's AHK pre-warm was a red herring. Conversions still failed on the first attempt after launch, with a more specific symptom: Workbench hid, the converted text was on the clipboard, but the keyboard cursor was *nowhere* – no window had focus, and the Ctrl+V went into the void. Manually clicking back into Trados and trying again worked. Root cause was the ordering in `ClipboardManagerWidget._paste_to_source`:
  1. `hide()` Workbench
  2. `QTimer.singleShot(100, …)` → `activate_foreground_window(trados)` → `send_paste()`
- That worked for Sidekick because `Qt.WindowType.Tool` windows ride on the parent's foreground slot and never own foreground in their own right – the moment Sidekick hid, the source window was already foreground, and `SetForegroundWindow` had nothing to do. Workbench is a regular top-level window, so it *does* own foreground while visible. Once it hides, the OS picks the next z-order window for foreground (sometimes that's the source, sometimes – racily – it's the desktop or nothing). 100 ms later when `activate_foreground_window(source)` calls `SetForegroundWindow(trados)`, Windows checks the foreground-process rule: only the *current* foreground process may grant another window foreground (or one of the AttachThreadInput exceptions). By that point Workbench is no longer foreground, so the OS silently refuses the switch. The dance failed quietly, no exception, just no focus change → user types and nothing happens.
- Fix reorders the same operations so that the focus switch happens *while Workbench still owns foreground*:
  1. `activate_foreground_window(source)` – Windows grants the switch because the caller (Workbench's main thread) currently owns foreground.
  2. `hide()` Workbench – source is already foreground, hide() can't disturb it.
  3. `QTimer.singleShot(150, send_paste)` – give the OS a beat to settle, then send Ctrl+V to the now-foreground source window.
- Bumped the post-activate delay from 100→150ms because first-activation-after-launch window-manager bookkeeping is measurably slower than steady-state. On subsequent activations the larger margin is invisible.
- Removed the AHK pre-warm from v1.10.2: it didn't hurt, but it also didn't fix anything – the cold-spawn theory turned out to be wrong (the keystroke wasn't slow, it was hitting the wrong target). Pre-warm code is kept (it doesn't cost much) but is no longer the load-bearing fix.


## v1.10.2 – May 13, 2026

### Fixed (First clipboard-conversion paste-back failed; tray icon was the low-DPI .ico)

- Sidekick-retirement phase 3 (issue #199) shipped the 3-column Clipboard tab with paste-and-return text conversions. Two follow-up issues surfaced on first real-world use:
  - **First paste-back after launch failed silently.** Triggering a Text Conversion (e.g. "Change case → UPPERCASE") wrote the converted text to the clipboard correctly but did not perform the Ctrl+V back in the source app — only on the *first* attempt after starting Workbench. Subsequent attempts within the same session worked. Root cause: the paste step spawns AutoHotkey via `CrossPlatformKeySender.send_paste()`, and the AHK binary's first cold-start spawn from disk takes ~1.5–2 s — long enough that the QTimer-scheduled paste raced ahead and the source window had already lost focus by the time AHK actually started. Subsequent spawns reuse the OS disk cache and complete in tens of milliseconds, hence the "only the first time" symptom. Fix adds a `_prewarm_ahk()` method called from the 2.5 s `_warm_up_top_tabs` warm-up: a background thread spawns AHK once with a no-op `ExitApp` script, populating the disk cache before the user ever triggers a real conversion. No-op on non-Windows. The conversion code path itself is unchanged — just the cold-start latency is paid up front during idle warm-up instead of inside the user's first action.
  - **Tray icon was rendering blurry on the Windows tray.** `_setup_tray_icon` was falling back to `assets/icon.ico`, which contains only a 16-px frame that Windows then awkwardly upscales on hi-DPI tray rows. Prefer the dedicated PNG family (`icon_24.png` → `icon_16x16.png` → `icon_128.png`) before falling back to `.ico`. Qt's `QIcon` picks the best available frame at paint time, so the tray gets a crisp 24- or 32-px rendering on every DPI scale. Window icon, taskbar icon, and Sidekick paths are unchanged.


## v1.9.490 – May 12, 2026

### Fixed (Phrase bilingual DOCX import still rejected short files via the omnibus detector)

- Same user, same file: `Praxe_PC-cs-de_de-TR.docx` (63 segments, 8 grid columns) was rejected again on import despite v1.9.487's fix, with the same warning. Root cause: there are two Phrase detectors. The dedicated `detect_phrase_docx` in `modules/phrase_docx_handler.py` was fixed in v1.9.487 to drop the `> 100 rows` threshold and accept 7- or 8-column tables. But `import_phrase_bilingual` in `Supervertaler.py` calls `detect_bilingual_docx_type` in `modules/trados_docx_handler.py` — the omnibus detector that dispatches Trados / CafeTran / Phrase / memoQ — and *that* one still had the old `len(table.rows) > 100 and cells >= 7` check. v1.9.487 had only updated one of the two paths.
- Fix aligns the Phrase branch in `detect_bilingual_docx_type` with the corrected logic: `len(rows) >= 2 and len(cells) in (7, 8) and ':' in first_cell`. Single source of truth would be cleaner, but this is the minimum change that unblocks the user; consolidation can come later.


## v1.9.489 – May 11, 2026

### Fixed (Tabs still centred on macOS — v1.9.486 fix was ineffective)

- v1.9.486 added `QTabWidget::tab-bar { alignment: left; }` to the global app stylesheet to left-align tabs on macOS. The rule looked right but was completely ineffective because of two stylesheet-precedence issues that I missed:
  - `modules/theme_manager.py:apply_theme()` calls `app.setStyleSheet(stylesheet)` and **replaces** the entire app stylesheet — wiping out the alignment rule the moment any theme is applied.
  - Several individual `QTabWidget` instances (e.g. `main_tabs` in `Supervertaler.py:9666`) call their own `setStyleSheet(...)`, which shadows the app stylesheet for that specific widget. Even if the app-level rule survives the theme, it can't reach those widgets.
- Proper fix lives in the existing `_NoFocusRectStyle` QProxyStyle: extended to also override `styleHint(SH_TabBar_Alignment)` returning `Qt.AlignmentFlag.AlignLeft`. Style hints are queried by every `QTabBar` at paint time and are **not** affected by stylesheets, so this path reaches every tab bar in the app regardless of theme changes, per-widget overrides, or future stylesheet additions.
- The previous CSS rule is kept as belt-and-braces but the proxy-style override is the load-bearing piece.


## v1.9.488 – May 11, 2026

### Fixed (Phrase bilingual DOCX import shifted target → status column by one)

- Follow-up to v1.9.487: a user pointed out the file they sent had 7 visible columns in Word, not 8. Investigation showed the underlying DOCX has 8 *grid* columns, but the Source column uses `<w:gridSpan w:val="2"/>` to span two adjacent grid positions. python-docx surfaces this as `row.cells[3] is row.cells[4]` — the same cell object returned twice — while Word shows a single visual Source column. v1.9.487 made the detector accept these files, but the parser still used hardcoded `cells[4]` as Target, which was actually a *duplicate of Source*. As a result, every 8-grid-column Phrase file silently shifted Target → Status → Comment by one column. Untranslated rows looked fine (source = "target" because both indices pointed to the same source cell); the moment the user translated and exported, their work would have been written to the source-duplicate slot — corrupted on round-trip.
- Fix introduces `PhraseDOCXHandler._unique_cells(row)` that deduplicates `row.cells` by Python object identity. Merged-source duplicates collapse to one entry, leaving 7 logical columns in either layout: `[ID, ICU, #, Source, Target, Status, Comment]`. Both the extract path (`extract_source_segments`) and the write path (`update_targets`) now use this helper, so 7- and 8-grid-column files behave identically end-to-end.
- 7-grid-column files (no merge) are unaffected — `_unique_cells` returns the same 7 cells in the same order.


## v1.9.487 – May 11, 2026

### Fixed (Phrase bilingual DOCX import rejected valid files)

- A user reported their Phrase-exported bilingual DOCX (`Praxe_PC-cs-de_de-TR.docx`, 63 segments) being rejected with `"This file does not appear to be a Phrase bilingual DOCX. Expected format: Multiple tables with 7-8 columns and segment IDs containing ':'."` even though the file was genuinely a Phrase export with valid segment IDs (e.g. `SSOMDWjYi5xvD7wq_dc10:0`).
- Two bugs in `modules/phrase_docx_handler.py`:
  - **The detector required more than 100 rows in the content table.** Short documents (one-page certificates, single-section files) routinely have fewer. The user's file had 63 rows. Threshold lowered to ≥ 2 rows (the colon-in-first-cell test is distinctive enough on its own — header rows in other tables use `ID` or are empty).
  - **The detector required exactly 7 columns.** Newer Phrase exports add a trailing column → 8. The parser already accepted ≥ 7, but the detector rejected before the parser got a chance. Now both accept 7 or 8.
- The error message also rewritten to describe the actual signal (column count plus segment-ID format with an example) rather than the old vague "Multiple tables" wording.


## v1.9.486 – May 11, 2026

### Fixed (Tabs centred on macOS instead of left-aligned)

- All `QTabWidget` tab bars now left-align their tabs on macOS, matching the Windows / Linux layout. Affects the main top-level tabs (Editor / TMs / Termbases / AI / Settings) and every nested sub-tab and sub-sub-tab. The macOS Fusion style we already switch to in v1.9.452 (because the native `QMacStyle` ignores `setExpanding(False)`) still centred tabs by default; the fix is a one-line global stylesheet rule — `QTabWidget::tab-bar { alignment: left; }` — applied app-wide in `Supervertaler.py:app.setStyleSheet(...)`.


## v1.9.485 – May 11, 2026

### Fixed (Floating Assistant / Sidekick failed to load on v1.9.484)

- The AutoPrompt cleanup in v1.9.484 removed the `show_autoprompt` constructor parameter from `ChatViewWidget` and updated the two callers that passed `show_autoprompt=True`. A third caller in `modules/floating_assistant.py` still passed `show_autoprompt=False`, which threw `TypeError: ChatViewWidget.__init__() got an unexpected keyword argument 'show_autoprompt'` during Sidekick construction. The error was caught and logged (`⚠ Failed to create Floating Assistant`) so the rest of the app still ran, but the Sidekick was unavailable for the entire session. Fix is a single deletion of the now-redundant kwarg.

### Fixed (Okapi DOCX merge crashed with NoSuchElementException on documents with inline runs split mid-sentence)

- A Mac user reported their docx export aborting with `Okapi merge failed, falling back to standard: None`. Sidecar log showed Okapi's `BlockTextUnitWriter.writeCode` popping from an empty tag stack — `ArrayDeque.pop` threw `NoSuchElementException` — because our combined translation contained a dangling `</run2>` with no matching opener. Root cause: when SRX segments a paragraph at a sentence boundary that falls inside an inline run (e.g. `<run1>GENERAL CAUTION</run1><run2>:  </run2>Read this entire ...`), the closing `</run2>` ends up at the start of segment 2, and the source-to-cf converter passes it through verbatim. Our `/merge` sub-segment combiner then concatenates two translations into a string that Okapi can't reconstruct.
- Fix is a one-line strip in the combiner: `combined = re.sub(r'</?run\d+>', '', combined)`. `<run\d+>` are Okapi-internal markers; user-facing translations only use `<cf>`/`<b>`/`<i>`/etc. Stripping any that leak through is safe because Okapi rebuilds the proper inline-code structure from the source codes table — our job is only to deliver the translated text + content tags.
- The fallback path (using the original document as a template) still kicks in for any remaining /merge failures, so users never lose their translation — they just lose Okapi's higher-fidelity inline-code reconstruction for that specific document.

### Fixed (Okapi sidecar download URL still pointed at v0.1.6)

- `modules/okapi_sidecar.py:EXPECTED_VERSION` has been `"0.1.7"` since v1.9.472 but `INSTALLER_URL_JAR_ONLY` was still pinned to the v0.1.6 asset at the `v1.9.416` release tag. pip-installed users on macOS Intel / Linux (the platforms that go through the JAR-only download path) ended up with a v0.1.6 sidecar and a "Sidecar JAR reports v0.1.6 but client expects v0.1.7 – rebuild required" warning on every launch. Updated URL to the v0.1.7 JAR now attached to the v1.9.484 release.
- Caveats: the Windows-with-bundled-JRE (`INSTALLER_URL_WINDOWS`) and macOS-ARM64-with-bundled-JRE (`INSTALLER_URL_MACOS_ARM64`) URLs still point at v0.1.6 bundles because v0.1.7 bundles haven't been rebuilt yet. Those bundles need a separate build pass (rebuild → re-upload as release assets) and are tracked for a follow-up release. pip users on Windows / macOS-ARM64 will still see the version-mismatch warning until those bundles are refreshed.


## v1.9.484 – May 11, 2026

### Changed (AutoPrompt – button moved out of the AI chat view)

- The AutoPrompt button no longer lives at the top of the AI Assistant chat view. Its previous styling – 11pt bold white on a solid full-width blue bar – read as a section heading rather than an action, and a user pointed out it was easy to miss. It now sits in the **Prompt Library toolbar** in the Prompt Manager sub-tab as **"✨ AutoPrompt"**, next to **+ New** and **📁 New Folder**, because all three create a prompt entry – manual, folder, or auto-generated. Tooltip explains what it does on hover.
- After AutoPrompt creates a prompt, the user is now navigated straight to it: the AI tab switches to **Prompt Manager**, the new prompt is selected in the Prompt Library tree (parents auto-expanded, scrolled into view), and the prompt's content loads into the Prompt Editor pane on the right. Previously the user was left in the chat view with only a confirmation message and had to click around to find the prompt that was just created.
- Terminology consistency: the panel title that read **"Active Prompt"** is now **"Custom Prompt"**, matching the field label inside it (`Custom Prompt ⭐:`). The chat confirmation message changed from "and activated as primary" to **"and set as the active Custom Prompt ⭐"**, and the status-bar log message changed from `✓ Set primary: X` to `✓ Set Custom Prompt ⭐: X`. One concept, one set of words.
- `ChatViewWidget` is now leaner – the `show_autoprompt` constructor flag and `autoprompt_requested` signal were removed since no caller used them after the move.
- Internal cleanup: the unused `_create_header()` method in `unified_prompt_manager_qt.py` (76 lines, never called) was deleted. It was a near-duplicate of `_create_library_buttons()` and caused real "wrong twin" confusion when the AutoPrompt button was first added to it instead of the live toolbar.


## v1.9.483 – May 11, 2026

### Changed (SuperLookup overhaul – responsiveness, deferral, focus race fix)

The Sidekick's SuperLookup tab was unresponsive on every Ctrl+Alt+L press, sometimes for long enough that Python crashed. Root cause was a stack of issues all hitting the main Qt thread at once on every search. Fixed in layers:

- **TM and termbase searches now run on a background `QRunnable` worker.** The worker uses a thread-local SQLite reader connection (new `DatabaseManager.get_reader_connection()` infrastructure with WAL + busy_timeout=15s + query_only=1) so it can read while Trados (cross-process) is writing without contention. Cancellation flag means a second Ctrl+Alt+L press immediately abandons the in-flight search instead of letting stale results overwrite the new ones. Hard cap of 500 termbase results prevents UI death on pathological matches.
- **MT providers (DeepL, Google, Microsoft, Amazon, ModernMT, MyMemory) now run in parallel** via `concurrent.futures.ThreadPoolExecutor` instead of one-at-a-time. Per-provider timeout dropped to 5s, overall batch capped at 6s. In the Sidekick context (where MT is handled separately by QuickTrans's own worker), `_perform_mt_lookup` now early-outs entirely – it was making N HTTP calls per Ctrl+Alt+L press and discarding the results. Was the dominant freeze cause.
- **Web Resources search is fully deferred until the user opens the Web Resources sub-tab.** Creating 16+ `QWebEngineView` instances during `perform_lookup` (each spawns a Chromium subprocess) was 3–8s of main-thread block – exceeding Windows DWM's "Not Responding" threshold and ghosting the Sidekick window. Now we just set a pending flag; `on_results_tab_changed` fires the search lazily when the tab is actually viewed. Users who never open Web Resources never pay the cost.
- **QuickTrans MT fan-out is deferred unless the user's landing tab is QuickTrans.** Same reasoning – spawning N provider calls and creating a hidden `WindowStaysOnTopHint` `MTQuickPopup` dialog every Ctrl+Alt+L press was wasted work plus a Z-order disturbance. The MT fetch now fires on demand when the user navigates to the QuickTrans sub-tab.
- **Sidekick's window flags now include `Qt.WindowType.WindowStaysOnTopHint`.** The actual fix for the "Sidekick gets buried by Trados while results load" symptom – Trados Studio aggressively reclaims foreground after a global hotkey fires, and no amount of `SetForegroundWindow` / `AttachThreadInput` / topmost-flip-then-drop could win that race because Trados re-fires its claim a few hundred ms after Sidekick appears. Putting Sidekick in the topmost Z-order class durably prevents the bury. The flag only matters while Sidekick is visible – Escape hides it and the normal Z-order is restored.

### Added (Configurable Ctrl+Alt+L landing tab)

- New radio-button group at the top of **SuperLookup → SuperLookup Settings** lets the user pick which sub-tab Ctrl+Alt+L lands on: **QuickTrans**, **TMs**, **Termbases** (default), or **Web Resources**. Persists in `general_settings.json` under `superlookup_landing_tab`.
- The chosen sub-tab is set unconditionally on every Ctrl+Alt+L press, so the inner sub-tab is no longer sticky from the user's last visit.
- Empty-clipboard no-op fixed: pressing Ctrl+Alt+L with nothing on the clipboard now opens the Sidekick window correctly (just doesn't auto-search). Previously it silently returned without showing the window.
- Uses `CheckmarkRadioButton` (existing custom class) for the radio buttons since the global theme stylesheet strips QRadioButton's native checked-state indicator – plain `QRadioButton` rendered as squares with no visible "selected" feedback.

### Changed (QuickTrans settings moved to SuperLookup Settings)

- The QuickTrans configuration page (provider on/off + LLM model selectors + Save) has moved out of **Workbench Settings → ⚡ QuickTrans** into a new **⚡ QuickTrans** sub-sub-tab inside **Sidekick → SuperLookup → SuperLookup Settings**, so it sits next to where it's actually used.
- Workbench Settings → QuickTrans is now a stub page with a brief explanation and a one-click **"Open in Sidekick"** button that opens Sidekick and navigates to the new location (outer SuperLookup, inner SuperLookup Settings, sub-sub-tab QuickTrans). Discoverability for users looking in the old place is preserved.
- `SuperlookupTab.__init__` now takes an `is_sidekick=False` flag. Only the Sidekick instance mounts the QuickTrans widget – keeps `_mtql_checkboxes` / `_mtql_llm_combos` on the main window unambiguous (single source of truth, single set of widget references).
- The widget is wrapped in `_wrap_in_scroll` when mounted in the Sidekick context so the form scrolls vertically inside the compact floating window.

### Fixed (status line clobbering)

- `_perform_web_search` no longer overwrites the SuperLookup status line with `Searching all web resources for 'X'` when invoked from `perform_lookup`. The status line is now owned solely by the background search worker and shows the final `✓ Found N results (TM: X, Termbase: Y)` correctly. Web search keeps its own status updates when invoked from the Web Resources tab's "Search All" button or other entry points (new `silent` parameter).


## v1.9.482 – May 9, 2026

### Changed (Add Term and Edit Termbase Entry are now one dialog)

- The two near-duplicate dialog classes that handled adding and editing termbase entries (`TermMetadataDialog` and the old `TermbaseEntryEditor`) have been merged into a single canonical class, `TermbaseEntryEditor`, living in `modules/termbase_entry_editor.py`. ~660 lines of duplicated layout code removed from `Supervertaler.py`.
- Mode is determined automatically by whether a `term_id` is supplied. If you pass `term_id`, you get the **Edit Termbase Entry** dialog (loads existing data, Delete button, Save UPDATEs the row). If you don't, you get the **Add Term to Termbase** dialog (Save just accepts and the caller does the INSERT). All five existing call sites continue to work unchanged.
- One concrete consequence: any future tweak to the dialog (a new field, a different button arrangement, a colour change) only has to be made once instead of being kept in sync across two files.
- Backward-compat alias `TermMetadataDialog = TermbaseEntryEditor` is kept in `Supervertaler.py` so any external scripts that imported the old name still resolve.


## v1.9.481 – May 9, 2026

### Changed (Add Term and Edit Termbase Entry dialogs now look identical)

- The metadata block in **Edit Termbase Entry** now uses the same `QFormLayout` (label-on-the-left) shape as **Add Term to Termbase**. Definition / Domain / Notes / URL / Client / Project all line up neatly in a column with their labels left-aligned, instead of stacking label-above-field. Both dialogs now feel like the same screen with two different titles.
- **Add Term to Termbase** button relabelled from "Add to Termbase" to "💾 Save" with the same floppy-disk icon the Edit dialog uses, so the bottom-right button reads the same in both.


## v1.9.480 – May 9, 2026

### Changed (Termbase terminology – user-facing strings now consistent)

- All user-facing copy in the Workbench that previously said "glossary" now says "termbase" – button labels, menu items, dialog titles, status messages, group box titles, tooltips, voice-command phrases, the assembled LLM prompt's `# TERMBASE` section, the analyser's "consider using a termbase…" suggestion, the `setup.py` / `pyproject.toml` description, and so on. ~130 strings touched across 11 modules.
- Internal identifiers (variable names, function names, class names, settings keys, the SQL `glossaries` legacy table name, sound-event keys persisted in JSON settings, GitBook help-topic slugs) are deliberately left as-is so existing user settings, help URLs, and the public Python API don't break.
- Voice-command phrase change worth knowing: the trigger phrase to add a term to the active termbase is now "termbase" (or "add term" / "add to termbase"). The old phrase "add to glossary" no longer works.
- The assembled LLM prompt now emits a `# TERMBASE` section header instead of `# GLOSSARY`. If you have custom prompt templates that reference the literal `# GLOSSARY` heading, update them to `# TERMBASE`.


## v1.9.479 – May 9, 2026

### Changed (Edit Glossary Entry dialog – matches the new Add Term layout)

- Right-clicking a term in TermLens (or anywhere else that opens the term editor for an existing entry) now opens a dialog with the same layout as the redesigned **Add Term to Glossary** dialog: side-by-side source / target language columns, abbreviation field directly beneath each term, source synonyms living inside the source column and target synonyms inside the target column, plus a metadata block with Definition, Domain, Notes, URL, Client, Project, Non-translatable and Forbidden in the same order as the Add dialog.
- Language captions on the columns are resolved from the active project's source/target codes – e.g. "Source (English)" / "Target (Dutch)" – rather than the generic "Source" / "Target".
- All four new fields (URL, source abbreviation, target abbreviation, definition) are now persisted on save. Previous edits to existing entries silently dropped the definition because the UPDATE statement didn't reference the column.
- `load_term_data()` SELECT now uses `COALESCE(url, '')` etc. so editing rows on databases that pre-date the v1.9.478 migration no longer blows up the dialog.


## v1.9.478 – May 9, 2026

### Added (URL + per-language Abbreviation fields on the Add-Term dialog)

- The **Add Term to Glossary** dialog now exposes two more fields that have been part of the Trados plugin's term editor for a long time but never made it into the Workbench:
  - **Abbreviation** for each language, sitting directly beneath the term field in its respective language column. Useful for terms like "European Patent Office (EPO)" where you want to record both the long form and the abbreviation as a single linked entry.
  - **URL** in the metadata block beneath Notes. For a reference link to a Wikipedia article, IATE record, internal style guide, etc.
- Schema migration adds three new columns to `termbase_terms` (`url`, `source_abbreviation`, `target_abbreviation`) on next launch. Existing rows get NULL for the new fields.
- `TermbaseManager.add_term()` accepts the new fields as keyword arguments. Callers that pre-date this change continue to work unchanged – the new params have safe `""` defaults.
- The dialog's `definition` field (added in v1.9.475) is now also actually persisted; the previous version surfaced a Definition box in the UI but didn't pass it through to the insert. Caller fixed in the same edit.

### Changed (Synonym widgets moved into their language column)

- The Source Synonyms / Target Synonyms collapsible groups used to sit stacked below the term row, taking up a lot of vertical space and making the dialog feel like a long form. They now live **inside** their respective language column – source synonyms drop directly under the source term + abbreviation, target synonyms under the target term + abbreviation. Mirrors the Trados plugin's per-language column layout.


## v1.9.477 – May 9, 2026

### Fixed (Ctrl+Alt+T typed a literal T into the active cell on Windows)

- **Add Term to Glossary** (`Ctrl+Alt+T`) now actually fires the dialog instead of letting Windows insert a capital T into the focused source/target cell. The two per-widget event filters that handle this shortcut were doing a strict-equality check on `event.modifiers()` against `Ctrl|Alt`. On Windows the `Ctrl+Alt` combo is the same scancode pair as `AltGr`, and the OS sometimes tags the event with an extra `GroupSwitchModifier` bit – the strict equality check then fails silently, the event falls through to the QTextEdit, and the literal letter gets typed.
- Fix masks the modifiers down to `{Ctrl, Alt, Shift, Meta}` before comparing, stripping `GroupSwitchModifier` and `KeypadModifier`. Same modifier-tolerance pattern Workbench already uses for arrow-key navigation. Applied to both `ReadOnlyGridTextEditor` (source side) and `EditableGridTextEditor` (target side – the latter was still on the old `Ctrl+E` binding by mistake; both editors now share the new shortcut).


## v1.9.476 – May 9, 2026

### Fixed (Ctrl+Alt+T shortcut collision)

- **Toggle Tag View** moved from `Ctrl+Alt+T` to `Ctrl+Shift+H`. v1.9.475 reassigned `Ctrl+Alt+T` to "Add term to glossary" (matching the Trados plugin shortcut), which collided with the existing Toggle Tag View binding. The new `Ctrl+Shift+H` matches Trados Studio's own default for the analogous "Show formatting characters / Tag display" toggle, so muscle-memory consistency with Trados is preserved.
- Tooltip text on the WYSIWYG / Tag toolbar buttons updated, plus the keyboard-shortcuts settings page, plus the inline docstrings.


## v1.9.475 – May 9, 2026

### Changed (Add-term-to-glossary dialog redesigned + shortcut Ctrl+Alt+T)

- **The "Save to Glossary(s)" checkbox list is gone.** Choosing a target glossary on every term-add was redundant – the Termbases tab already has a Read/Write toggle that captures the same intent more durably. The dialog now writes the term to **every glossary that is active for the current project AND marked Write**. If no glossary is marked Write, you get a clear error pointing at the right toggle.
- **Source and target term fields are editable.** They were read-only labels before; now they're proper QLineEdits so you can correct the spacing, capitalisation, or typo of the captured pair before saving. Matches the Trados plugin's editable behaviour.
- **Side-by-side layout** for the source/target term row, mirroring the Trados plugin's "Source: English / Target: Dutch" two-column header. The column captions pick up the current project's source/target language names automatically.
- **New `Definition` field** between the term row and the Domain row – the database column has always been there, the dialog just never exposed it. Use it for the brief gloss; keep usage notes / URLs in `Notes`.
- **New `Non-translatable (keep source text in target)` checkbox** alongside the existing `Forbidden term` one. Lights up the `is_nontranslatable` column on the row, so the entry shows up correctly when filtering by NT lists.
- **Header label removed** ("Add term pair to glossary") – the dialog title bar already says "Add Term to Glossary", saying it twice was clutter.

### Changed (Keyboard shortcut)

- **`Ctrl+Alt+T`** now opens the Add-Term dialog (was `Ctrl+E`). Matches the equivalent shortcut in the Supervertaler for Trados plugin so muscle memory transfers between products. The Settings → Keyboard Shortcuts default is updated; existing custom binding overrides are unaffected. Tooltip strings, right-click menu labels, and welcome-tip messages all reflect the new shortcut.


## v1.9.474 – May 9, 2026

### Removed (Modular features architecture and the Features Settings tab)

- The **Features tab** under Settings is gone. It used to show a list of optional feature modules (AutoFingers, Local Whisper, Web Browser, PDF Rescue, MT Providers, Hunspell Spellcheck) with their pip-extra install commands and an "installed / not installed" indicator. Every package those modules depended on has been a core dependency for some time, so the panel reduced to "everything is installed" by default and was no longer earning its keep on the Settings sidebar.
- The **first-run modular features pop-up** is gone. Earlier versions threw up a dialog explaining Supervertaler's modular architecture and routed the user to Settings → Features. New users now go straight from the data-folder setup page to a brief "you're all set!" confirmation – no architecture lecture, no follow-on tab navigation.
- The setup wizard's second page no longer mentions modular features at all. It's a one-screen "your data folder is ready" confirmation with a single "don't show this on startup" checkbox.
- `_create_features_settings_tab` (≈ 270 lines) is removed. `_show_first_run_welcome` is reduced to a no-op stub – any pending QTimer callbacks still resolve harmlessly.
- `pyproject.toml`'s `[project.optional-dependencies]` extras (`voice`, `web`, `pdf`, `mt`, `hunspell`, `local-whisper`, `windows`, `core`, `all`) are kept as empty buckets so existing scripts that do `pip install supervertaler[voice]` don't fail – but no public docs advertise them anymore. The single recommended install command is `pip install supervertaler`.

### Notes

- This is a UX simplification, not a feature removal. Everything that was previously "optional" is still installed and works exactly as before – there's just no longer a UI to toggle anything.


## v1.9.473 – May 9, 2026

### Added (Help links surfacing the Supported File Formats reference)

- New **❓ Supported file formats (online help)…** entry at the foot of **File → Import** and **File → Export**. Opens the [Supported File Formats](https://supervertaler.gitbook.io/help/import-and-export/formats) page on GitBook in your browser. The format list (DOCX, IDML, HTML, XLIFF, PO, XLSX, PPTX, plus all the CAT-tool round-trips) was previously only discoverable by digging through the menus or the help site directly; this puts it one click away from where you actually pick the format.
- The language-pair dialog inside **File → Import → Other format via Okapi…** now has the standard "?" help badge in its title bar, matching the convention used in the rest of the app (Settings tabs, dialogs, etc.). Clicking the badge also opens the Supported File Formats reference.


## v1.9.472 – May 9, 2026

### Fixed (Okapi sidecar v0.1.7: HTML, XLIFF, PO inline codes leaking as `[#$dpNN]` placeholders)

- **HTML round-trip was producing visibly-corrupted output** in v1.9.470/471. An HTML test page imported, translated, and exported via the new Okapi-via menu came back with literal `[#$dp13]`, `[#$dp14]`, `[#$dp16]…` strings rendered as visible text on the page where the original had `<a href="…">`, `<img>`, and `<button>` markup. Verified: 14 unique placeholder leaks in the round-tripped HTML, 0 in the source.
- **Root cause** was in the Okapi sidecar's `convertCodesToTags()`. It only knew how to emit friendly tag names (`<b>`, `<i>`, `<cf>`) for OOXML formatting codes (bold/italic/underline/colour). For HTML inline elements like `<a>`, `<button>`, `<span class>` – none of which match the OOXML formatting analysers – the function fell through to `fragment.toText()`, which serialises codes as raw `[#$dpNN]` Okapi placeholders. The LLM then saw the placeholders as literal text and copied them through; the merge regex (`<…>` only) didn't recognise them and they ended up as plain text in the output.
- **Fix** in the sidecar (Java, v0.1.6 → v0.1.7): both `convertCodesToTags()` and `getTagNameForCode()` now fall back to a generic numbered tag scheme – `<gN>...</gN>` for opening/closing pairs, `<xN/>` for placeholders – using the source code's id. The merge path's tag-queue lookup pairs them back to source codes by name, and a new `placeholderById` map handles `<xN/>` round-trip. Result: HTML segments now appear in the Workbench grid as e.g. `<g1>Workbench</g1>` instead of `[#$dp16]Workbench</a>`, and the merged HTML reconstructs original anchors/buttons/images byte-perfectly.
- The fix is generic – it benefits any Okapi-supported format with non-OOXML inline codes, not just HTML. XLIFF and PO files with inline tags will see the same improvement automatically.
- **`EXPECTED_VERSION = "0.1.7"`** in `modules/okapi_sidecar.py` so a stale v0.1.6 sidecar gets restarted automatically when the user upgrades.

### Notes

- **Re-import any HTML / XLIFF / PO project** that was created with the broken v1.9.470/471 sidecar – the saved segments still contain the old `[#$dpNN]` placeholder text and will round-trip badly until re-extracted.
- Sidecar source: [okapi-sidecar/src/main/java/com/supervertaler/sidecar/FilterService.java](okapi-sidecar/src/main/java/com/supervertaler/sidecar/FilterService.java).


## v1.9.471 – May 9, 2026

### Fixed (Okapi merge: dropped sentences after the first in multi-sentence paragraphs)

- **Critical fix on top of v1.9.470.** The Okapi-via round-trip introduced in v1.9.470 silently dropped every sub-segment past the first in any multi-sentence paragraph. Verified end-to-end against an IDML round-trip: an 8-segment story ended up with only 3 of the 8 translations in the merged file, and one whole italic `CharacterStyleRange` disappeared along the way.
- **Root cause** lived in the interaction between extract and merge in the Okapi sidecar. Extract applied SRX sentence segmentation as a string-level helper – producing one row per sentence in the Workbench grid for translation – but did not apply it to the underlying `ITextUnit`. When `/merge` reopened the original file the TU still had `srcSegments.count() == 1`, and the Java merge loop's `if (idx >= totalSegs) continue;` silently dropped every `MergeSegment` whose `segmentIndex >= 1`.
- **Fix** is on the Workbench side, in `_try_okapi_merge_export`: group all sub-segments per `okapi_tu_id`, sort them by `segmentIndex`, concatenate the translations with a single space, and send one entry per text unit at `segmentIndex=0`. The TU's single source segment now receives the full multi-sentence translation in one go. Whitespace at SRX sentence boundaries may pick up an extra space (LLMs tend to strip leading/trailing whitespace from each sub-segment translation), but every translation now reaches the merged file.
- A new log line, `Combining sub-segments for merge: N text-unit(s) had multiple sentences (concatenated for /merge)`, makes the workaround visible at export time. Single-sentence projects ignore this code path entirely.

### Notes

- v1.9.470's external entry points (the import/export menu items, the test IDML round-trip) are unchanged. This release is a behaviour fix, not a feature addition.


## v1.9.470 – May 9, 2026

### Added (Import any format the Okapi sidecar can extract)

- New menu entry **File → Import → Other format via Okapi (IDML, HTML, XLIFF, PO, XLSX, PPTX)…**. Pick a file in any of those formats and Supervertaler routes it through the same Okapi extract → translate → merge round-trip that's been used for DOCX since v1.9.227. You translate the segments in Supervertaler, then export back to the original format via the matching new menu entry – byte-for-byte preservation of the original file structure, courtesy of the Okapi skeleton mechanism.
- New menu entry **File → Export → Original format via Okapi (IDML, HTML, XLIFF, PO, XLSX, PPTX)…**. For Okapi-imported projects, opens a Save As dialog defaulted to the original file's extension and asks the sidecar to merge the translation back. Works for any format the sidecar supports; the per-segment `okapi_tu_id` / `okapi_segment_index` round-trip metadata has been collected at import time since v1.9.227, so this is genuinely just a new entry point onto an already-tested pipeline.
- The IDML use case (raised in the public forum) now works without any user-facing Okapi conversion step. Drop the .idml file in, translate, drop it back out as .idml.

### Notes

- **Tag handling**: Okapi inline tags (`<g>`, `<bpt>`, `<ph>`, etc.) appear in the grid as their tagged-text representation. Keep them in the translation; the merge endpoint uses them to reconstruct the inline-code structure of the original file.
- **DOCX continues to have its own dedicated import / export entry** – the new Okapi-via menu is for everything *else*. Internally both paths use the same code; the separate DOCX entries are kept because they're far more commonly used and have specific dialog wording.


## v1.9.469 – May 9, 2026

### Changed (Settings UI tidy-up)

- **All settings tabs are now a consistent 1000 px wide**, left-anchored against the sidebar with the right side acting as breathing room. Previously each tab claimed only as much width as its longest internal row, so clicking between General, AI Settings, Language Pair etc. felt jumpy. Centralised in `_wrap_in_scroll`, so future settings tabs inherit the same look automatically. Width lives in a single `SETTINGS_CONTENT_MAX_WIDTH` class constant for easy tweaking.
- **Mouse-wheel guard on sliders, spin boxes, and combo boxes** across every settings tab. By default these widgets grab wheel events whenever the cursor crosses them – which meant scrolling a page could silently move the Context-window slider, change a Batch size, or flip a model dropdown. They now ignore wheel events unless the user has actually clicked into the widget. Implemented as a small `_WheelGuard` event filter installed automatically by `_wrap_in_scroll`.
- **AI Settings page**: dropped the "💡 Free vs Paid API Access" info banner that was repeating help-doc material on every visit, replaced with a single "AI Settings" page title.
- **LLM Provider radio buttons** (Settings → AI Settings → LLM Provider Selection) finally look like radio buttons. The custom widget had been rendering as a square indicator with a checkmark – which made an early reviewer think the eight providers were multi-select when they're actually mutually exclusive. Now circular indicators with a green centre dot when active. Behaviour was already correct (the underlying widget is `QRadioButton`); only the visual changed.


## v1.9.468 – May 9, 2026

### Added (Attach a Trados Studio TM as a read-only TM in Supervertaler)

- New **🔗 Attach Trados TM** button on the TM list (next to **📥 Import TMX**). Pick a `.sdltm` file – Trados can stay open at the same time, the file is read in SQLite read-only mode – and Supervertaler mirrors its translation units into the database under a dedicated `tm_id`. Tags are converted to Supervertaler's inline form (`<116>...</116>` etc.) on the way in, so a TM hit lines up with the same tag markers your imported segments already use.
- The mirror **stays in sync** with the live `.sdltm`. A 5-second timer watches the file's mtime; whenever Trados saves new or modified TUs, only the delta is pulled in (id-and-change-date filtered, no full re-read), and the entry count on the TM row updates automatically. The mtime check itself is essentially free, so idle ticks cost nothing.
- The TM is created **read-only** by default – Supervertaler never writes back to the `.sdltm`. Re-attaching the same file lands on the same TM and offers a one-click "Replace?" path that wipes and re-mirrors (use this if you'd rather refresh manually than rely on the timer).
- Schema migration adds four columns to `translation_memories` (`external_source_path`, `external_last_sync_id`, `external_last_sync_date`, `external_last_mtime`) on next launch.
- New module: `modules/sdltm_handler.py` (`SDLTMReader`).

### Added (smaller wins)

- **SDLXLIFF import progress dialog** for File → Import → Bilingual XLIFF (.sdlxliff)... and the folder variant. Large standalone files no longer look frozen during the parse phase, and the dialog supports cancel.
- **TermLens font zoomer** (small "A" / big bold "A" buttons in the TermLens panel header). Click to bump the panel font size in 1-pt steps; the value persists across sessions and stays in sync between the TermLens tab and the Match Panel copy.

### Fixed

- **Clear Filters** now keeps the previously selected segment in view. Before, if you used the source filter to jump to a segment near the end of the document and then clicked **Clear Filters**, the grid snapped back to page 1 even though the selection was still on (e.g.) segment 1000. The grid now navigates to the page containing the active segment and centres it in the viewport.

### Changed (active-row visuals)

- The **active row** now has a clean Trados-style icy-blue tint (`#CFE5F5`) across all columns – ID, Type, source, target – so it's instantly clear which segment you're on. The tint dims to a soft slate when focus moves to the AI Assistant or TermLens panel and brightens back when you return to the grid. Previously, the QTextEdit cell widgets painted over the QSS selection rule and only the segment-number column changed colour.
- **In-cell text selection** uses high-contrast black-on-white (Trados style) instead of the old pale blue. Selecting a word with the mouse or Ctrl-A now produces a clearly visible highlight in both source and target cells.

### Updated (Workbench help docs)

- New page: `workbench/translation-memory/trados-sdltm.md` documenting the Attach Trados TM workflow.
- `workbench/cat-tools/trados.md`: added a "Just want to consult a TM?" pointer near the top with a cross-link to the new page.


## v1.9.467 – May 9, 2026

### Changed (Tools tab retired – PDF Rescue and TMX Editor now open in their own windows)

- The **Tools** main tab is gone. PDF Rescue and TMX Editor were the only two remaining sub-tabs after this week's removals (Superconverter, Text Encoding Repair, Superbench), and an entire main tab for two tools wasn't pulling its weight.
- Both tools are now reachable from the **Tools menu** at the top of the window:
  - **Tools → 🔍 PDF Rescue...** opens PDF Rescue in its own resizable window.
  - **Tools → ✏️ TMX Editor...** opens TMX Editor in its own resizable window.
- Each tool window is reused if already open: clicking the menu again raises the existing window to the front rather than spawning duplicates. Closing the window destroys the underlying widget so the next open is a fresh instance.
- The Workbench's main tab strip is now: Editor / TMs / Termbases / AI / Settings (5 tabs, was 6).
- Internal: removed `create_specialised_tools_tab`, `create_pdf_rescue_tab`, `create_tmx_editor_tab`, and `_navigate_to_tool` (no callers after this change). Renamed the SuperlookupTab construction (which is needed for global hotkey registration) into a dedicated `_setup_superlookup_hotkeys` method.
- Updated all hardcoded Settings tab indices from `5` to `4` to match the new layout (View → Navigate To → Settings, post-config-import navigation, MT Settings shortcut, etc.).

### Updated (Workbench help docs)

- `workbench/tools/tmx-editor.md` and `workbench/tools/pdf-rescue.md`: "Where to find it" sections updated. Both now say "open the Tools menu at the top of the window" instead of "go to the Tools tab".


## v1.9.466 – May 8, 2026

### Removed (Superbench tool – simplifying the surface area)

- The **Superbench** sub-tab under Tools is gone, along with its Tools-menu entry. It was a multi-model LLM benchmarking workflow (translate the same segments through GPT-4o / Claude / Gemini side by side, score with chrF++, export comparison Excel). Distinct enough from the core translation workflow that the maintenance overhead wasn't justified.
- Deleted `modules/llm_leaderboard.py` (~785-line backend) and `modules/llm_superbench_ui.py` (PyQt6 panel).
- Removed `create_llm_leaderboard_tab()` from `Supervertaler.py` (~47 lines).
- Updated `workbench/reference/faq.md` in the help docs: removed the dedicated Superbench section and the eight inline mentions across feature lists, recommendations, system-requirements notes, and the roadmap. Replaced the "use Superbench to test models" recommendations with a generic "try a couple of models on a representative passage" wording.
- Internal: the generic `_navigate_to_tool("Superbench")` dispatcher silently no-ops if anything still references that tool name (no callers do after this change).

### Notes

- Same theme as v1.9.463 (Superconverter removal) and v1.9.464 (Text Encoding Repair removal): trimming features that distract from the core translation workflow.


## v1.9.465 – May 8, 2026

### Note (Project field on term entries – companion to Trados v4.19.88)

- The Workbench has had a **Project** free-text metadata field on term entries since the Workbench database migration introduced the `project` column some versions back, and the Workbench's term-entry editor already exposes it (the field appears between Notes and Client in the metadata group).
- The companion Trados plugin v4.19.88 release (same day) brings the Trados side up to parity: the column is now visible in the Trados Termbase Editor grid, in the term-entry edit dialog, and is round-tripped through the Trados-side INSERT/UPDATE path. Workbench and Trados now share the same field across the SQLite database.
- No code changes needed in the Workbench for this release; bumped here purely to keep the two products' release cadences in lockstep.


## v1.9.464 – May 8, 2026

### Removed (Text Encoding Repair tool – simplifying the surface area)

- The **Text Encoding Repair** sub-tab under Tools is gone, along with its Tools-menu entry. It detected and fixed UTF-8/Latin-1/Windows-1252 mojibake in translation files. Standard external tools (Notepad++ "Convert to UTF-8", or `ftfy` on the command line) cover the same ground, so keeping a custom in-app implementation wasn't pulling its weight.
- Deleted the three module files: `modules/encoding_repair.py` (backend), `modules/encoding_repair_Qt.py` (PyQt6 panel), and `modules/encoding_repair_ui.py` (legacy Tkinter UI, no longer wired in anywhere).
- Workbench help docs updated: `troubleshooting/import-export-errors.md` and `troubleshooting/common-issues.md` now point users at Notepad++ and `ftfy` for encoding-fix workflows instead of the (now-removed) in-app tool.

### Removed in v1.9.463 (continued)

Same theme as v1.9.463's Superconverter removal — trimming features that distract from the core translation workflow.


## v1.9.463 – May 8, 2026

### Removed (Superconverter tool – simplifying the surface area)

- The **Superconverter** sub-tab under Tools is gone, along with its Tools-menu entry. It bundled three things: an "Export project as Markdown table" button, a "Convert DOCX/TXT to Markdown" workflow (single + batch), and a TMX-conversion placeholder that was never wired up. None of these were core to the translation workflow and tracking suggested they were rarely used.
- The Markdown-table export functionality survives where it's actually useful: **File → Export → AI-Readable Markdown (.md)**, which calls the same `export_bilingual_table_markdown` method as before.
- Removed the per-document Markdown conversion helpers (`convert_document_to_markdown`, `batch_convert_documents_to_markdown`, `_convert_docx_to_markdown`, `_convert_txt_to_markdown`). 205 lines of dead code gone after this commit.
- Internal: the generic `_navigate_to_tool("Superconverter")` dispatcher silently no-ops if anything still references that tool name (no callers do after this change).


## v1.9.462 – May 8, 2026

### Fixed (Chat metadata: cost silently omitted for unknown models, masking real cost)

- `modules/llm_pricing.py: estimate_cost` now returns `None` (instead of `0.0`) when a model is not present in the pricing table. The chat reply's metadata strip distinguishes three cases: a known cost (`~$X.XX`), a genuinely-free provider like Ollama (cost omitted, as before), and an unknown model — which now displays **cost unknown** so users don't silently overlook real charges from non-curated OpenRouter models (e.g. `deepseek/deepseek-v4-pro`).
- Mirrors Trados plugin v4.19.87 on the same day.


## v1.9.461 – May 8, 2026

### Added (Capture cache-hit token counts from OpenAI and Gemini responses)

- `_call_openai_with_usage` now captures `prompt_tokens_details.cached_tokens` from OpenAI's response (the auto-cache hit count, billed at 50% of the input rate). When the response comes through OpenRouter from an Anthropic model, `cache_read_input_tokens` and `cache_creation_input_tokens` are also captured.
- `_call_gemini_with_usage` now captures `usage_metadata.cached_content_token_count` from Gemini 2.5+'s implicit-cache response (billed at 25% of the input rate).
- All three providers (Anthropic, OpenAI-shape, Gemini) now return a normalised `usage` dict with the same shape: `input_tokens` (total, including any cached portion), `output_tokens`, `cache_read_input_tokens`, `cache_creation_input_tokens`. This finishes the data-capture half of issue #8 (per-job cost in tokens and EUR) – when the cost UI lands, it can compute the true cache-aware billed cost for every supported provider with no further plumbing.

### Notes

- `input_tokens` is now consistently "total billed input including cache" across all providers (matching OpenAI and Gemini conventions). Anthropic's `usage.input_tokens` natively excludes the cached portion; we add `cache_creation` and `cache_read` back in to give the same total. Existing consumers that compute cost as `input_tokens × input_rate` will now slightly over-estimate when caching is active – the safer-than-actual direction; cache-aware cost calc using the new fields will be added with issue #8.


## v1.9.460 – May 8, 2026

### Added (Anthropic prompt caching for batch translation – ~80% input-cost reduction on cached tokens)

- Pre-translation batch runs now mark the static portion of each batch's prompt (the prompt-library template, glossary, and project context built by `prompt_manager.build_final_prompt`) with Anthropic's `cache_control: {"type": "ephemeral"}` marker, so the first batch in a run pays a one-time 1.25× cache-write surcharge and every subsequent batch within ~5 minutes pays only 0.1× of the input rate for the cached portion. For large jobs (e.g. a 1000-segment patent with full glossary context on Sonnet 4.6) this drops the API bill by roughly 60–65% overall and ~80% on the cached portion alone.
- The batch worker now sends the static instructions as a `system` prompt and the per-batch segment list as the `user` message, so the cacheable prefix is byte-stable across batches even when the last batch has a different segment count. Single-shot callers (chat, single-segment translate) still pass `enable_prompt_caching=False` to avoid the 1.25× write surcharge on calls that won't be reused.
- Anthropic native: `system` is sent as a content block with `cache_control: ephemeral` via the official Python SDK.
- OpenRouter → Anthropic: same marker passed through in the OpenAI-compatible content-array form, so OpenRouter forwards it correctly to the underlying Anthropic provider.
- Other providers (OpenAI, DeepSeek, Gemini 2.5+) get implicit automatic caching at the provider layer because the system prompt is now byte-stable across batches; no marker required. Mistral / Grok / Ollama: no caching available, the flag is a no-op.
- The `usage` dict returned from `translate_with_usage()` now also captures Anthropic's `cache_creation_input_tokens` and `cache_read_input_tokens` so cost surfacing (issue #8) can compute the real billed cost with cache discounts applied.

### Fixed (cost estimator: Claude Opus 4.6 / 4.7 priced at 1/3 the real rate)

- `modules/llm_pricing.py` had Opus 4.6 and 4.7 listed at $5 / $25 per million input/output tokens. Anthropic's published rate for Opus 4.x is $15 / $75 per million – the table was off by 3×, causing the in-app cost estimate to under-shoot the real bill on Opus calls. Corrected the rate in both the `claude` and `openrouter` (anthropic/claude-opus-*) pricing entries. Sonnet 4.6 ($3 / $15) and Haiku 4.5 ($1 / $5) were already correct.

### Notes

- These changes mirror the equivalent Trados plugin v4.19.86 release on the same day, so the two products are again in lock-step on cost-relevant behaviour.


## v1.9.459 – May 8, 2026

### Changed (Sidekick snippets – simpler model: filename = label, body = inserted text)

- **The eight default Special Characters snippets are renamed: filename now matches the displayed glyph label.** Previously a pair of fields was needed – an ASCII-safe filename (`Maths symbols.md`) plus a `name:` line in YAML front matter holding the unicode label (`± × ÷ ≠ π ∞`). After the rename the on-disk filename *is* the label (`± × ÷ ≠ π ∞.md`) and the file body is just the inserted text – no front matter at all. To customise an entry, you just rename the file or edit the body in any text editor.
- **One-shot migration in [`snippet_library.py`](modules/snippet_library.py)** moves existing user installations onto the new naming. For each of the eight known default filenames (`Maths symbols.md`, `Arrows.md`, `Currency.md`, etc.) the seeder checks the user's snippet folder: if the legacy file is still there and the new glyph filename does not exist yet, the body is copied to the new filename and the legacy file deleted. Any body edits the user made to a default are preserved across the rename. User-created snippets and any case where the user has *already* created a file with the new name are never touched.
- **Front-matter parser kept as a silent fallback.** Any user-authored `.md` file that includes `name:` in front matter still works exactly as before – the loader strips the front matter and uses `name:` as the displayed label if present. Only the default seeder stops emitting front matter; existing customised files are never re-touched.

---

## v1.9.458 – May 8, 2026

### Changed (Sidekick Clipboard global hotkey: Ctrl+Shift+C → Ctrl+Alt+C)

- **Default global hotkey for "Open Sidekick – Clipboard tab" changed from `Ctrl+Shift+C` to `Ctrl+Alt+C` (= ⌘⌥C on macOS).** The old default was unreliable on Windows because `Ctrl+Shift+C` is widely claimed by other apps – Chrome / Firefox / Edge bind it to "Inspect element" in their DevTools, several IDEs use it for case conversion or comment commands, and Microsoft apps occasionally grab it for paste-format. With Workbench installed alongside any of those, the global hook would silently lose to whichever app was in the foreground.
- One-shot migration in [`shortcut_manager.py`](modules/shortcut_manager.py) drops a custom value matching the old `Ctrl+Shift+C` default so the new `Ctrl+Alt+C` default takes effect on next launch. Anyone who'd already explicitly rebound the shortcut to something else is left untouched.

---

## v1.9.457 – May 7, 2026

### Changed (AutoFingers voice commands – press-to-capture keystrokes, cross-platform shortcuts)

- **Voice command keystrokes now use a press-to-capture editor instead of a free-text field.** Editing a `keystroke` voice command on the AutoFingers tab used to show a `For keystroke: ctrl+s` placeholder and require typing the modifier names by hand – which meant Mac users had to mentally translate "type ctrl+a here, but it'll fire ⌘A" or, worse, type `cmd+a` and discover that none of the shipped defaults worked because they all contained `ctrl`. Replaced with a [`KeystrokeCaptureEdit`](modules/voice_command_dialog.py) widget: click the field, press the keys you actually want to send, and the field shows the platform-native symbols (⌘⇧⌥⌃ on macOS, Ctrl+Shift+Alt elsewhere). Same widget pattern as the Keyboard Shortcuts settings dialog.
- **macOS keystroke dispatcher now follows Qt's Ctrl↔Cmd swap convention.** [`platform_helpers._keystroke_to_applescript`](modules/platform_helpers.py) used to map `ctrl` → `control down` literally, so a stored `ctrl+s` fired `⌃S` (which most Mac apps don't bind) instead of `⌘S` (the actual Save shortcut). Now `ctrl` → `command down` and `meta` → `control down`, matching the rest of the Workbench's macOS handling. The dispatcher still accepts `cmd` / `command` directly for users who'd prefer to be explicit. Net effect: every shipped voice command (`ctrl+z`, `ctrl+s`, `ctrl+o`, etc.) now does the right thing on Mac without any per-platform JSON files.

---

## v1.9.452 – May 7, 2026

### Fixed (macOS pip install of v1.9.451 broken: vosk wheel pin)

- **`pip install supervertaler==1.9.451` failed for every macOS user** with `Could not find a version that satisfies the requirement vosk>=0.3.45`. The pin was introduced in v1.9.435 when Vosk was promoted from optional to core dependency, but vosk 0.3.45 ships wheels only for Linux and Windows – the previous version 0.3.44 has a `macosx_10_6_universal2` wheel and is the highest version that works on macOS today. Caught when building the v1.9.451 macOS DMG: PyInstaller's `pip install -e .` step failed in the build venv on a clean MacBook checkout.
- Fix at [`pyproject.toml`](pyproject.toml) relaxes the pin to `vosk>=0.3.44`. No functional change for Linux/Windows users (their resolver still picks 0.3.45+ when available); macOS users now get 0.3.44 instead of a hard install failure. The 0.3.45 → 0.3.44 difference is a maintenance bump in vosk itself with no Supervertaler-relevant features – we'd been arbitrarily on the latest version without needing anything from it.

---

## v1.9.451 - May 6, 2026

### Fixed (SuperLookup Web Resources sidebar ignored global UI scale)

- **The Web Resources sidebar list inside Sidekick (IATE, Linguee, ProZ.com, Reverso, Google, Wikipedia, etc.) stayed at its hardcoded 9 pt size even at 150% global UI scale**, while every other Sidekick element scaled correctly. Reported by Michael with a screenshot. Cause: `SuperlookupTab` lives in [`Supervertaler.py`](Supervertaler.py) (not `floating_assistant.py`), so the v1.9.449 Sidekick refactor didn't reach it.
- Fix at [`Supervertaler.py`](Supervertaler.py): the SuperLookup Web Resources sidebar – header (`🌐 Resources`), each resource button, the "Search All" button, the Mode label, the "Open in Browser" button, and the language info label – now all use `scaled_pt(N)` from `modules.ui_scale`. Also added the import at the top of the file. The 9 pt resource buttons now render at 13.5 pt at 150% scale, matching the rest of the Sidekick chrome.

---

## v1.9.450 - May 6, 2026

### Fixed (UI scaling – v1.9.449 follow-up)

Two distinct bugs surfaced after the v1.9.449 Sidekick scaling work, both reported by Michael while testing 150% scale on the MacBook.

- **`modules.ui_scale.scaled_pt()` always returned 100.** The helper looked at `<user_data>/settings.json`, but the unified settings file actually lives at `<user_data>/workbench/settings/settings.json`. Helper silently fell through to its default → no scaling reached Sidekick or the Clipboard widget even though the slider was set to 150%. Fix at [`modules/ui_scale.py`](modules/ui_scale.py) tries the unified path first and falls back to the old top-level location for legacy installs.

- **Grid source/target text didn't scale, and grid zoom (Ctrl+= / Ctrl+-) didn't work either, but only at non-100% UI scales.** [`ThemeManager.apply_theme`](modules/theme_manager.py:328) emits an app-level `QTextEdit { font-size: Xpt; }` stylesheet rule whenever `font_scale != 100`, and Qt's stylesheet cascade ranks app-level rules higher than `widget.setFont()`. So the per-cell setFont calls in `apply_font_to_grid` silently lost: source/target stayed at the scaled rule's size while segment-number and type columns (plain `QTableWidgetItem`s, untouched by the QTextEdit selector) grew normally – producing the "numbers huge, text small" mismatch. Same mechanism broke grid zoom: changing `default_font_size` and re-running `setFont` had no visible effect. (At 100% scale the rule isn't emitted, which is why the bug only appeared once a user nudged the slider.)
- Fix at [`Supervertaler.py`](Supervertaler.py): `ReadOnlyGridTextEditor` and `EditableGridTextEditor` now own their font-size in their own stylesheet via a new `apply_grid_font_size(size_pt)` method, which a widget-level rule wins over the app-level one. The editors also gained a `_rebuild_grid_stylesheet` helper that composes the cell stylesheet from current bg colour + font size, so `set_background_color` (alternating row colours) no longer wipes the focus border or font size when it runs. `apply_font_to_grid` and the cell-creation paths in `load_segments_to_grid` both call `apply_grid_font_size(self.default_font_size)` alongside the existing `setFont`, so grid zoom and global UI scale now both produce the expected size for source/target text.

---

## v1.9.449 - May 6, 2026

### Fixed (Sidekick + Clipboard history: respect global UI font scale on Retina screens)

- **Sidekick (floating Assistant) and the Clipboard History widget ignored the Settings → AI Settings global UI font scale.** Even with the slider set to 125% / 150%, the Menu tree, QuickTrans input/results, info bar, and especially the Clipboard History list and column headers stayed at their hardcoded 7–10 pt sizes – uncomfortably small on a MacBook's Retina screen. Reported by Michael while testing on macOS.
- Root cause: both modules built their stylesheets with hardcoded `font-size: Xpt` literals inside class constants and inline `setStyleSheet(...)` calls, which bypass both ThemeManager and Qt's app-level QFont. The slider only reaches widgets that inherit from those mechanisms.
- Fix introduces a small helper at [`modules/ui_scale.py`](modules/ui_scale.py) (`get_ui_font_scale()`, `scaled_pt()`, `refresh_ui_font_scale()`) that reads `general.global_ui_font_scale` from `settings.json` and caches it. [`modules/floating_assistant.py`](modules/floating_assistant.py) and [`modules/clipboard_manager_widget.py`](modules/clipboard_manager_widget.py) now use `scaled_pt(N)` for every content font-size: the Menu tree, QuickTrans source label / results list / hint / input, Clipboard list, count label, Clear-all button, footer hint, empty-state labels, and the column-header active/inactive styles. The class-level style constants (`_FOCUS_STYLE_*`, `_LIST_STYLESHEET`, `_COL_HEADER_*`) became `@property` so they re-evaluate per Sidekick launch and pick up the current scale. `Supervertaler._apply_global_ui_font_scale` now also calls `refresh_ui_font_scale()` so the cache invalidates the moment the slider changes. Already-open Sidekick / clipboard widgets keep their construction-time scale until reopened – acceptable trade-off for not having to wire a Qt signal through these modules.
- Title-bar chrome glyphs (settings ⚙, minimise –, maximise □, close ×) are deliberately left at their fixed point sizes because they sit inside fixed 24×24 px buttons and scaling the glyph without scaling the button would overflow.

---

## v1.9.448 - May 6, 2026

### Fixed (Grid: arrow-key segment navigation broken on macOS)

- **Pressing Up / Down arrow keys in a target (or source) cell did not move to the next/previous segment on macOS.** The cursor "stuck" inside the current cell. Reported by Michael while testing on a MacBook.
- Root cause: macOS Qt sets `KeypadModifier` on every arrow-key event (and on Home/End/PageUp/PageDown) regardless of which physical key produced it – a long-standing macOS-only quirk to differentiate cursor keys from typing keys. The grid's keyPressEvents (and the `_GridArrowKeyEventFilter`) used strict equality checks like `event.modifiers() == Qt.KeyboardModifier.NoModifier` / `... == ControlModifier` / `... == AltModifier`, all of which evaluate to `False` on macOS because the bit set is `NoModifier | KeypadModifier` (etc.) rather than just `NoModifier`. The segment-navigation branches therefore never fired and the events fell through to default Qt cursor-within-text movement, which is invisible on a single-line segment – hence "stuck."
- Fix at [`Supervertaler.py`](Supervertaler.py) introduces a small helper `_cleaned_modifiers(event)` that returns `event.modifiers() & ~Qt.KeyboardModifier.KeypadModifier`, and replaces every arrow-key / Home / End / PageUp / PageDown modifier comparison with the cleaned form. Affects: plain Up/Down (memoQ-style segment hop at line boundaries) in both `ReadOnlyGridTextEditor` and `EditableGridTextEditor`, the `_GridArrowKeyEventFilter` (Ctrl+Up/Down for fast navigation, Alt+Up/Down for quick-add-to-glossary), and the per-editor Ctrl+Up/Down/Home/End and Alt+Left/Up/Down shortcuts. No behavioural change on Windows/Linux (where `KeypadModifier` isn't set on the main arrow keys, so masking it off is a no-op).

---

## v1.9.447 - May 6, 2026

### Improved (Okapi sidecar: surface modal-dialog state in the diagnostic log)

- **The "Java required" and "Download Okapi sidecar?" modal dialogs were invisible in the log** – when an Intel Mac / Linux user without system Java tried to import a DOCX, Supervertaler showed a `QMessageBox.critical` explaining the brew/apt one-liner, but if the user dismissed it without reading (or didn't notice it at all behind the main window), the diagnostic log just stopped at "Importing: filename.docx" with no clue what had happened. Reported in the field by an Intel Mac user whose log Michael spent half an hour piecing together because the dialog state was untracked.
- Fix at [`Supervertaler.py`](Supervertaler.py) adds `self.log()` calls around every modal in [`_maybe_show_okapi_java_warning`](Supervertaler.py) (startup-time Java warning) and [`_ensure_okapi_sidecar`](Supervertaler.py) (DOCX-import-time Java required + sidecar download prompt). Each dialog now logs three things: when it appears, what the user clicked, and any "Don't show again" preference. Effect: the next remote-debugging session shows exactly which dialog the user encountered and what they did about it, without having to ask. Zero behavioural change otherwise – purely diagnostic.

---

## v1.9.446 - May 6, 2026

### Added (context-sensitive "?" help button on the Clipboard tab)

- Second placement of the [`HelpButton`](modules/styled_widgets.py) introduced in v1.9.445. Sits next to the "Clear all" button in the [Clipboard tab header](modules/clipboard_manager_widget.py) and opens the [Clipboard GitBook page](https://supervertaler.gitbook.io/help/get-started-1/workbench/sidekick/clipboard) in the user's default browser.

---

## v1.9.445 - May 6, 2026

### Added (context-sensitive "?" help button – first demo on the AutoFingers tab)

- Mirrors the Trados plugin's convention: a small flat 22×22 px circular grey "?" button sits at the top-right of a section / tab / dialog and one click opens the corresponding GitBook help page in the user's default browser.
- New shared widget [`HelpButton`](modules/styled_widgets.py) – takes a topic identifier from [`Topics`](modules/help_system.py) (e.g. `Topics.AUTOFINGERS`), wires up the click → `open_help(topic)` itself, no boilerplate at the call site. Tooltip defaults to "Open help for this section". Doesn't grab tab focus, so it doesn't interfere with form navigation.
- Pairs with the existing F1 system: F1 walks the widget tree to find the nearest `set_topic`-tagged ancestor, so adding a `HelpButton` AND tagging the same widget gives users two equivalent paths to the same documentation page.
- First placement: top-right of the [AutoFingers tab header](modules/autofingers_tab.py) → opens the [AutoFingers GitBook page](https://supervertaler.gitbook.io/help/get-started-1/workbench/sidekick/autofingers). More to follow as we sprinkle "?" buttons across other Workbench surfaces (Settings, dialogs, Sidekick tabs).

---

## v1.9.444 - May 6, 2026

### Fixed (phantom vertical gap in Edit Voice Command dialog when Type ≠ Internal)

- Reported by Michael with a screenshot: with Type set to Keystroke (or any non-internal type), there was a noticeable empty band of vertical space inside the dialog. Root cause: the "Preset" row was added as a bare `QHBoxLayout` and hidden by toggling `setVisible(False)` on each child widget when not in Internal mode. The layout itself remained in the parent `QVBoxLayout` and reserved its own contents-margins/spacing even with no visible children, producing a phantom gap.
- Fix: wrap the row in a single container `QWidget` (`_internal_actions_widget`). Toggling the container's visibility collapses the whole row – widgets, margins, spacing, the lot – so non-internal action types now sit flush against the next field.

---

## v1.9.443 - May 6, 2026

### Added (Edit Voice Command dialog now has a context-sensitive cheat sheet)

- The Action field's meaning changes wildly with the Type dropdown (an internal action name vs. a keystroke spec vs. an AHK snippet vs. a path to an .ahk file), so the dialog used to show a single generic placeholder that didn't help anyone. New cheat-sheet panel below the Action field auto-updates with the Type:
  - **Internal Action** &mdash; lists every built-in action name (`navigate_next`, `confirm_segment`, `start_dictation`, …) so you don't have to guess or scroll the Preset dropdown.
  - **Keystroke** &mdash; modifier syntax (`ctrl`/`alt`/`shift`/`win`), the full special-key vocabulary (`enter`, `tab`, `escape`, `pageup`, `f1`–`f12`, etc.), and four canonical examples (`ctrl+s`, `ctrl+shift+enter` for Trados confirm-and-advance, plain `tab`, `alt+tab`).
  - **AutoHotkey Code** &mdash; `Send`, `SendText`, `Sleep`, `WinActivate` patterns plus a link to the AHK v2 docs. Multi-line snippets supported.
  - **AutoHotkey Script File** &mdash; clarifies it expects an absolute path to a `.ahk` file, with examples and a pointer to the "Open Scripts Folder" button on the AutoFingers tab.
- Implementation in [`modules/voice_command_dialog.py`](modules/voice_command_dialog.py): single `_ACTION_CHEAT_SHEETS` dict at module scope keyed on action_type, rendered into a `QLabel` with rich-text formatting and external-link support, refreshed from the existing `_on_type_changed` slot.

---

## v1.9.442 - May 6, 2026

### Added (Vosk auto-rebuilds its grammar when you add / edit / disable a command)

- Reported by Michael: "It's also not clear what happens when I want to add new commands that Vosk does not yet recognize." Up to v1.9.441 the workaround was: edit the command, stop Always-On, start it again. Now that's automatic.
- Implementation:
  - New ``commands_changed`` signal on [`VoiceCommandManager`](modules/voice_commands.py), emitted from every ``save_commands()`` call (so all add / edit / remove / enable-toggle paths are covered without touching individual call sites).
  - [`ContinuousVoiceListener.start()`](modules/voice_commands.py) connects the listener to that signal; ``stop()`` disconnects.
  - The slot, ``_on_commands_changed``, is a no-op outside the Vosk engine and when the listener isn't running. When applicable it sets a ``_needs_grammar_rebuild`` flag on the worker thread.
  - The transcription worker checks the flag between clips and rebuilds the Vosk recogniser via ``_maybe_rebuild_vosk_grammar()``: re-runs ``_collect_vosk_grammar()`` to gather the current phrases, builds a fresh ``KaldiRecognizer`` against the cached ``VoskModel`` (no model reload from disk), and swaps it in atomically.
- The cached ``VoskModel`` (the heavy Kaldi data) is now stashed on the worker thread so rebuilds are sub-100 ms – just JSON encoding the new grammar plus constructing the lightweight recognizer object.
- Status bar shows ``🔄 Vosk grammar refreshed (N phrases)`` when the swap happens, so the user has positive confirmation that their edit took effect.

---

## v1.9.441 - May 5, 2026

### Added (Separate "Push-to-talk dictation engine" dropdown so the routing rule isn't a hidden surprise)

- Reported by Michael: "It's also not really clear in the UI why if you set it to Vosk for commands only, faster-whisper is used for running text and not the API." Fair point – the routing rule (Vosk → faster-whisper) was buried in [`Supervertaler.py:start_voice_dictation`](Supervertaler.py) with no UI signal.
- New **"Dictation engine"** dropdown in the Push-to-Talk Mode group, persisted as `pushtotalk_engine` in dictation settings. Three options:
  - **Same as Always-On (auto-route)** *(default, preserves v1.9.440 behaviour)*: Vosk → faster-whisper, faster-whisper → faster-whisper, OpenAI API → OpenAI API.
  - **faster-whisper (offline)**: always offline, regardless of Always-On engine.
  - **OpenAI Whisper API (online, fast, requires API key)**: always API. Lets a user pair Vosk for free continuous commands with the OpenAI API for fast dictation – which the previous fixed routing didn't allow.
- The "ℹ️ Push-to-talk will use: ..." indicator below the dropdown shows the *resolved* engine after auto-routing, so the rule is always visible.

---

## v1.9.440 - May 5, 2026

### Improved (label "Whisper model" → "faster-whisper model" so it's clear it's offline only)

- Reported by Michael: the AutoFingers tab said "Whisper model size" + listed sizes from `tiny ~75 MB` to `large ~2.9 GB`, which someone could read as applying to the OpenAI Whisper API (it doesn't – the API always uses `whisper-1` server-side). Renamed the group title to `🤖 faster-whisper Model (offline; used for push-to-talk and Always-On if engine = faster-whisper)` and updated the size-table caption to start "faster-whisper model size …" plus an explicit note that the OpenAI API ignores this setting.

---

## v1.9.439 - May 5, 2026

### Improved (AutoFingers tab cleanup under Vosk)

- **The "OpenAI API mode is recommended for Always-On" hint is now hidden everywhere.** Pre-Vosk it was sound advice; after v1.9.435 made Vosk the default, the hint actively misleads (Vosk is faster, free, and recommended for commands). The recommendation now lives where it should: on the engine selector itself ("Vosk — recommended").
- **The "Speech Recognition Model" group has been retitled** to `🤖 Whisper Model (push-to-talk dictation; also Always-On if engine = Whisper)` so it's obvious those Whisper sizes (`tiny ~75 MB` … `large ~2.9 GB`) only apply when a Whisper engine is in use. Vosk picks its own model from the Language setting; the Whisper sizes are irrelevant to it.
- **New "Push-to-talk dictation will use: ..." indicator** under the Push-to-Talk Mode group. Updates dynamically with the engine selector to remove ambiguity about which engine handles Ctrl+Alt+D / F9. With Vosk or faster-whisper selected as Always-On engine: shows "faster-whisper". With OpenAI Whisper API selected: shows "OpenAI Whisper API". This addresses the user's confusion when Vosk is the Always-On engine but Ctrl+Alt+D is for running text – Vosk is commands-only, so push-to-talk silently routes through faster-whisper.
- All three changes converge in a new `_sync_engine_dependent_widgets()` method that runs once on load (after every engine-dependent widget exists) and again whenever the engine combo changes.

---

## v1.9.438 - May 5, 2026

### Fixed (v1.9.437's "grey out the checkbox under Vosk" didn't actually grey out)

- Reported by Michael with a screenshot: the "Listen for commands only" checkbox still looked fully active even with Vosk selected. Root cause: the shared [`CheckmarkCheckBox`](modules/styled_widgets.py) widget's stylesheet pins explicit fill colours (`#4CAF50` etc.) on the indicator and doesn't include a `:disabled` rule, so Qt's automatic disabled-state palette gets overridden. `setEnabled(False)` was called correctly but the indicator stayed bright green visually.
- Switched from disabling to **hiding** the checkbox under Vosk. Cleaner UX anyway – the option is structurally a no-op there, so just take it off the screen rather than show a "disabled" widget that might still look clickable. When the user switches engine to faster-whisper or the OpenAI API the checkbox re-appears.

---

## v1.9.437 - May 5, 2026

### Improved (Always-On UX polish — checkbox auto-disable + terminology fix)

- **"Listen for commands only" checkbox is now greyed out when Vosk is the active engine.** Vosk's grammar-constrained recogniser already drops non-command speech at the recogniser level (returns `[unk]` and we throw it away), so the checkbox setting was structurally a no-op for Vosk users – just confusing UI noise. With Vosk selected the checkbox is now disabled with a tooltip explaining why; switching to faster-whisper or the OpenAI API re-enables it (those engines do produce transcribable text from non-command speech, so the user actually has a meaningful choice). Reported by Michael while testing v1.9.435.
- **Engine-selector labels: "free-text" → "running text".** "Free text" reads ambiguously like "free as in money" rather than "as opposed to fixed phrases". Translators – the primary audience – say "running text" for prose, so the new labels are: **Vosk (offline, free, commands only) — recommended** / **faster-whisper (offline, dictates running text)** / **OpenAI Whisper API (online, fast, dictates running text)**. Same change applied to internal code comments and v1.9.435–v1.9.436 CHANGELOG entries for consistency.

---

## v1.9.436 - May 5, 2026

### Fixed (Push-to-talk dictation now coexists with Always-On instead of killing it)

- **Reported by Michael**: with v1.9.435's Vosk-as-default Always-On engine, pressing F9 / Ctrl+Alt+D for push-to-talk dictation just *deactivated* Always-On instead of dictating anything. The trigger logic at [`Supervertaler.py:45404`](Supervertaler.py:45404) was a leftover from when both paths used Whisper and shared the mic exclusively – the easy-out was to treat F9 as an Always-On kill-switch when Always-On was running.
- **Now**: F9 / Ctrl+Alt+D **pauses** Always-On for the duration of the push-to-talk dictation, then **resumes** it automatically when the dictation thread finishes (or errors out). Vosk continues monitoring for commands afterwards without the user having to manually re-enable it. The pause is implemented via a new `ContinuousVoiceListener.pause()` / `resume()` pair plus an `is_paused` flag that the audio callback checks; while paused, incoming audio chunks are silently dropped (so a) dictated text isn't half-interpreted as a command, and b) we don't waste CPU running Vosk on Whisper-bound speech).
- The new architecture matches the user's mental model from v1.9.435 — Vosk for continuous commands, faster-whisper for explicit running-text dictation — instead of forcing them to be mutually exclusive. Resume happens unconditionally on both success (`on_dictation_finished`) and error (`on_dictation_error`) paths, so a Whisper failure never leaves Always-On stuck in the paused state.

### Note: F9 (in-app shortcut) vs Ctrl+Alt+D (global hotkey)

- F9 is a Qt `QShortcut` and only fires when Supervertaler has window focus. To trigger push-to-talk from any application (Trados / memoQ / Word / browser / etc.) use **Ctrl+Alt+D**, which is registered via the cross-platform global hotkey manager. This isn't new behaviour but worth restating since users testing v1.9.435 for the first time may try F9 from their CAT tool's editor and conclude push-to-talk is broken when actually it's just not in scope for in-app shortcuts.

---

## v1.9.435 - May 5, 2026

### Added (Vosk as the new default Always-On engine — free, instant, commands-only)

- **AutoFingers always-on listening now defaults to Vosk** instead of cloud Whisper. Vosk is a Kaldi-based, offline, lattice-decoding recogniser purpose-built for fixed-vocabulary command recognition. With your active command phrases passed in as a JSON grammar, recognition latency drops from ~1–3 seconds (Whisper-class) to ~30 ms, accuracy improves (the recogniser is biased toward your phrase list and rejects unrelated speech as `[unk]`), and per-utterance cost is exactly zero. The CPU spike per command is ~15–30% on one core for a fraction of a second – inaudible-fan-noise territory.
- **Engine selector in [Sidekick → AutoFingers settings](modules/autofingers_tab.py)** is now three-way: **Vosk (offline, free, commands-only) — recommended**, **faster-whisper (offline, dictates running text)**, **OpenAI Whisper API (online, fast, dictates running text)**. Legacy `recognition_engine='local'` setting auto-migrates to `'faster_whisper'`. Vosk is the default for fresh installs.
- **Push-to-talk dictation (F9 / Ctrl+Alt+D) keeps using faster-whisper** for running-text capability. If a user has Vosk selected as the always-on engine but triggers push-to-talk, the dictation path silently routes to faster-whisper (Vosk's grammar mode isn't built for running-text transcription). This matches the natural split: continuous always-on listening is commands-only and free, intentional push-to-talk dictation produces running text and uses Whisper.
- **Auto-download on first use** at [`modules/vosk_model_manager.py`](modules/vosk_model_manager.py): the small English (~40 MB) and small Dutch models are mirrored from alphacephei.com to `<user_data>/vosk-models/<model-name>/` on first activation, similar to the Okapi sidecar download pattern. Model selection is auto-resolved from the user's language hint, falling back to small-en-us.
- New `ContinuousVoiceListener.engine` field replaces the old `use_api: bool`, with class constants `ENGINE_VOSK`, `ENGINE_FASTER_WHISPER`, `ENGINE_API`. The old `use_api` is kept as a backward-compat property.
- Vosk grammar is built from the user's *enabled* commands (each command's phrase plus its aliases), with `"[unk]"` always appended as the catch-all so out-of-grammar speech doesn't error.

### Changed (Local Whisper / faster-whisper / Vosk are now CORE dependencies, not optional extras)

- **Removed the `[local-whisper]` extra dance.** Both `vosk>=0.3.45` and `faster-whisper>=1.0.0` are now listed in core `dependencies` in [`pyproject.toml`](pyproject.toml). A plain `pip install supervertaler` gets you all three voice engines out of the box. Adds ~50 MB to the wheel install (mostly ctranslate2's C++ engine), ~100 MB to the standalone Windows ZIP.
- The legacy `[local-whisper]` extra is kept as an empty no-op for backward compatibility – any existing install scripts that include it still resolve cleanly.
- Error messages and install-doc snippets across [`modules/voice_dictation.py`](modules/voice_dictation.py), [`modules/voice_dictation_lite.py`](modules/voice_dictation_lite.py), and [`modules/voice_commands.py`](modules/voice_commands.py) updated to drop all `pip install supervertaler[local-whisper]` advice.

---

## v1.9.434 - May 5, 2026

### Changed (Local Whisper backend swapped from `openai-whisper` to `faster-whisper`)

- **Why:** the old `openai-whisper` backend was noticeably slow on CPU and required a ~1.5 GB PyTorch dependency tree, plus the per-call `ffmpeg` subprocess that was causing the cmd-window flash fixed in v1.9.433. The new [`faster-whisper`](https://github.com/SYSTRAN/faster-whisper) backend uses the CTranslate2 inference engine: identical Whisper models, identical recognition quality, ~4× faster on CPU, ~3-4× lower RAM, no `ffmpeg`. Drop-in replacement at the user level – same model names (`tiny` / `base` / `small` / `medium` / `large-v3`), same language codes, same UI.
- Applied at all three local-Whisper transcribe sites: [`modules/voice_commands.py`](modules/voice_commands.py) (always-listener), [`modules/voice_dictation.py`](modules/voice_dictation.py) (full-app push-to-talk thread), [`modules/voice_dictation_lite.py`](modules/voice_dictation_lite.py) (lite engine for the dictation surface). `WhisperModel(name, device="cpu", compute_type="int8")` for the speed/quality balance recommended by the project. `transcribe()` returns a segments generator and an info object instead of a dict; we just join the segment texts for the final transcription.
- Dependency change in [`pyproject.toml`](pyproject.toml): `local-whisper = ["faster-whisper>=1.0.0"]` (was `openai-whisper>=20230314`). Existing users on the `[local-whisper]` extra get the new backend on next `pip install --upgrade`. The first transcription downloads the CTranslate2-format model (separate from any `~/.cache/whisper` you may already have); subsequent runs reuse the local cache.
- The `hide_subprocess_console_windows()` wrapper added in v1.9.433 is kept defensively around each `transcribe()` call. faster-whisper does its audio decoding via libsndfile in-process (no `ffmpeg` subprocess, no flash), so the wrapper is a no-op in the common case – but it costs nothing and protects against any future code path that might shell out.
- Feature-manager package list and first-run help text updated to advertise `faster-whisper` instead of `openai-whisper`.

---

## v1.9.433 - May 5, 2026

### Fixed (AutoFingers always-listening flashed a black cmd window every few seconds in local Whisper mode)

- **Reported by Michael**: with AutoFingers always-on listening enabled and *no* OpenAI API key (so the always-listener falls back to local Whisper), a black cmd window was flashing on screen at regular intervals. Closing Supervertaler stopped the flashing entirely.
- Root cause: OpenAI's `openai-whisper` Python library decodes audio files by shelling out to `ffmpeg` via plain `subprocess.run([...])` with no `creationflags=CREATE_NO_WINDOW`. When Supervertaler runs as a console-less GUI (`Supervertaler.exe` or `pythonw.exe`), every ffmpeg invocation flashes a black cmd window for ~100 ms. Always-listening triggers a transcription per VAD chunk, so the user sees a periodic flash for as long as listening is active. Push-to-talk dictation in local mode was equally affected, just less obvious because it fires once per recording.
- Fix at [`modules/platform_helpers.py`](modules/platform_helpers.py): new `hide_subprocess_console_windows()` context manager that temporarily wraps `subprocess.Popen` so any process spawned inside the `with` block gets `CREATE_NO_WINDOW` added to its creationflags. No-op on non-Windows platforms. Restored on exit.
- Applied to all three local-Whisper transcribe sites: [`modules/voice_commands.py`](modules/voice_commands.py) (always-listener, the user-reported case), [`modules/voice_dictation.py`](modules/voice_dictation.py) (full-app dictation thread), and [`modules/voice_dictation_lite.py`](modules/voice_dictation_lite.py) (lite dictation engine for the standalone-app voice dictation surface).
- This is a surgical fix for the immediate symptom – a longer-term plan to add Vosk as a free, fast, command-only local STT backend (purpose-built for fixed-vocabulary command recognition, no ffmpeg roundtrip) is tracked separately as a future enhancement.

---

## v1.9.432 - May 5, 2026

### Fixed (v1.9.431's LLM-bundling fix was still incomplete — HTTP transitive deps missing)

- **Verified by inspecting the v1.9.431 build ZIP**: the LLM client libraries themselves were bundled this time (`google/generativeai/` 69 files, `openai/` 1003 files, `anthropic/` 448 files), but every one of their HTTP / validation transitive dependencies was missing — `httpx`, `httpcore`, `anyio`, `h11`, `sniffio`, `distro`, and the actual `pydantic` Python source (only `pydantic_core/_pydantic_core.cp312-win_amd64.pyd` and the dist-info shipped, no `pydantic/` tree). Every API call would have failed at runtime with `ModuleNotFoundError: No module named 'httpx'` (or anyio, etc.) the moment `openai.OpenAI()` or `anthropic.Anthropic()` was constructed.
- Root cause: `collect_all('openai')` and `collect_all('anthropic')` correctly enumerate their own submodules, but PyInstaller's static analysis of those submodules does not reliably follow `import httpx` / `import httpcore` / `import pydantic` into the dependency tree — even though the corresponding hooks (`hook-anyio.py`, `hook-pydantic.py`) DO get loaded. Looks like an interaction between `collect_all` adding the package as data files vs. as analyzed Python source.
- Fix at [`Supervertaler.spec`](Supervertaler.spec) and [`Supervertaler_macOS.spec`](Supervertaler_macOS.spec): extend the `collect_all()` loop to name each HTTP / validation transitive dep explicitly — `httpx`, `httpcore`, `h11`, `sniffio`, `anyio`, `distro`, `pydantic`, `jiter`. More verbose, but guarantees they all ship.

### Fixed (Build script silently masked PyInstaller failures)

- **Reported by Michael during today's session**: the v1.9.431 build's first attempt actually crashed mid-COLLECT with `OSError: [WinError 145] The directory is not empty` (Windows couldn't `rmtree` a stale `node_modules/` deep-path tree under `dist/Supervertaler/_internal/docs/help/workbench/`). The build script proceeded to copy user_data, copy Start-Menu shortcuts, and zip up whatever incomplete bundle PyInstaller had managed to produce — and reported _"DONE. Release asset: ..."_ as if everything was fine. Verifying the ZIP showed `anthropic/` was missing entirely, masking the bundling bug we were trying to fix.
- Fix at [`build_windows_release.ps1`](build_windows_release.ps1): check `$LASTEXITCODE` after the PyInstaller call and abort the build with a clear error if it's non-zero (PowerShell's `$ErrorActionPreference = 'Stop'` does not catch native-command non-zero exits). Also added a hint about the `node_modules` cause since this is the same trip-hazard that any future contributor will hit if they leave a JS dep tree under a bundled docs folder.
- The blocking `node_modules/` itself was deleted from `docs/help/workbench/` — it was already `.gitignore`d and shouldn't have been in the source tree anyway. The folder is the unused stub of an experimental MkDocs / JS-based Workbench help system; the actual published Workbench help lives in the Trados repo's `docs/workbench/`.

---

## v1.9.431 - May 5, 2026

### Fixed (v1.9.427's hiddenimports fix wasn't enough — LLM libs still missing from Windows ZIP / macOS DMG)

- **Verified by building a v1.9.430 Windows ZIP and inspecting it**: `google.generativeai`, `openai`, and `anthropic` are still absent from the bundle, despite v1.9.427's `hiddenimports` entries. PIL (also added in v1.9.427) was bundled correctly. So issue #187 is *not* fixed for standalone-bundle users yet.
- Root cause: `hiddenimports` only adds the *named* module to PyInstaller's analysis graph. It does not pull in submodules or data files. Real Python packages with submodule structure (`google.generativeai.types`, `openai._client`, `anthropic._streaming`, etc.) need [`collect_all`](https://pyinstaller.org/en/stable/hooks.html#PyInstaller.utils.hooks.collect_all) from `PyInstaller.utils.hooks` instead — that gathers binaries, data files, *and* the full submodule tree. v1.9.427's commit message even flagged this caveat ("you may need `--collect-submodules`...") and then didn't act on it.
- Fix at [`Supervertaler.spec`](Supervertaler.spec) and [`Supervertaler_macOS.spec`](Supervertaler_macOS.spec): replace the plain `'google.generativeai'`, `'openai'`, `'anthropic'` hiddenimports entries with a `collect_all()` loop that gathers everything each package needs. PIL stays as a plain hiddenimport because it was already working.
- Verified after rebuild: the v1.9.431 Windows ZIP now contains the full `google/generativeai/`, `openai/`, and `anthropic/` package trees.

### Note on `google-generativeai` deprecation

- During this work, `google-generativeai` printed a `FutureWarning` saying support has ended and users should migrate to `google.genai`. Tracking this as a separate follow-up — the v1.9.431 fix gets the standalone bundle working with the current dep, but the migration to `google-genai` is the longer-term answer.

---

## v1.9.430 - May 5, 2026

### Fixed (Settings → Change Data Folder silently reverted on restart in dev mode)

- **Reported by Michael**: clicking "Change..." in Settings → General → Data Folder Location, picking a new folder, and getting the "Data folder changed / All managers have been reinitialized" confirmation – then on next launch the path quietly reverted to `~/Supervertaler`. Only happened in dev mode (`.supervertaler.local` present).
- Root cause: `MainWindow.__init__` had a dev-mode branch that hardcoded `self.user_data_path = Path.home() / "Supervertaler"` and skipped the unified pointer-file resolution entirely. The `Change...` button correctly wrote the new path to `%APPDATA%\Supervertaler\config.json`, but on restart the dev branch never read that file – it just took the home-folder default and ignored what the user had chosen. The comment claimed "use same path as a real user", but a real user's path is whatever is in their pointer, not necessarily the default.
- Fix at [`Supervertaler.py`](Supervertaler.py): drop the dev-mode special case. All three flavours (dev, pip, EXE) now go through `get_user_data_path()`, which (1) reads the pointer file if it exists, (2) auto-recovers by writing a pointer at the default location if `~/Supervertaler` already has content, or (3) falls back to the default for genuinely fresh installs. Dev installs that already have data at `~/Supervertaler` will see no behaviour change after pulling – the auto-recovery writes a pointer matching the previous hardcoded path on the first run. Devs who want their data elsewhere can now actually use Settings → Change... and have it stick.
- Also dropped a dead `base_folder = "user_data_private" if ENABLE_PRIVATE_FEATURES else "user_data"` line that was being computed but never used – legacy from the tkinter version, no behavioural impact in current code.

---

## v1.9.429 - May 5, 2026

### Changed (Launcher and Start-Menu scripts moved into `scripts/`)

- **Source-tree cleanup.** The five launcher / shortcut helper scripts that used to clutter the repo root have moved into the existing `scripts/` folder alongside the other dev helpers (`cleanup_old_build_artifacts.ps1`, `kill_build_processes.ps1`, `smoke_test_windows_exe.ps1`):
  - `run.cmd`
  - `run-silent.cmd`
  - `create_dev_start_menu_shortcut.ps1`
  - `create_start_menu_shortcut.ps1`
  - `Add Supervertaler to Start Menu.cmd`
- **End-user ZIP layout unchanged.** `Add Supervertaler to Start Menu.cmd` and `create_start_menu_shortcut.ps1` still ship at the **root** of the Windows ZIP (next to `Supervertaler.exe`), exactly where end users find them on extraction. The `build_windows_release.ps1` Copy-Item paths were updated to source from `scripts/` while keeping the destination at `$distDir\` root.
- **Path-relative logic updated.** `run.cmd` and `run-silent.cmd` now `cd /d "%~dp0.."` at the top so they find `Supervertaler.py` and the rest of the source tree from one level up. `create_dev_start_menu_shortcut.ps1` derives the source root via `Split-Path $PSScriptRoot -Parent` for the `Supervertaler.py` / icon / `.venv` lookups; `run.cmd` itself stays under `$PSScriptRoot` since both files live in `scripts/`.
- **Action required if you have existing dev shortcuts:** re-run `.\scripts\create_dev_start_menu_shortcut.ps1` once. Both dev shortcuts will be regenerated with the new `scripts/run.cmd` target. Old shortcuts pointing at the root-level `run.cmd` are simply replaced (same shortcut name).
- README inside the Windows ZIP (generated by `create_release_zip.py`) updated to recommend double-clicking `Add Supervertaler to Start Menu.cmd` rather than right-clicking the `.ps1`, since the friendlier `.cmd` wrapper added in v1.9.428 now exists.

---

## v1.9.428 - May 5, 2026

### Added (One-click "Add Supervertaler to Start Menu" for Windows ZIP users)

- **Windows ZIP releases now ship `Add Supervertaler to Start Menu.cmd`** alongside the existing `create_start_menu_shortcut.ps1`. End users double-click the `.cmd` and it runs the PowerShell script under `-ExecutionPolicy Bypass`, so they don't have to right-click → "Run with PowerShell", confirm scary security dialogs, or know that PowerShell ExecutionPolicy is even a thing. The `.ps1` is still the actual implementation; the `.cmd` is just a friendly user-visible wrapper.
- Build pipeline ([build_windows_release.ps1](build_windows_release.ps1)) updated to copy the new `.cmd` into `dist/Supervertaler/` alongside the `.ps1` so it lands in the Windows ZIP automatically.

### Improved (Dev script now also creates a no-terminal shortcut)

- **`create_dev_start_menu_shortcut.ps1` now creates two dev shortcuts in one go:**
  - `Supervertaler Workbench (Dev)` — targets `run.cmd`, opens a console window so stdout/stderr (`[LOG]` lines, exceptions) stream live. Useful when actively debugging.
  - `Supervertaler Workbench (Dev, no terminal)` — targets `pythonw.exe Supervertaler.py` directly, no console flash. Useful for everyday dev use when you don't need to watch the console.
- The no-terminal shortcut targets `pythonw.exe` directly (preferring `.venv\Scripts\pythonw.exe` if it exists, else PATH) rather than going through `run-silent.cmd`, because launching a `.cmd` from a shortcut still flashes a brief console window even with `start /b` inside.
- Re-run the script after moving the source tree — both shortcuts embed absolute paths and need to be regenerated when the source folder moves.

---

## v1.9.427 - May 5, 2026

### Fixed (Standalone Windows / macOS bundles couldn't translate – "Google AI library not installed")

- **Reported by @marochanhk in #187**: pressing Ctrl+T to translate raised _"Failed to translate segment: Google AI library not installed. Install with: pip install google-generativeai pillow"_, and following the suggested `pip install` had no effect. Affected both Windows ZIP and macOS DMG users; pip-installed users were unaffected because pyproject.toml lists the LLM clients as core dependencies that pip pulls in automatically.
- Root cause: in [`modules/llm_clients.py`](modules/llm_clients.py), the per-provider call methods (`_call_gemini`, `_call_openai`, `_call_claude`) wrap their imports in `try/except ImportError` blocks – a sensible pattern for surfacing a friendly error if a user's pip install is missing a dep, but PyInstaller's static analyzer treats function-local imports inside `try/except ImportError` as *optional* and silently drops them from the bundle. Combined with `google.*` being a namespace package (only `google._upb` from protobuf was being pulled in transitively), the standalone bundles shipped without `google.generativeai`, `openai`, or `anthropic` at all. The user's attempted `pip install` ran against system Python, which a PyInstaller bundle (with its own bundled Python) doesn't see.
- Fix: explicit `hiddenimports` entries for `google.generativeai`, `openai`, `anthropic`, and `PIL` in both [`Supervertaler.spec`](Supervertaler.spec) and [`Supervertaler_macOS.spec`](Supervertaler_macOS.spec). PyInstaller now bundles them regardless of the static-analyzer heuristic. Pip-installed users were already getting these via pyproject.toml's `dependencies` list, so this is purely a build-config fix for standalone-bundle users.

---

## v1.9.426 - May 5, 2026

### Added (Proactive "install Java" dialog for Intel Mac / Linux pip users)

- **Intel Mac and Linux users who pip-install Supervertaler now get a friendly heads-up at startup if they don't have Java installed yet.** Without it, they'd silently use the app for plain-text work and only discover the missing dependency when they tried to open a Word document, where the existing in-flow dialog would catch them. Better to warn them up front so they can fix it before hitting the wall.
- The dialog explains *why* the bundled JRE in the macOS DMG isn't an option for Intel Macs (the JRE is built for Apple Silicon only) and gives a one-line install command per platform: `brew install --cask temurin@17` on macOS, `sudo apt install default-jre` (or distro equivalent) on Linux. Includes a "Don't show again" checkbox stored under the unified settings → `general.okapi_java_warning_dismissed`. Driven by a new `OkapiSidecar.needs_system_java_install()` helper that returns True iff the current platform falls back to the JAR-only download path *and* no Java runtime is reachable.
- The existing in-flow Java-required dialog (which fires when a user with no Java tries to import a DOCX) was tightened up to recommend Eclipse Temurin specifically and to prompt a `java -version` verification step. Plain `brew install openjdk` works but pulls latest, which has tripped people on macOS in the past with `JAVA_HOME` not set.

### Fixed (Lazy-install flow was unreachable on first DOCX import)

- **Pip-installed users on a fresh machine could never reach the "Download Okapi sidecar?" prompt – they hit a misleading "client not initialised" error instead.** `_start_okapi_sidecar` was setting `self.okapi_sidecar = None` whenever `is_available` returned False, but `is_available` requires *both* the JAR on disk *and* a reachable Java – which is precisely the state every fresh pip install starts in. Once the sidecar object was nulled, the path-3 lazy-download dispatch in `_ensure_okapi_sidecar` short-circuited at its first `if not self.okapi_sidecar:` check. The lazy-install flow has been the documented user-facing path since v1.9.415, but it was unreachable on a clean install.
- Fix: keep `self.okapi_sidecar` populated even when the sidecar can't run yet. The lifecycle now distinguishes three startup states (running, JAR-not-installed, JAR-present-but-no-Java) and logs each plainly without disabling the lazy-install path. Added `Supervertaler.py`'s missing top-level `import platform` while in there – the module was used inside `_ensure_okapi_sidecar` but only ever imported lazily inside one unrelated diagnostic-log method, so the same Path-3 dispatch would have hit a `NameError` had it ever been reached.

---

## v1.9.425 - May 5, 2026

### Fixed (Held arrow keys snagged when navigating the Clipboard list)

- **Holding Up or Down to scroll through the Sidekick Clipboard list produced visible stalls every few rows.** Single key taps were fast; only key auto-repeat tripped the hitch. Reported by Michael with 250-item history (200 text snippets, 50 image thumbnails).
- Root cause: the list widgets were configured with `QAbstractItemView.ScrollMode.ScrollPerPixel`. That mode animates a smooth pixel-level scroll on every selection change. Mouse-wheel scroll feels nice with it; key auto-repeat does not – Qt fires KeyDown events faster than the smooth-scroll animation can complete, so each new event has to wait for the in-flight animation. Once a queue forms, the user sees stalls, then a sudden burst as the queue drains. Compounded by the lack of `setUniformItemSizes(True)`, which forced Qt to remeasure every row's geometry on each paint pass.
- Fix at [clipboard_manager_widget.py `_make_list_widget`](modules/clipboard_manager_widget.py): switch the vertical scroll mode to `ScrollPerItem` (snaps directly to the next row, no animation), enable `setUniformItemSizes(True)` (text items are single-line via `setWordWrap(False)`, image items are all 48×48 thumbs – the invariant Qt needs to cache row heights and skip per-row measurement), and switch the layout mode to `QListView.LayoutMode.Batched` with a 50-item batch size (Qt processes layout in chunks instead of one row at a time).
- Net effect for held-key navigation: each Up/Down now does `selection += 1` and a single snap-scroll repaint instead of `selection += 1` + queued smooth-scroll animation + per-row geometry recalc. Mouse-wheel scrolling switches from animated to snap, which is a small loss in feel but is the same behaviour every other application list uses (Outlook, Explorer, Trados Studio's grid).

---

## v1.9.424 - May 5, 2026

### Fixed (First Alt+K / Ctrl+Shift+C hotkey press took 4-5 seconds)

- **The very first press of Alt+K (Sidekick) or Ctrl+Shift+C (Clipboard) after launching Supervertaler stalled for 4-5 seconds before the window appeared.** Subsequent presses were near-instant. Reported by Michael as a consistent cold-start hitch.
- Root cause: `FloatingAssistant._ensure_superlookup_tab()` ran lazily on the first `show_at_cursor()` call. That single function constructs the entire SuperlookupTab widget (which itself brings up TM lookup wiring, termbase managers, MT engines, and the Web Resources tabs), the Clipboard tab, and the AutoFingers tab in one synchronous burst on the GUI thread. The deferral was originally chosen because the FloatingAssistant is constructed during `create_main_layout()`, before the database / termbase managers are fully wired – building the lazy tabs at construction time would have crashed.
- Fix: `FloatingAssistant.__init__` now schedules a `QTimer.singleShot(2500, self._warm_up_lazy_tabs)` at the end of construction. By 2.5 s after start-up, the main window has painted, the Okapi sidecar has handed over, the database has settled, and everything the lazy tabs need is in place – so the warm-up calls `_ensure_superlookup_tab()` (which cascades into the Clipboard + AutoFingers ensure-helpers) in the background while the user is reading the welcome message or loading their project. By the time the user presses Alt+K or Ctrl+Shift+C, all three tabs are already constructed and the hotkey path becomes the fast `isVisible()` / `raise_()` path.
- Idempotent: the `_ensure_*` helpers are all guarded by `_added` flags, so the warm-up + first-show pair is a no-op the second time around. Also failure-safe: any exception during warm-up is swallowed; the lazy paths still run on first show as a fallback. No change to subsequent-hotkey latency – this only collapses the first-time hitch.

---

## v1.9.423 - May 4, 2026

### Fixed (Freshly imported DOCX → 20-second cell-select freezes in filtered mode)

- **Clicking a segment after applying a source/target filter on a freshly-imported DOCX could freeze the app for 20+ seconds.** Reported by Michael with a full instrumentation log: a single cell-select burned **19,822 ms** inside `find_termbase_matches_in_source` (a 4 100-term database, even though all termbases were deactivated for the new project), with the rest of the cell-select cost negligible by comparison.
- Root cause: `import_docx_from_path` (the fresh-import code path) ended without calling `_start_termbase_batch_worker()`, so the in-memory termbase index was never built for the imported project. Without the index, `find_termbase_matches_in_source` falls back to a brute-force scan over every term across every termbase in the database, which on a fairly modest 40-termbase / 4 100-term collection takes about 1 second cold and gets dramatically worse on the synchronous fall-through that fires from `_on_cell_selected_glossary_only` in filtered mode (the path runs every click, so any locking / re-entrancy makes it cumulatively much slower than a single isolated call). The same project loaded via `Open Project…` (the saved `.svproj` path) was fine because the project-load code at line 25720 already called `_start_termbase_batch_worker()`. Two doors into the same room; only the saved-project door was wired up.
- Fix: `import_docx_from_path` now calls `_start_termbase_batch_worker()` (which builds the index and starts the background match-cache fill) and `_start_prefetch_worker(first_50)` at the end of the import, mirroring the saved-project load path. Both calls are wrapped in try/except so a background-worker failure doesn't block the visible grid load. After the fix, the user's reproduction shipped this log line on import:

  ```
  ✅ Built termbase index: 4 100 terms in 0.49 s
  🔄 Starting background termbase batch processor for 276 segments...
  ✅ Termbase batch worker complete: 276 segments in 0.42 s
  ```

  …and the subsequent filtered-mode cell-select dropped from 19,822 ms to ~300 ms (cache miss with index built) or ~70 ms (cache hit).
- Note on the synchronous fall-through itself: `_on_cell_selected_glossary_only`'s `else` branch at `Supervertaler.py:38087-38090` is labelled *"do a fresh (cheap) termbase-only lookup"*. It's cheap *with the index built*, expensive without. With the index now reliably built on import, the fall-through stays cheap. A future hardening pass could move that fall-through onto a worker so even an index-less path stays UI-responsive, but it's no longer urgent.

---

## v1.9.422 - May 4, 2026

### Changed (Top-level tabs: TMs and Termbases promoted out of Resources)

- **TMs and Termbases are now their own top-level tabs.** The main tab strip changes from `Editor – Resources – AI – Tools – Settings` (5 tabs) to `Editor – TMs – Termbases – AI – Tools – Settings` (6 tabs). Both used to be sub-tabs nested inside the Resources wrapper, requiring two clicks to reach; promoting them gives each one a permanent slot on the main bar and brings every commonly-used view to one click.
- The old wrapper QTabWidget (`resources_tabs`) and its `create_resources_tab()` method are gone. Construction now calls `create_translation_memories_tab()` and `create_termbases_tab()` directly and adds each as a peer to Editor / AI / Tools / Settings on `main_tabs`. The TMs tab inherits the `TM_BASICS` help topic; the Termbases tab gets `GLOSSARY_BASICS`.
- View → Navigate To submenu updated: "Project resources" entry replaced with "TMs" and "Termbases" entries pointing at indices 1 and 2 respectively. AI / Tools / Settings entries shift to indices 3 / 4 / 5.
- All hard-coded tab indices in helpers updated: `_navigate_to_tool()` now uses 4 (was 3), `_navigate_to_settings()` uses 5 (was 4), Sidekick "go to glossary" navigates to the Termbases top-level tab by name match instead of drilling through a wrapper, MT-settings navigation in SuperLookup uses Settings = 5, AI-tab navigation uses 3 (was 2).
- Backward compatibility: `self.resources_tabs` is kept as an alias for `self.main_tabs` so any third-party / legacy nav code that did `setCurrentIndex` on it doesn't crash – but the index it expects (0 = TMs, 1 = Termbases inside the old wrapper) no longer matches; callers should switch to `self.main_tabs.setCurrentIndex(<top-level index>)` and target TMs (1) or Termbases (2) directly.

---

## v1.9.421 - May 4, 2026

### Fixed (CRITICAL: Monolingual DOCX import via Okapi ignored the user's language choice)

- **Importing a monolingual DOCX via Okapi always sent the file to the sidecar as English-to-English, regardless of what source/target languages the user picked in the Import DOCX Options dialog.** Reported by Michael: he selected Dutch → English in the dialog, but the import-progress popup briefly flashed the wrong direction and Okapi was effectively segmenting the Dutch text using English rules. Project metadata (the `Project` constructor at line 27718-27719) was correct because that code reads the right attribute names, but the actual call to `okapi_sidecar.extract(...)` at line 27538-27542 read attributes that don't exist anywhere in the codebase and silently fell through to the default `'en'`.
- Root cause: a two-line typo at [Supervertaler.py:27518-27519](Supervertaler.py). The dialog stores the user's choice on `self._import_source_lang` / `self._import_target_lang`, but those two lines read `self._last_import_source_lang` / `self._last_import_target_lang` – names that are never assigned anywhere in the codebase. Both `getattr` calls hit the `'en'` fallback every single import, so the Okapi sidecar received `source_lang='en', target_lang='en'` and applied English segmentation rules to whatever was in the document.
- Real-world impact: Okapi's segmenter uses different SRX rules per language (different sentence-boundary heuristics, abbreviation lists, quotation handling). Forcing English rules on Dutch / German / French / Japanese / Chinese source texts can produce subtly different segment boundaries than expected. The visible symptom – the wrong-direction flash in the progress dialog – is what surfaced this, but the underlying segmentation issue is the more important fix.
- Fix: read `_import_source_lang` / `_import_target_lang` (the names actually used by the dialog) in the Okapi extraction path, defaulting to `'en'` only as a last resort. The progress dialog now shows the correct direction (e.g. "NL → EN" when the user picks Dutch → English). `import_docx_from_path` already used the correct attributes for project construction, so projects created since this code shipped have the right `source_lang` / `target_lang` metadata even though Okapi was being misled – no data corruption, just wrong segmentation. Also added a missing `QApplication.processEvents()` call inside the elapsed-time progress-update loop so the dialog stays responsive while Okapi runs.

---

## v1.9.420 - May 4, 2026

### Fixed (more Mac tab clipping)

- **The main-window right-side panel tabs were also being truncated on macOS** ("Match Panel" → "Match…", "AI Assistant" → "AI Ass…", "Preview" → "Pr…", "Segment note" → "Segment…", "Proofreading" → "Proofreadin…", "Session log" → "Sessio…", "Scratchpad" → "Scrat…"). v1.9.419 only addressed the Sidekick + SuperLookup tab bars; the main `right_tabs` (and the lower-left `bottom_tabs` containing TermLens / Session log) still had Qt's default `ElideRight` and clipped on Mac even with plenty of horizontal space. Fix: same `setElideMode(ElideNone)` treatment applied to both. `setUsesScrollButtons(True)` is left on so a narrow window falls back to scroll arrows instead of overflowing — readable labels in the common case, no overflow in the edge case.
- Cosmetic only, no functional change. Windows behaviour unchanged (it never elided to begin with).

### Note on Mac global hotkeys (still pending)

- The in-app **Cmd+K** binding now correctly opens Sidekick when Supervertaler is the focused app (this is the regular Qt key sequence, unchanged by v1.9.419). However, **global Cmd+K from another app does not summon Sidekick on macOS** because the v1.9.419 fix disables the pynput backend entirely on Darwin to prevent the TSM crash. A Cocoa-native replacement (NSEvent.addGlobalMonitorForEvents or main-thread Carbon `RegisterEventHotKey`) is the proper solution and is tracked separately. For now Mac users should bring Supervertaler to the foreground first (or use the menu-bar tray icon) to summon Sidekick. Note also that Cmd+K is "Connect to Server…" in Finder, so even with a future global binding the choice of shortcut may need revisiting.

---

## v1.9.419 - May 4, 2026

### Fixed (CRITICAL: Mac global hotkeys crashed the entire app)

- **The Cmd+K Sidekick hotkey (and every other global hotkey) crashed Supervertaler on macOS 26 (Sequoia).** Reported by a Mac DMG user with Accessibility + Input Monitoring permissions correctly granted: the very first press of Cmd+K reliably aborted the process with `EXC_BREAKPOINT` (SIGTRAP) inside `libdispatch`'s `_dispatch_assert_queue_fail`. The crash dump pointed at Thread-2 (pynput's keyboard listener) calling Carbon's `TSMGetInputSourceProperty` via `ffi_call` → `ctypes`. Root cause: pynput's macOS listener runs on a background CFRunLoop thread, and macOS 26 hard-asserts that Text Services Manager calls happen on the main thread. The assertion fires the moment a registered hotkey is pressed.
- **The fix:** disable the pynput-based global-hotkey backend on macOS (`platform_helpers.GlobalHotkeyManager._start_pynput`). This prevents the crash but means there are currently no global hotkeys on Mac — Sidekick / SuperLookup / QuickTrans are still reachable via the menu-bar tray icon, which is where Mac users were summoning them anyway. Windows and Linux are unaffected (Windows uses RegisterHotKey on a dedicated message-pump thread; Linux's X11 doesn't have the TSM constraint).
- A proper Cocoa-native replacement (NSEvent.addGlobalMonitorForEvents or Carbon's RegisterEventHotKey, both running on the main thread) is tracked as a follow-up.

### Fixed (Mac UI: tab labels truncated to ellipsis)

- **Sidekick tab labels (Clipboard, AutoFingers, SuperLookup, …) were clipped to "Clipb…", "AutoFi…", "SuperLo…" on macOS** even when there was plenty of horizontal space available. Same issue on the SuperLookup sub-tabs ("QuickTrans" → "QuickT…", "Web Resources" → "Web Reso…"). Qt's default `ElideRight` on `QTabBar` is too aggressive on Mac because the tab-width metric calculation differs from Windows. Fix: explicitly set `ElideNone` and `setUsesScrollButtons(False)` on the affected tab bars (`modules/floating_assistant.py` for Sidekick's left tabs, `Supervertaler.py`'s `SuperlookupTab` for the results sub-tabs). Cosmetic only, no functional change.

### Note on Mac DMG

- This release is being shipped on PyPI first so the Mac user can re-test via `pip install --upgrade supervertaler` without waiting for a new signed/notarized DMG. The Mac DMG will be rebuilt for v1.9.419 (or a later version) once both fixes are confirmed working in the wild.

---

## v1.9.418 - May 4, 2026

### Fixed (CRITICAL: Mac standalone Okapi sidecar broken in v1.9.417)

- **The signed v1.9.417 Mac DMG shipped a JVM that crashed immediately on launch**, so DOCX import via Okapi was unavailable on the standalone Mac build (the in-app log showed *"⚠️ Okapi sidecar failed to start – using built-in filters"* on every launch). Diagnosis (huge thanks to thorough crash-log forensics): PyInstaller's macOS binary-relocation pass extracts `libjli.dylib` from inside the JRE tree out to `Contents/Frameworks/libjli.dylib` and rewrites its `LC_LOAD_DYLIB` / `LC_RPATH` commands. `libjvm.dylib` stays inside the JRE at its original path, untouched. The launcher (rewritten libjli) is then incompatible with the unmodified VM (libjvm), the JLI→JVM call dispatches into a null function pointer, and the JVM crashes with SIGSEGV at `0x0` inside `libjli`'s launcher code. Verified against `hs_err_pid*.log` produced by the broken build.
- **The fix:** copy the bundled JRE into the .app *after* PyInstaller has finished, before code signing, instead of listing it in `Supervertaler_macOS.spec`'s `datas=`. PyInstaller never sees the JRE, never relocates `libjli`, both launcher and VM stay paired with their original install_names. The existing signing pass already discovers JRE Mach-O binaries via `find` and signs them with the full JIT entitlements (`cs.allow-jit`, `cs.allow-unsigned-executable-memory`, `cs.disable-library-validation`), so no signing changes are needed.
- Two file changes: `Supervertaler_macOS.spec` drops the JRE entry from `datas=` (keeps the JAR); `build_macos_signed.sh` adds a post-PyInstaller `cp -R` between the framework-fix step and the code-signing step. The Windows build is unaffected (the issue is specific to PyInstaller's macOS binary-relocation pass).
- v1.9.417 Mac DMG users: please upgrade to v1.9.418. The v1.9.417 standalone Mac build is unfortunately functionally broken for DOCX import; pip users on macOS were unaffected because the lazy-download path doesn't go through PyInstaller.

---

## v1.9.417 - May 4, 2026

### Added (Apple Silicon pip users now get a bundled JRE)

- **Apple Silicon (arm64) Mac pip users no longer need to install Java themselves.** v1.9.416 added macOS lazy-download but only as a JAR-only flavour requiring system Java; this release ships an arm64-specific bundle (~50 MB, JAR + JRE built via `jlink` on the bundled-JRE machine) on the v1.9.416 GitHub release, and `download_install()` now picks it on Apple Silicon Macs.
- **Intel Macs and Linux** still use the JAR-only flow with system Java required (the "Java required" pre-flight still fires for those two combinations). Tracked as a future improvement: an x86_64 Mac bundle and a Linux bundle. The current release detects the architecture via `platform.machine() == "arm64"` so Intel Mac users automatically fall through to the JAR-only path rather than downloading an unrunnable arm64 JRE.
- `INSTALLER_URL_MACOS_ARM64` constant added (`okapi-sidecar-macos-v0.1.6.zip` on the v1.9.416 release). The Mac standalone `.app` (`Supervertaler-v1.9.416-macOS.zip`, also Apple Silicon only) was uploaded to the v1.9.414 / v1.9.415 / v1.9.416 GitHub releases for users who prefer a standalone download to a pip install.

---

## v1.9.416 - May 4, 2026

### Fixed (macOS / Linux DOCX import)

- **macOS and Linux pip users can now lazy-download the Okapi sidecar.** v1.9.415 only supported lazy-download on Windows (it ships a JAR + bundled JRE so no system Java is needed); on macOS / Linux it showed an unhelpful "build from source" message that effectively blocked DOCX import for any user not on Windows. Reported by a Mac user.
- The Mac/Linux flow is JAR-only (~28 MB instead of ~70 MB) and requires the user to have Java on their system. `_ensure_okapi_sidecar()` now checks for Java *before* offering the download, so users without Java see a clear platform-specific install hint (`brew install openjdk` on macOS, `apt install default-jre` etc. on Linux) instead of downloading the JAR and then failing to launch it.
- New helper methods on `OkapiSidecar`: `_download_install_zip_bundle()` (Windows: JAR + JRE bundle), `_download_install_jar_only()` (macOS/Linux: JAR alone), and a shared `_stream_to_file()` helper.
- Future improvement: ship platform-specific JRE bundles for macOS and Linux too so those users don't need their own Java install. Tracked separately.

---

## v1.9.415 - May 3, 2026

### Added (Lazy-download for the Okapi sidecar)

- **The Okapi sidecar is now lazy-downloaded on first use** when running from a `pip install supervertaler` (the desktop EXE release continues to ship the sidecar JAR + JRE bundled inside `_internal/okapi-sidecar/`, so it doesn't trigger this path). Required because v1.9.413 made DOCX import Okapi-only – pip users without the sidecar would otherwise just hit a hard-error dialog whenever they tried to import a `.docx`.
- On first DOCX import attempt, if the sidecar isn't installed locally, Supervertaler shows a one-click prompt: *"Download Okapi sidecar (~70 MB)?"*. On accept it fetches a Windows-specific bundle (JAR + bundled JRE so no system Java needed) from the v1.9.415 GitHub release into `%LOCALAPPDATA%\Supervertaler\okapi-sidecar\`, with a live progress dialog. The next launch finds the sidecar there automatically (no further downloads).
- New `OkapiSidecar.is_installed()` and `OkapiSidecar.download_install(progress_callback=…)` API.
- `_ensure_okapi_sidecar()` helper in the main app handles the three scenarios (already running, installed-but-not-yet-started, not installed → offer download) cleanly and is shared between the single-file and multi-file DOCX import paths.
- macOS / Linux pip users: lazy-download isn't supported yet on those platforms (they need a different JRE bundle). They'll see a clear message pointing at the build-from-source instructions. Adding macOS / Linux bundles is a future improvement.

---

## v1.9.414 - May 3, 2026

### Fixed (packaging)

- **Okapi sidecar is now found in PyInstaller bundles.** v1.9.413's Windows release shipped the sidecar JAR + JRE inside `_internal/okapi-sidecar/` (PyInstaller's standard data-file location for `--onedir` builds), but the lookup logic in `modules/okapi_sidecar.py` only looked next to the EXE itself, never inside `_internal/`. End result: users on the v1.9.413 release saw "Okapi sidecar not installed – using built-in filters" at start-up and DOCX import didn't have the new Okapi-only path available. `_find_sidecar_dir` now also checks `sys._MEIPASS / okapi-sidecar` and `<exe-dir>/_internal/okapi-sidecar` when frozen.

---

## v1.9.413 - May 3, 2026

### Added (Batch pre-translation)

- **Progress dialog now stays open through the post-batch grid rebuild.** Previously the TM batch pre-translation closed its progress dialog at 100 % and *then* called `load_segments_to_grid()` synchronously – on the 2,588-segment test project this locked the UI for ~3 minutes with the title bar saying "Not Responding" and no visible feedback. The dialog now switches to indeterminate mode for the rebuild phase, shows a live row counter (`Reloading grid… (1,250 / 2,588)`), and yields to the Qt event loop every 25 rows.

### Added (Cell-select timing instrumentation)

- **The cell-selection handler now logs timings for any block that exceeds 50 ms, and the total click cost when it exceeds 250 ms** (look for `⏱️ [cell-select] …` in `supervertaler.log`). Some users have reported multi-minute UI freezes when clicking a segment after a fresh import or batch pre-translation; the freeze is intermittent and hard to reproduce, so the next time it bites the log will show exactly which lookup (`find_termbase_matches_in_source`, `find_nt_matches_in_source`, `_update_both_termlens`, `highlight_source_with_termbase`, `_search_mt_and_llm_matches`, …) ate the time. Cheap when nothing is slow; only logs on actual slowness.

### Note

- The async-lookup rewrite that would actually *fix* the cell-select freeze (move TM/MT/LLM/termbase lookups onto a worker thread, cancel stale lookups on rapid navigation, post per-result UI updates) is parked as a separate task. The instrumentation in this release is only the diagnostic step; it doesn't change behaviour beyond extra log lines when something is genuinely slow.

---

## v1.9.412 - May 3, 2026

### Changed (DOCX import is now Okapi-only)

- **The "Choose Import Engine" dialog is gone.** DOCX import always goes through the Okapi sidecar. The dialog and the python-docx import path created a class of bugs where users could end up unable to round-trip exports cleanly, plus the python-docx fallback couldn't produce the `okapi_tu_id` / `okapi_segment_index` metadata the merge endpoint needs at export time.
- **Sidecar pre-flight check.** If the sidecar isn't running when DOCX import is attempted (Java missing, port conflict, sidecar startup failure, …) the user now sees a clear "Okapi sidecar required" dialog with troubleshooting steps, instead of a silent fallback.
- **Multi-file batch DOCX import** at `_import_multifile_project` now also uses the Okapi sidecar per file. Previously this path was *unconditionally* on the python-docx engine with no Okapi option – meaning translators working with multiple complex DOCX files in one project couldn't get the faithful round-trip the single-file path offered. Same pre-flight check applies before any file is touched.
- **Multi-file batch DOCX export** routes by project's `import_engine`. New (Okapi-imported) projects use the sidecar's `/merge` endpoint per file; legacy standard-imported projects keep the python-docx export path so existing on-disk projects still export correctly.

### Fixed (Okapi import/export)

- **Okapi merge no longer silently fails on large projects.** Jetty's default form-content limit (200 KB) caused `POST /merge` to reject the `translations` JSON for any project with more than a few hundred segments – the export then fell back to the standard python-docx path and, for Okapi-imported projects, produced output entirely in the source language because the fallback couldn't match Okapi's tagged source segments against the original DOCX paragraphs. The sidecar now raises the form limit to 100 MB.
- **Hardened the python-docx fallback** so that even if the Okapi merge ever fails for an Okapi-imported project, placeholder tags (`<hyperlink1>`, `<tags2/>`, `<run1>`, `</run1>`, etc.) are stripped from both the source-matching keys and the target text before substitution. Previously these tags broke matching and ended up as literal text in the output.
- **Faithful round-trip for structural codes.** The merge code now indexes every source code by its raw `getData()` string and looks up translation tags by full literal form before falling back to the formatting-tag (`<b>`, `<i>`, `<cf>`) logic. Hyperlinks, runs and placeholder codes that the AI preserved in the translation now reach the exported document as proper Okapi codes – the output document is structurally identical to the source.
- **Tag regex broadened.** The merge regex previously only matched lowercase letters, so `<hyperlink1>`, `<run2>`, `<tags2/>` etc. silently passed through as literal text. Now matches digit-suffixed names and self-closing tags. Self-closing placeholders without a matching source code are dropped silently instead of corrupting the open/close stack.

### Fixed (Okapi sidecar lifecycle)

- **No more manual `java.exe` kills after a sidecar update.** When Supervertaler exited it left the sidecar JVM running on port 8090; on next launch it would reuse the stale JVM, which had the previous JAR's bytecode loaded into memory – any update to the sidecar (new bytecode on disk) had no effect until the user manually killed the process from Task Manager. The Python client now does a version handshake against the sidecar's `/health` endpoint at startup. If the running version doesn't match `EXPECTED_VERSION`, it asks the sidecar to exit via the new `POST /shutdown` endpoint, then force-kills the port-holder process if the polite shutdown fails or is unavailable. New behaviour kicks in exactly once per launch – no respawn loops if the JAR on disk also reports the old version.

### Added (Okapi sidecar)

- **`POST /shutdown` endpoint** for clean restart on version mismatch. Returns `{"status":"shutting down"}` then exits the JVM 150 ms later so the response actually flushes back to the client.
- **Cross-platform port-holder kill** via `netstat -ano` on Windows and `lsof` on POSIX, used as the force-restart fallback when `/shutdown` isn't reachable.

### Added (Project loading)

- **Progress dialog when opening a project from the recent menu** or from `File → Open`. Large projects (1,000+ segments) used to lock the UI thread for several seconds while `load_segments_to_grid()` built widgets per row, and Windows would mark the window "Not Responding". The new dialog shows live progress (`Loading segments into grid… (1,250 / 2,588)`) and yields to the Qt event loop every 25 rows so the window stays responsive.
- **Same progress treatment during DOCX import** via the Okapi route – the dialog now stays animated during extraction (run on a background thread) and during the segment-into-grid build.

### Added (faithful hyperlink round-trip)

- **Hyperlinks, runs and OOXML placeholder codes now round-trip end-to-end through the Okapi merge.** Previously these structural tags (`<hyperlink1>…</hyperlink1>`, `<run1>…</run1>`, `<tags2/>`) were stripped silently from the translation – the exported document came out without hyperlinks, and broken-but-present links in the source disappeared from the target.
- The merge in `FilterService.java` was rewritten to populate the target **per source segment** (`tu.getSource().getSegments()` paired with `target.getSegments()` by `segmentIndex`) instead of concatenating all segment translations into one string and calling `target.setContent(...)`, which collapsed the multi-segment target into one segment and broke the OpenXML filter writer's run-properties stack on SRX-segmented TUs (455 of 1,960 TUs in the test project). This was the missing piece behind the `NoSuchElementException` failures in 0.1.3/0.1.4.
- `buildTargetFragment` now indexes source codes by their raw `getData()` string (`<hyperlink1>`, `</hyperlink1>`, `<tags2/>`, …) and emits `Code.clone()` copies when the AI's translation contains the same literal tag. `clone()` preserves all fields – `outerData`, `originalId`, `mergeable`, … – that the OpenXML filter writer needs for proper open/close pairing.
- Verified end-to-end on a 2,588-segment / 1,960-TU project (LFENIM0002): hyperlinks present in the exported document, formatting and layout identical to the source.

### Sidecar version

- Sidecar JAR bumped from **0.1.0** to **0.1.6** (Jetty form limit, `/shutdown` endpoint, broadened merge regex, diagnostic logging on merge failure, per-segment merge, faithful hyperlink/run/placeholder round-trip via `Code.clone()`).
- `FilterService.merge` keeps a per-segment diagnostic dump (TU id, source segment text, code list with id/type/tagType/data, combined translation) that is logged whenever the writer throws – future structural-code regressions can be diagnosed from `supervertaler.log` without re-instrumenting.

---

## v1.9.411 - May 1, 2026

### Added (Sidekick Chat – Trados-aware mode)

- **The floating Sidekick Chat now picks up the active Trados project context automatically when the Supervertaler for Trados plugin is running.** New `🔗 Trados` chip in the chat tab's context-chip row above the input. Behaviour:
  - **Hidden** until the plugin's localhost bridge is detected for the first time. Users without the Trados plugin never see the chip.
  - **Lit green** when the bridge is reachable. Click to toggle off (the user pref is shared across all chat views via the parent app).
  - **Greyed** when the bridge previously responded but is now unreachable (e.g. Trados was closed mid-session). Recovers automatically when Trados restarts.
  - On chat send, the active Trados segment, surrounding segments, TM matches, termbase hits, and project metadata are fetched via the bridge's `GET /v1/active-context` endpoint and prepended to the system prompt – the same fields the in-Trados Chat itself uses, so answer quality is comparable.
- **Both the default `_do_send` path and `UnifiedPromptManagerQt._context_aware_send`** (the manager-driven override that powers the Sidekick chat tab) now consult the Trados context. Toggling the chip in any chat view affects every send path.
- **New module `modules/trados_bridge_client.py`** – tiny synchronous HTTP client. Resolves the user-data root the same way `llm_clients.load_api_keys` does, reads the handshake file (`<root>/trados/runtime/bridge.json`), and uses very short HTTP timeouts so a stuck/dead bridge never blocks the chat send. Polled every 3 s while the chat tab is alive.

### How to use

1. Start Trados Studio with the Supervertaler plugin (v4.19.52 or later) and Assistant access (paid or trial)
2. Open the **Supervertaler Assistant** panel inside Trados once – this triggers the bridge to start and write its handshake file
3. Open Sidekick (Alt+K) → Chat tab → the **🔗 Trados** chip appears, lit green
4. Type your question. The chat will see the segment you're currently editing, its TM matches, termbase hits, and project metadata.

Privacy: the bridge listens only on `127.0.0.1`, requires a per-session Bearer token, and only starts when the user has Assistant access. Hidden setting on the Trados side (`AiSettings.SidekickBridgeEnabled = false`) for users who want to opt out entirely.

---

## v1.9.410 - May 1, 2026

### Fixed

- **Help → Supervertaler Workbench Help now opens the current GitBook docs site.** The menu item, the placeholder Help tab label, and the `_open_superdocs_tab` fallback all still pointed to the deprecated VitePress site `https://help.supervertaler.com/`. They now open `https://supervertaler.gitbook.io/help/get-started-1/workbench/` — the unified GitBook site that hosts both the Workbench and Trados plugin documentation. Reported by a user.

---

## v1.9.409 - May 1, 2026

### Fixed

- **DeepSeek now appears in the AI Settings dialog.** v1.9.408 added DeepSeek as a provider in `modules/llm_clients.py` but the Settings dialog UI hadn't been updated, so the option was invisible to users. The Settings dialog now has a **DeepSeek** radio button (between Mistral and OpenRouter) with a model dropdown for V4 Pro and V4 Flash, an API key field in the API Keys section, and a 🐋 status-bar indicator. DeepSeek also appears in QuickTrans's per-provider model list. Reported by a user.

---

## v1.9.408 - May 1, 2026

### Added

- **DeepSeek V4 Pro and DeepSeek V4 Flash added as a dedicated AI provider.** DeepSeek now appears in the provider list alongside OpenAI, Claude, Gemini, Mistral, and others. The integration uses DeepSeek's OpenAI-compatible API (`api.deepseek.com/v1`). Set your DeepSeek API key in **Settings → AI Settings** to use it. V4 Pro is the default (flagship model); V4 Flash is available for high-volume, cost-sensitive work.
- **DeepSeek V4 Pro and V4 Flash also available via OpenRouter.** Both models are now listed in the OpenRouter model dropdown under the model IDs `deepseek/deepseek-v4-pro` and `deepseek/deepseek-v4-flash`, enabling access through your existing OpenRouter API key without a separate DeepSeek account.

---

## v1.9.407 - May 1, 2026

### Added

- **Delete individual clipboard entries via right-click or Delete key.** Right-clicking any item in the Clipboard tab (text or image column) shows a context menu with a "🗑 Delete" option. Selecting a row and pressing the **Delete** key also removes it. The item is removed from both the list and the database immediately. A "Clear all" option is also available in the same context menu as a convenience shortcut to the existing top-right button. The footer hint has been updated to surface these actions.

---

## v1.9.406 - May 1, 2026

### Added

- **Per-command enable/disable checkbox in the AutoFingers voice commands table.** Each row now has a checkbox in the first column. Unchecking a command greys it out and excludes it from recognition without deleting it. The checkbox state is persisted immediately to the voice commands file.
- **Batch enable/disable via column header and context menu.** Clicking the checkbox column header toggles all commands on or off in one click (enabling any disabled, or disabling all if all are already enabled). Right-clicking one or more selected rows shows a context menu with "Activate" and "Deactivate" options, applying to the full selection.
- **Multi-select in the voice commands table.** Rows can be selected with `Shift+Click` and `Ctrl+Click` for bulk operations.
- **Double-click to edit a voice command.** Double-clicking any row in the table opens the Edit Voice Command dialog for that command, replacing the previous workflow of selecting then clicking Edit.
- **Resizable and persistent table columns.** All columns in the voice commands table can be resized by dragging the column header dividers. Column widths and the splitter position between the settings panel and the table are saved to settings and restored on next launch.

### Changed

- **AutoFingers tab redesigned as a two-column layout.** The settings controls (sensitivity, hotkeys, model, etc.) are now in a left panel; the voice commands table fills the right panel. A draggable `QSplitter` divides the two sides, making the command list much more usable for large command sets compared to the previous narrow single-column scroll layout.

### Fixed

- **Windows microphone privacy indicator no longer flickers during Always-On voice listening.** The Windows 11 system tray mic icon was briefly disappearing each time a voice command was recognised, causing a visible bounce in the notification area. Root cause: `_process_audio` (transcription – typically 1–2 seconds of blocking work) was being called directly inside PortAudio's audio callback thread, causing PortAudio to close and reopen the audio stream during transcription, which briefly released the microphone. Fixed by separating the audio pipeline into two stages: the PortAudio callback now only performs lightweight amplitude-based VAD and enqueues captured audio via a `queue.Queue`; a dedicated daemon thread (`transcription_worker`) consumes from the queue and calls `_process_audio`. The `sd.InputStream` `with` block stays continuously open for the entire session, so the microphone is never released between commands.
- **Voice commands using keystrokes now work in Trados Studio.** Trados Studio's WPF editor ignores `SendEvent`-style input (PostMessage). Changed `_execute_keystroke` to use `SendInput` in the generated AutoHotkey v2 script, which sends input at the Win32 hardware-input-queue level and is correctly received by WPF applications.

---

## v1.9.405 - April 30, 2026

### Changed

- **`DOCS_BASE_URL` in `modules/help_system.py` updated to `https://supervertaler.gitbook.io/help`** to match the renamed GitBook site slug (chosen over root publishing because GitBook's free plan requires a non-empty slug). Workbench `Topics` constants are unchanged from v1.9.404 – they still begin with `workbench/`, just appended to the new base. F1 / context-help opens will now land at `…/help/workbench/<topic>`.

---

## v1.9.404 - April 30, 2026

### Changed

- **Workbench help docs migrated to the unified Supervertaler GitBook site**, joining the existing Trados-plugin docs in a single space. The previous standalone VitePress site at `help.supervertaler.com` had drifted badly out of sync – the Trados GitBook was up to date and well-maintained, so rather than fix two parallel docs systems, the Workbench content was imported alongside the Trados content under a `workbench/` URL prefix. GitBook's "Parts" feature visually separates the two products' sidebars in a single space (no need for the £250/month Ultimate plan that would have allowed two independent spaces). `modules/help_system.py` updated accordingly: `DOCS_BASE_URL` now `https://supervertaler.gitbook.io`, all `Topics` constants prefixed with `workbench/`. Topic identifiers (`Topics.TRANSLATION_GRID`, `Topics.AI_BATCH`, etc.) are unchanged – call sites continue to work without modification.
- **`Topics.TOOL_VOICE` removed** since the page it pointed to (`tools/voice-commands.md`) was orphaned by the AutoFingers reorganisation in v1.9.401 and is gone from GitBook. Voice/dictation help now lives in the AutoFingers tab inside Sidekick (and in the Workbench Settings → AutoFingers signpost). No callers existed for `TOOL_VOICE`.
- **Source-of-truth for Workbench docs moved to the `Supervertaler-for-Trados` repo** (the docs side, not the plugin side) since that's the repo GitBook syncs from. The old `Supervertaler/docs/help/supervertaler/` location was deleted to avoid confusion. (The marketing site at `Supervertaler/docs/` is unaffected.)

---

## v1.9.403 - April 30, 2026

### Added

- **Sidekick keyboard navigation overhaul.** Sidekick is now fully keyboard-driveable: `Ctrl+Tab` / `Ctrl+Shift+Tab` cycle through the four left-pane tabs (Chat → SuperLookup → Clipboard → AutoFingers, wrapping); `Tab` jumps focus from the active tab content to the right-pane Menu (action tree) and back; `Left` arrow on the Menu returns focus to whatever left-pane widget was previously focused (not just a default – so if you navigated *out of* the clipboard image list with `Right`, `Left` brings you back to the image list specifically). The Tab-pane filter is implemented as an app-level event filter rather than a `QShortcut`, so `Tab` keeps its normal meaning inside text-editing widgets (chat input, search fields, etc.). Sidekick tracks `_last_left_pane_focus` via Qt's `focusChanged` signal, with descendant-walk filtering so action-tree focus events don't pollute the remembered left-pane focus.

- **Visual focus indicator on region headers.** Every "region" of Sidekick that you can keyboard-navigate to now has a header that lights up when keyboard focus lands inside it: the right-pane "Menu" label, the clipboard "📝 Text snippets" header, and the clipboard "🖼 Images" header. Active styling is `#1976D2` (Material blue) text with a 2px underline; inactive is the previous muted dark-blue / grey. The active tab indicator in the `QTabWidget` already showed *which tab* you're on; this fills in the *which sub-region* gap so you never have to guess where keyboard input will land next.

- **Sortable columns in the AutoFingers voice commands table.** Click any column header (Phrase / Aliases / Type / Action / Category) to sort ascending; click again to flip to descending. Especially useful as your command list grows: sort by Type to group all keystroke commands together, by Category to see all "navigation" or "editing" commands side by side, by Phrase for alphabetical lookup. Sorting is suspended during `_populate_table`'s bulk insert (Qt's "items reshuffle while you're inserting" gotcha) and re-enabled after – so adds, edits, removes, and resets all keep working correctly. Edit / Remove identify the selected command by its phrase rather than by row index, so they're already sort-order-agnostic.

### Changed

- **Clipboard tab split into two side-by-side columns: text snippets and images.** Replaces the previous single mixed list. A horizontal `QSplitter` (default 60/40 favouring text – text snippets are typically more numerous, images are fewer but visual) lets users rebalance to taste. Each column has its own header showing the per-kind count ("📝 Text snippets (N)" / "🖼 Images (N)"); the title bar still shows the total. When a column is empty, a centred italic placeholder ("No text snippets yet – copy any text to start" / "No images yet – copy any image to start") appears via a `QStackedLayout` in-place of the empty list, so the layout doesn't jump when the first item arrives. Default focus on opening the Clipboard tab is the text list (where most clips go); `Right` switches to the image list, `Right` again jumps out to the right-pane Menu (matching the Tab pane-switch convention); `Left` reverses through the same path; `Up` / `Down` move within a column and stop at the edges; `Enter` pastes the selected item. Internally the `ClipboardManagerWidget`'s lists, trim cap enforcement, dedup, and per-column "pasted = grey" state are split per-list, but the existing per-kind cap behaviour (200 text + 50 images) is preserved.

- **Right-pane Menu's "Workbench Tools" section heading renamed to "Tools".** Inside Sidekick, the "Workbench" qualifier was redundant (every Sidekick tool is a Workbench tool). Shorter heading, cleaner panel.

- **Hotkey changes apply immediately, no restart required** *(originally v1.9.402 – strengthened in this release with the Sidekick keyboard work above).*

### Fixed

- *(none – this release is all additive UX work; no defects fixed.)*

### Refactor

- **Consolidated nine duplicate `CheckmarkCheckBox` class definitions into one** at `modules/styled_widgets.py` *(landed in v1.9.402; rolled forward here as a stability check – no callers broke).*

---

## v1.9.402 - April 30, 2026

### Added

- **`Ctrl+Alt+A` global hotkey toggles AutoFingers Always-On listening from any application.** Sibling to `Ctrl+Alt+D` (push-to-talk for one-shot dictation), but where Ctrl+Alt+D records a single utterance and stops, Ctrl+Alt+A flips Always-On into continuous-listening mode and back off. Registered through the same `GlobalHotkeyManager` as the other global shortcuts, with handlers that marshal off the pynput thread via `QTimer.singleShot(0, ...)` exactly like the existing patterns. Customisable in Settings → Keyboard Shortcuts → Global as `global_alwayson_toggle`. The previous wiring of Ctrl+Alt+D had been ambiguously documented as "AutoFingers push-to-talk" – users reasonably read that as the Always-On toggle and were confused when it didn't behave that way; this change clarifies the split (Ctrl+Alt+D = single utterance, Ctrl+Alt+A = listening-mode toggle) and gives each its own hotkey.

- **Hotkey rebindings now apply immediately, no restart required.** Previously the OS-level global hotkey listeners (pynput / WinAPI / AHK fallback) were registered once at app startup, and changing a `global_*` shortcut in Settings → Keyboard Shortcuts only took effect on the next launch – so a user could remap and then discover their new key did nothing. `register_global_hotkey` is now idempotent: it stops any previously-running manager before registering a fresh set, so it's safe to call repeatedly. A new `reload_global_hotkeys` method on the Workbench delegates to that registration, and `KeyboardShortcutsWidget` calls it after every save path (the edit dialog, single-shortcut reset, reset-all, and import). The "Changes will take effect when you restart" copy in those success dialogs has been replaced with "applied immediately" / "applied immediately to the OS-level hotkey listeners".

- **System tray icon for Always-On state.** A small microphone glyph drawn programmatically (no extra asset bundled) appears in the system tray whenever Workbench is running. Grey when Always-On is off, red (`#C62828`) when actively listening – convention follows OBS / Zoom / Discord / Microsoft Teams (grey = off, red = live). The icon stays permanently allocated rather than appearing on activation: that avoids the visible "bounce" of neighbouring tray icons that would otherwise occur every time the tray slot is allocated/deallocated. Single-click toggles Always-On; right-click shows a small menu with the same toggle (label adapts: "▶ Start" / "⏹ Stop") plus an "Open AutoFingers in Sidekick" shortcut. Tooltip changes between states ("Always-On is OFF. Click to start." / "Always-On listening." / "recording speech." / "processing speech."). Icon is created once at the end of Workbench `__init__`, so the tray slot is allocated as part of normal app startup instead of on the first Always-On toggle.

- **Always-On "commands-only" mode toggle in the AutoFingers tab.** A new green-checkmark checkbox, *"Listen for commands only – don't type unmatched speech as dictation"*, sits inside the Always-On Listening section. When unchecked (default, current behaviour) Always-On still does both: matches voice commands first, falls back to typing unmatched speech as dictation. When checked, Always-On only fires voice commands – anything spoken that doesn't match a command is logged (so you can see what was heard, useful for debugging "why didn't my command fire?") but never typed anywhere. Pairs nicely with the focus-doesn't-matter behaviour: with commands-only on, Always-On can run all day and any random speech / mumbling / colleague-conversation passes through harmlessly instead of getting injected into whatever window happens to have focus. For dictation, the explicit Ctrl+Alt+D / F9 push-to-talk paths still work normally. Persists under `dictation_settings.alwayson_commands_only`; the in-Workbench `_on_alwayson_dictation` handler reads the flag on every utterance, so the change applies immediately without toggling Always-On off and on.

### Changed

- **Consolidated nine duplicate `CheckmarkCheckBox` class definitions into one** at `modules/styled_widgets.py`. Each of `Supervertaler.py`, `keyboard_shortcuts_widget.py`, `llm_superbench_ui.py`, `model_update_dialog.py`, `pdf_rescue_Qt.py`, `termbase_entry_editor.py`, `tmx_editor_qt.py`, and `unified_prompt_manager_qt.py` previously carried its own copy of this widget – sometimes near-identical, sometimes with subtle drift (16×16 vs 18×18 indicators, slightly different checkmark proportions, some missing the painter `try/finally` cleanup, one using `QPolygonF + drawPolyline` while others used two `drawLine` calls). Net change: −706 lines (the local copies) / +94 lines (the canonical class), so about 612 lines lighter. All callers now import `CheckmarkCheckBox` from `modules.styled_widgets`. The dominant 16×16 + canonical-checkmark variant was kept as the standard; the small visual drift in the three files that used 18×18 indicators is resolved in favour of consistency. Style tweaks in future will propagate everywhere automatically. `superdocs_viewer_qt.py`, which is a deprecated shim, had its orphan copy of the class deleted outright since nothing in that module uses it.

- **AutoFingers "commands-only" checkbox uses the standard Supervertaler `CheckmarkCheckBox`** (green fill with white tick when checked) instead of the default platform `QCheckBox`, matching every other settings checkbox in the app.

- **In-Workbench Always-On status pill now reads "🎤 ALWAYS-ON"** (was "🎤 VOICE COMMANDS ON"). Brings the wording in line with the rest of the AutoFingers naming.

### Fixed

- **F9 hold-to-talk no longer produces Whisper hallucinations** ("MBC 뉴스 이덕영입니다", "you you", and similar Whisper-on-silence artefacts). Root cause: Qt's `QShortcut` defaults to `setAutoRepeat(True)`, so holding F9 fired the `activated()` signal repeatedly while the key was held – and since each fire called `start_voice_dictation()`, which is a *toggle*, holding F9 oscillated between recording and not-recording, producing fragmented near-empty audio buffers. Fixed by calling `setAutoRepeat(False)` on the `voice_dictate` QShortcut. Toggle mode unaffected; hold mode now records the full press duration and transcribes cleanly on release. (The hold-to-talk infrastructure itself shipped in v1.9.401; this is a follow-up correctness fix on the same code path.)

---

## v1.9.401 - April 30, 2026

### Added

- **AutoFingers – Supervertaler's voice command and dictation system – now has its own dedicated tab in Sidekick**, replacing the previous Tools → Supervoice page in Workbench. The new tab (`modules/autofingers_tab.py`, lazy-loaded as the 4th Sidekick tab and listed under Workbench Tools in the right-hand action menu) hosts the Always-On listening toggle, Recognition Engine selector, mic sensitivity, the full voice-commands table with Add/Edit/Remove/Reset, the Whisper model + max-duration + language settings, and the AutoHotkey integration card. The layout is restructured for Sidekick's narrower window: everything stacks vertically inside a `QScrollArea` instead of the old two-column layout, and section grouping was kept consistent with the rest of Sidekick (rounded info banners, GroupBoxes per concern). State sync between Always-On status and the AutoFingers tab uses the existing `_update_alwayson_ui` hook, which now also pushes status updates into `_floating_assistant._autofingers_widget` whenever it exists.

- **Global push-to-talk hotkey: `Ctrl+Alt+D` triggers dictation from any application on your computer.** Registered through the same `GlobalHotkeyManager` used for Ctrl+Alt+L (Superlookup), Ctrl+Alt+Q (QuickTrans), Alt+K (Sidekick) and Ctrl+Shift+C (Clipboard), so it inherits the WinAPI / pynput / AHK fallback chain. Customisable in Settings → Keyboard Shortcuts → Global as `global_pushtotalk`. The hotkey re-uses Workbench's existing `start_voice_dictation` path – same recording, same transcription, same voice-command matching – but the dictated text is now routed through a shared insertion helper (`_insert_dictated_text`) that types into whichever app currently has focus rather than only Supervertaler's grid editor.

- **Hold-to-talk mode for F9 push-to-talk dictation.** Configurable in Sidekick → AutoFingers → Push-to-Talk Mode. *Toggle* (default) keeps the existing press-to-start, press-again-to-stop behaviour; *Hold-to-talk* turns F9 into a walkie-talkie key – recording starts when you press, transcription fires the moment you release. Implemented via a new app-level event filter (`_F9HoldReleaseFilter`) that watches for genuine F9 KeyRelease events (autorepeat releases ignored) and calls `dictation_thread.stop_recording()` directly. Press handling stays on the existing `voice_dictate` QShortcut so toggle mode is unchanged. The global Ctrl+Alt+D hotkey stays toggle-only because chord shortcuts don't lend themselves to hold semantics.

- **Cross-app dictation typing path.** A new `CrossPlatformKeySender.type_text(str)` method types arbitrary text character-by-character into the foreground window: AHK `SendText` on Windows, `osascript`'s `keystroke` on macOS, `pynput.Controller.type` on Linux. Both F9 push-to-talk and Always-On dictation now use this when focus is outside Supervertaler's grid editor – so dictation works in Word, Trados, memoQ, Outlook, browsers, and crucially also in Windows Terminal / cmd / VSCode terminals which don't bind Ctrl+V the standard way. The clipboard remains as a last-resort fallback if typing fails. Translators don't need any extra setup – AHK is already a project dependency.

- **Settings → AutoFingers signpost tab in Workbench Settings.** New entry (right after AI Models in the Settings sidebar) with a brief explanation of why AutoFingers lives in Sidekick rather than Workbench, a green "Open AutoFingers in Sidekick" button that one-clicks into Sidekick's AutoFingers tab via `_open_to_autofingers`, and a quick-reference card listing F9 / Ctrl+Alt+D / Always-On / voice commands plus a pointer to Settings → Keyboard Shortcuts → Global for rebinding. Discoverable for users who don't yet know about Sidekick.

- **"Type" column in the voice commands table.** The data (`action_type`: internal/keystroke/ahk_inline/ahk_script) was always there but the table only showed Phrase / Aliases / Action / Category – so users couldn't see at a glance whether a command was a built-in action, a keystroke, or an AHK script. Now displayed with humanised labels: "Command" / "Keystroke" / "AHK Script" / "AHK Inline".

### Changed

- **Voice product renamed from Supervoice → AutoFingers**, including the file rename `modules/dicta_tab.py` → `modules/autofingers_tab.py` (a brief intermediate "Dicta" name never shipped publicly), the class rename `DictaTab` → `AutoFingersTab`, and Sidekick's symbol renames (`_dicta_widget` → `_autofingers_widget`, `_ensure_dicta_tab` → `_ensure_autofingers_tab`, and so on). User-facing copy across the tab header banner, the "Save Settings" button label, the `QMessageBox` titles, the right-hand menu entry, the tab info-bar tip, and a handful of log/status messages all switched from "Supervoice" to "AutoFingers". Verb forms (e.g. "🎤 Dictate (F9)" button, "Dictation:" log lines) were left as-is – they describe the action, not the brand. The naming evolution: Supervoice was generic-and-functional; the player-piano metaphor of AutoFingers (voice in → keystrokes out) carries a clearer concept and matches Supervertaler's slightly cheeky brand register, while still standing on its own if the product ever ships standalone.

- **Translation-grid button labels clarified.** "Voice Commands OFF/ON" → "Always-On: OFF/ON" (the OFF/ON read as an instruction rather than a state, and the label conflated "voice commands" with the broader thing the toggle actually controls – a continuous listener that does both commands and dictation). The grid's push-to-talk button gained a consistent F9 affordance: the initial label was sometimes "🎤 Dictation" and sometimes "🎤 Dictation (F9)"; both have been normalised to "🎤 Dictate (F9)" in idle state, "⏹️ Stop (F9)" in recording state. Tooltips updated alongside.

- **Out-of-date error-message paths corrected.** Several user-facing error messages told users to "switch to OpenAI Whisper API in Settings → Supervoice" – but that path didn't exist. The correct legacy path was Tools → Supervoice; with this release the canonical path is Sidekick → AutoFingers. All four locations (`voice_commands.py`, `voice_dictation.py`, `voice_dictation_lite.py`, `Supervertaler.py`) now point to the right place, and the AutoFingers tab itself surfaces the same routing in its in-tab tip so users don't need to chase paths.

### Fixed

- **Local Whisper error message in the Windows EXE no longer suggests `pip install supervertaler[local-whisper]`.** That instruction is meaningless inside the portable EXE – extras can't be installed into a frozen build at runtime. The error now detects `sys.frozen` and shows a build-aware message directing the user to switch to OpenAI Whisper API mode (which works in the EXE since the `openai` client is bundled). All four error-message paths updated: the synchronous voice command toggle path in `voice_commands.py`, the lite dictation path in `voice_dictation_lite.py`, the full dictation path in `voice_dictation.py`, and the API-key-missing dialog in `Supervertaler.py`. Reported by erixx11 in Workbench issue #186 – the same EXE Whisper hallucination that prompted the cross-app dictation work below.

- **F9 hold-to-talk produced Whisper hallucinations ("MBC 뉴스 이덕영입니다", "you you").** Root cause: Qt's `QShortcut` defaults to `setAutoRepeat(True)`, so holding F9 fires the `activated()` signal repeatedly while the key is held. Each fire called `start_voice_dictation()`, which is a *toggle* – so holding F9 oscillated between recording and not-recording, producing fragmented near-empty audio buffers that Whisper hallucinated over (those two strings are well-known Whisper artefacts on silence). Fixed by calling `setAutoRepeat(False)` on the `voice_dictate` QShortcut. Toggle mode unaffected; hold mode now records the full duration of the press and transcribes cleanly on release.

- **Dictation outside the translation grid actually goes somewhere.** Both `on_dictation_complete` (F9 / Ctrl+Alt+D) and `_on_alwayson_dictation` (continuous listener) used to hard-check for `EditableGridTextEditor` focus and *silently drop the text* otherwise – a long-standing bug that effectively limited dictation to Supervertaler's own editor. Both now route through a shared `_insert_dictated_text` helper: if the grid editor is focused, direct insert (preserves cursor handling); otherwise, type the text via `CrossPlatformKeySender.type_text` into whichever app holds keyboard focus; if typing isn't available, drop the text on the clipboard so it isn't silently lost.

- **Always-On Listening sensitivity now persists across restarts.** Previously, sensitivity was only saved when the user clicked "Save" on the now-removed Tools → Supervoice tab – which read directly from `self.sensitivity_combo`, a widget on that tab. Settings written from anywhere else were lost. AutoFingers persists the value via a `_set_dictation_keys` helper directly on the unified settings JSON, and `_toggle_alwayson_listening` now reads `alwayson_sensitivity` from the persisted settings rather than from a UI widget that may not exist.

### Removed

- **Old "AutoFingers" (memoQ TMX auto-typer) removed completely** – about 1,700 lines deleted across `modules/autofingers_engine.py` (entire file, ~466 lines), the `AutoFingersWidget` class in `Supervertaler.py` (~784 lines), the Tools menu action, the tab registration in modules_tabs, the `show_autofingers` handler, voice-command dispatch wiring (`_call_autofingers` plus its three command bindings in `voice_commands.py`), the `FeatureModule` entry in `feature_manager.py`, the `tools_autofingers` entry in `ShortcutManager`, the `TOOL_AUTOFINGERS` member in `HelpTopics`, the quick-access sidebar button in `quick_access_sidebar.py`, and the help doc at `docs/help/supervertaler/tools/autofingers.md` along with its `SUMMARY.md` TOC entry. The dependencies it pulled in – `keyboard>=0.13.5` and `pyautogui>=0.9.54` – were removed from `requirements.txt` and `pyproject.toml` after verifying nothing else imported them; `ahk>=1.0.0` stays because Superlookup's hotkey-fallback path uses it. The original AutoHotkey script that inspired the tool still works for anyone who actually wants it. Freeing the AutoFingers name was a prerequisite for the voice-product rename above.

- **`modules/tag_cleaner.py` removed.** ~260 lines of dead code with no remaining callers after the old AutoFingers was deleted.

- **Tools → Supervoice tab removed from Workbench Settings.** AutoFingers in Sidekick replaces it. The supporting `_create_voice_dictation_settings_tab` method (~295 lines), the `_save_voice_settings` settings persister (~37 lines, now unreachable), and the voice-command CRUD methods `_add_voice_command` / `_edit_voice_command` / `_remove_voice_command` (~75 lines, now unreachable – AutoFingers has its own internal CRUD that talks to the shared `voice_command_manager`) all deleted. The tab registration in modules_tabs and the corresponding entry in the Tools menu were removed alongside.

---

## v1.9.400 - April 29, 2026

### Added

- **Clipboard manager added as a third tab in the Sidekick.** Cross-platform via PyQt6's `QClipboard.dataChanged` (Windows / macOS / Linux out of the box, no platform-specific code). Persistent history stored in the existing SQLite database alongside termbases and TMs, with separate per-kind caps (200 text + 50 images) trimmed independently so a flood of one kind cannot push the other out. Each clip's "pasted" state is persisted, so an item that has already been used once stays grey across restarts – inspired by ArsClip's grey-after-paste affordance, which is the standout feature of that Windows-only tool. Click an item to paste it back to the previously-focused application; the dataChanged signal is suppressed for the round-trip so re-capturing the paste itself is impossible.

- **Image clipboard support.** When a raster image is on the clipboard (e.g. a screenshot or a copied figure), it is captured as PNG bytes and stored in a new `image_data` BLOB column on the `clipboard_history` table, with a `kind` discriminator (`'text'` / `'image'`). List items render a 48×48 thumbnail icon and a label such as "🖼 Image 1920×1080 (245 KB)". SHA-1 dedup so re-copying the same image does not insert a second row. Pasting branches on kind: text uses `clipboard.setText` + Ctrl+V, images use `clipboard.setPixmap` + Ctrl+V – most apps that accept clipboard images respond to Ctrl+V identically to text.

- **Ctrl+Shift+C global hotkey opens the Sidekick directly to the Clipboard tab.** Registered through the same pynput `GlobalHotkeyManager` that handles Alt+K and Ctrl+Alt+L, so it fires from any application. Customisable in Settings → Keyboard Shortcuts as `global_clipboard`. Captures the foreground window before opening so paste-and-return correctly returns focus to the source app after a clip is selected.

- **Default-tab preference for the Sidekick.** Right-click any tab on the Sidekick (Chat / SuperLookup / Clipboard) and choose "Open to '…' by default". Persists in `assistant_geometry.json` and applies whenever the Sidekick opens via Alt+K or any other path. Right-clicking the currently-default tab shows a greyed-out checkmark instead. When Clipboard is the default, the list owns focus on activation and arrow keys / Enter work immediately, including in the "already visible" path where Alt+K previously stole focus to the action tree.

- **'E' opens the term editor from the Sidekick's Term Insert Popup (Ctrl+K).** The popup closes itself first, then emits the edit signal, so the editor dialog opens onto a clean screen. Mirrors the keyboard-only edit flow added to the Trados plugin's TermLens popup. Right-click is intentionally not supported – the popup stays 100% keyboard-driven; right-click edit is reserved for the regular TermLens panel on the right.

### Fixed

- **Termbase import silently did nothing after selecting a file.** A redundant `import os` inside `_import_termbase` (at the progress-dialog construction block) shadowed the module-level `os` import. Python treats any name that is assigned or imported anywhere in a function as local throughout that function's scope, so the earlier `os.path.basename(filepath)` call – used to build the Import Options dialog – hit an `UnboundLocalError` before the local import was ever reached. Qt's signal system swallowed the exception, leaving the UI with no feedback. Removed the redundant local import; the module-level `os` is now used throughout. (Originally tagged for v1.9.399 but never released; rolled into this release.)

### Changed

- **Author website references updated from `michaelbeijer.co.uk` to `beijer.uk` across the Supervertaler.com site and bundled help docs.** Touches `docs/index.html`, `docs/privacy/index.html`, `docs/workbench/index.html`, `docs/trados/index.html` (footer "Author's Website" + "Contact" links and the two pricing-section "get in touch" links), `docs/guides/FAQ.md`, `docs/help/supervertaler/reference/faq.md`, and the SuperLookup web-resource lists in `docs/help/supervertaler/superlookup/{overview,web-resources}.md`. michaelbeijer.co.uk redirects to beijer.uk anyway, but having the canonical URL throughout avoids stale Google index entries and unnecessary redirect hops.

---

## v1.9.399 - April 29, 2026

### Fixed
- **Termbase import silently did nothing after selecting a file.** A redundant `import os` inside `_import_termbase` (at the progress-dialog construction block) shadowed the module-level `os` import. Python treats any name that is assigned or imported anywhere in a function as local throughout that function's scope, so the earlier `os.path.basename(filepath)` call – used to build the Import Options dialog – hit an `UnboundLocalError` before the local import was ever reached. Qt's signal system swallowed the exception, leaving the UI with no feedback. Removed the redundant local import; the module-level `os` is now used throughout.

---

## v1.9.398 - April 27, 2026

### Changed
- **Keyboard Shortcuts settings page laid out in two columns for laptop screens.** The previous single-column stack (header → search → table → action buttons → Import/Export → tip → Global Hotkeys) crammed the shortcuts table into a tiny vertical slice on smaller screens – only ~4 rows visible at a time on a 1080p laptop because everything below was reserving room. Reworked into a horizontal `QSplitter` (default 70/30 split, draggable): the **left column** holds search → table → edit/reset buttons with the table set to absorb all spare vertical height; the **right column** stacks the Import/Export group (now vertical, with Export Cheatsheet promoted to the top since it's the action people actually use) and the Global Hotkeys group. Header and description sit above both columns. The cheatsheet tip moved into the Import/Export group so it doesn't claim its own row.

---

## v1.9.397 - April 27, 2026

### Changed
- **Main "📝 Grid" tab renamed to "📝 Editor".** The tab hosts the segment-by-segment translation editor; "Editor" describes what the user does there more directly than "Grid" (which described the underlying widget). Internal identifiers (`grid_widget`, `_get_grid_visibility_state`, etc.) and a couple of fallback error messages mentioning "Grid view" are left as-is – they're either implementation detail or rarely-surfaced fallbacks, and renaming them would just churn the codebase without changing what users see.

---

## v1.9.396 - April 27, 2026

### Fixed
- **AI tab no longer shows two stacked "Prompt Manager" labels.** v1.9.395 wrapped the existing AI tab content (which was a `UnifiedPromptManagerQt` widget exposing its own internal QTabWidget with Prompt Manager / Variables / Assistant) inside an *outer* QTabWidget that added Image Context as a peer to "Prompt Manager". The result: the user saw one "✨ Prompt Manager" tab on the outer strip, and clicking it revealed an inner strip whose first tab was *also* labelled "📋 Prompt Manager" – same label twice in a vertical stack. Reworked to skip the outer wrapper entirely: Image Context is now added as a fourth tab on the existing inner strip (`UnifiedPromptManagerQt.sub_tabs`), so the AI tab strip reads as a single flat row: 📋 Prompt Manager / 📝 Variables / 💬 Assistant / 🎯 Image Context. Navigation helper still works because `self.ai_subtabs` now points at the inner strip directly.

---

## v1.9.395 - April 27, 2026

### Changed
- **Image Context moved from Resources to the AI tab.** The 🎯 Image Context pane (per-project figures the AI sees with each segment) was sitting under Resources alongside TMs and Termbases, but unlike those it is not a translation aid – nothing else in Workbench reads those images, only the AI translation pipeline. Resources should be the home for things both classical CAT lookups and AI use; AI-only inputs belong with the AI flow. The "✨ AI" main tab now wraps a `QTabWidget` with two sub-tabs ("✨ Prompt Manager" and "🎯 Image Context"); previously it was a single Prompt Manager widget. The `show_image_extractor_from_tools` navigation helper used by the Tools menu was rewritten to find the AI tab by label and select the Image Context sub-tab inside it, rather than the old Resources → Image Context route.

### Fixed
- **Clicking an NT pill in TermLens to insert no longer crashes.** Latent bug going back to the original `NTBlock` implementation: `nt_clicked` is a one-arg signal (the NT text), but it was wired straight to `on_term_insert_requested`, which expects two args (source_term, target_term). Every click on an NT pill raised `TypeError: missing 1 required positional argument: 'target_term'` – caught by the global excepthook, so the app didn't actually crash, but the insert never happened. Adapter lambda at the connection site now passes the same string for both arguments (NT entries have target = source by convention).

---

## v1.9.394 - April 27, 2026

### Fixed
- **Non-translatable terms can now be edited and deleted from TermLens.** Follow-up to v1.9.393's NT unification: the `NTBlock` widget that renders the pastel-yellow NT pill in the TermLens panel had no right-click context menu, so a non-translatable added via Ctrl+Alt+N (or marked via the term editor) could be inserted but not edited from the panel – even though `TermBlock`, the regular-glossary cousin, has had Edit / Delete entries since the entry editor was added. Since NTs are now just termbase rows with `is_nontranslatable=1`, the same edit dialog handles them transparently. `NTBlock` now carries `term_id` and `termbase_id` (both threaded through `find_nt_matches_in_source`'s match dicts and the widget's `nt_dict`), exposes `edit_requested` / `delete_requested` signals matching `TermBlock`, and shows ✏️ Edit Non-Translatable / 🗑️ Delete Non-Translatable on right-click. Connections at the instantiation site go straight to the existing `_on_edit_entry_requested` / `_on_delete_entry_requested` host handlers, so the dialog already opens with the NT checkbox pre-ticked (the editor reads `is_nontranslatable` per v1.9.393).

---

## v1.9.393 - April 27, 2026

### Changed
- **Non-translatables unified with the termbase model – standalone NT system removed.** Workbench previously kept non-translatables in standalone `.svntl` files under `user_data/resources/non_translatables/`, surfaced via a dedicated "🚫 Non-Translatables" tab under Resources and matched at runtime by `modules/non_translatables_manager.py`. The Trados plugin had already moved to a different model: NTs are flagged on individual termbase entries via an `is_nontranslatable` column on `termbase_terms`, and that column was sitting unread in the shared SQLite database whenever the two products were pointed at the same termbases. This release deletes the standalone Workbench system in favour of the Trados approach, so a single termbase entry can flag itself as a non-translatable and both products see it.
  - **Schema:** `is_nontranslatable BOOLEAN DEFAULT 0` added to `termbase_terms` in [`modules/database_manager.py`](modules/database_manager.py); migration auto-runs in [`modules/database_migrations.py`](modules/database_migrations.py) so existing databases get the column on next launch via `ALTER TABLE`. The legacy `non_translatables` SQLite table (regex patterns, never actually used by the runtime – the .svntl Python system was the production path) is no longer created on new databases; old databases that have it just keep an unused table.
  - **Read path:** [`_build_termbase_index`](Supervertaler.py) now selects the new column and stores it on each in-memory term entry. [`find_nt_matches_in_source`](Supervertaler.py) was rewritten to walk the existing termbase index, filter for `is_nontranslatable=1`, and return positions via the same regex patterns the index already maintains for source-term lookups – no new database round-trips, and the function's return shape is preserved so all 9 call sites work unchanged. NT highlighting in the source cells continues to render in pastel yellow (#FFFDD0).
  - **Write path:** the grid right-click "🚫 Add to Non-Translatables (Ctrl+Alt+N)" action now adds a termbase entry with `is_nontranslatable=1` (target_term mirrored from source_term, the Trados convention) on the project termbase, falling back to the first writable activated termbase. The in-memory index is rebuilt synchronously after the insert so highlighting updates on the current segment without a project reload.
  - **Term editor:** [`modules/termbase_entry_editor.py`](modules/termbase_entry_editor.py) gains a "🚫 Mark as NON-TRANSLATABLE (copy source to target unchanged)" checkbox alongside the existing "Mark as FORBIDDEN" toggle. Ticking it auto-mirrors source into target so the entry copies through at translation time. New `set_nontranslatable(term_id, is_nontranslatable)` method on [`modules/termbase_manager.py`](modules/termbase_manager.py) for programmatic toggles.
  - **Removed:** the entire 489-line `create_non_translatables_tab()` method, the "🚫 Non-Translatables" tab from the Resources tab strip, the `nt_manager` instantiation, all per-project NT save/restore logic (the `nt_settings` Project field is left in place but unused – old projects load fine, new projects just don't populate it), and `modules/non_translatables_manager.py`. The `.svntl` files on disk under `user_data/resources/non_translatables/` are not touched by this release; they are simply orphaned. Anyone who wants the data can re-add the entries through the new termbase NT toggle.

---

## v1.9.392 - April 27, 2026

### Fixed
- **TermLens now finds matches regardless of language naming or direction.** Two bugs combined to silently break TermLens when projects and termbases used different conventions for the same languages. First, `_get_termbase_status_hint` ([Supervertaler.py:14385](Supervertaler.py)) did a direct string-equal on lowercased lang fields, so a project saved with full English names ("Dutch", "English") would not match a termbase imported with ISO codes ("nl", "en") even though they were the same languages – the UI showed "Glossaries don't match language pair" and no matches at all. Second, `_build_termbase_index` ([Supervertaler.py:26319](Supervertaler.py)) only stored `source_term` as the lookup key, so a project running nl→en against a termbase stored en→nl would never find anything – the Dutch segment text was being searched against the `source_term` column (in English). Both paths now normalise lang strings through the existing `_convert_language_to_code` helper, and the index now swaps `source_term` / `target_term` for termbases whose direction is reversed relative to the project. The swap happens once at index-build time, so the search code itself is unchanged and remains in the fast path. Restart Workbench (or reload the project) to rebuild the in-memory index after upgrading.

---

## v1.9.391 - April 27, 2026

### Added
- **System tray icon with close-to-tray, start-minimized, and start-with-computer toggles.** Right-clicking the new tray icon offers Show Workbench, three checkable preferences (Close to tray, Start minimized to tray, Start with computer), and Quit. *Close to tray* makes the window's X button hide to tray instead of quitting the process. *Start minimized to tray* launches with no visible window – useful when paired with *Start with computer*, which registers Workbench with the OS autostart facility (Windows `HKCU\…\Run`, macOS `~/Library/LaunchAgents`, Linux FreeDesktop `~/.config/autostart`) without elevation. The autostart launch command resolves the GUI Python variant in dev (`pythonw.exe`) and `sys.executable` directly in frozen builds, so no terminal flashes at boot. `QApplication.setQuitOnLastWindowClosed(False)` is flipped on when the tray initialises, with `QApplication.quit()` called explicitly from the X-button path so close-to-tray-off behaves as before.
- **Diagnostic log now captures everything the dev terminal would.** Four sources that previously vanished under `pythonw` / the frozen Windows .exe (`console=False`) are now teed into `supervertaler.log` via a new `_install_log_hooks()` helper called immediately after `_setup_diagnostic_log()`: `threading.excepthook` for worker-thread crashes, `qInstallMessageHandler` for `qWarning` / `qCritical` / layout warnings, the stdlib `logging` module (router for every `modules/*` `logger.info(...)`), and `logging.captureWarnings(True)` for `DeprecationWarning` / `ResourceWarning` etc. Combined with the existing `_Tee`, the in-app diagnostic log is now a true superset of stdout/stderr – Help → Open Diagnostic Log shows everything `run.cmd` would have shown.
- **Okapi sidecar stdout drained into the log.** A daemon thread now reads `self._process.stdout` line-by-line in `OkapiSidecar.start()` and forwards each line via `logger.info("[sidecar] …")`. Fixes a latent ~64 KB Windows pipe-buffer hang where the sidecar would block on its next write once the unread pipe filled, and surfaces sidecar diagnostics in the Workbench log.

### Changed
- **Default global hotkey for Supervertaler Sidekick changed from `Ctrl+Shift+A` to `Alt+K`.** Existing user customisations of `Ctrl+Shift+A` (the previous default) are auto-migrated to the new default on first launch via a value-upgrade path in `ShortcutManager.load_shortcuts`; explicit overrides to anything else are preserved. Frees `Ctrl+Shift+A` for the AutoFingers menu shortcut, which had been silently shadowed by the global hotkey on Windows.
- **Sidekick is now a `Qt.WindowType.Tool` window – invisible when not in use.** Sidekick previously had a regular `Qt.WindowType.Window` flag, which gave it a taskbar entry whenever it was visible. Switching to `Tool` means Windows never allocates a taskbar slot for Sidekick: it has no taskbar icon, no Alt+Tab entry, no system tray presence – it's purely summon-on-demand via Alt+K (global) or Ctrl+Q (in-app). Trade-off accepted: the previous "click the Sidekick taskbar icon to bring it back" path is gone; the keyboard hotkeys are the only way in, which matches how the user invokes it 99% of the time anyway.
- **Sidekick activation tries plain `SetForegroundWindow` first, falls back to AttachThreadInput.** The original implementation in `_force_foreground_focus` always used the AttachThreadInput + SetForegroundWindow trick to bypass focus-stealing prevention. That trick is unnecessary for hotkey-driven summons (Windows briefly grants the receiving process foreground rights when a registered global hotkey fires) and carries Windows shell side effects on Windows 11. Plain `SetForegroundWindow` runs first now; the AttachThreadInput trick is reserved as a fallback for cases where the plain call is rejected (e.g. Trados Studio aggressively reclaiming focus from a non-hotkey path).
- **`global_quicklauncher` shortcut ID renamed to `global_sidekick`.** Internal cleanup: the global hotkey's settings ID, callback (`_on_pynput_quicklauncher` → `_on_pynput_sidekick`), main-thread handler, and clipboard reader along the global Sidekick chain now all read "sidekick" rather than the stale "quicklauncher" name. `_LEGACY_IDS` and the `load_shortcuts` migration map both accept the old key, so persisted user customisations carry across. The editor-only QuickLauncher prompt-action menu (Ctrl+Q within the grid) keeps its `editor_open_quicklauncher` ID – that genuinely is QuickLauncher.
- **B:/ "no disk in drive" pop-ups suppressed at startup on Windows.** A `SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX)` call early in `main()` tells Windows to fail-silently when in-process code probes a removed/unmounted drive, instead of presenting a system-modal dialog the user can do nothing useful with.

### Known limitations
- **Tray icons briefly bounce when Sidekick is summoned via Alt+K on Windows 11.** Investigated and ruled out as taskbar reflow, focus-stealing trick, clipboard manager, and Workbench's own tray icon being queried. Appears to be a Windows 11 shell broadcast triggered by any visible foreground transition into a previously-invisible window of a process that has tray icons. Considered an acceptable cosmetic quirk – the bounce is brief and the user is rarely looking at the tray when summoning Sidekick.

---

## v1.9.390 - April 27, 2026

### Changed
- **Tools tab sub-tabs are horizontal again.** v1.9.x had switched the Tools sub-tab strip (AutoFingers / Superconverter / PDF Rescue / Superbench / Supervoice / Text Encoding Repair / TMX Editor) to a vertical sidebar borrowed from the Settings tab. Reverted to a horizontal `QTabWidget` to match every other tab strip in the Workbench. The sidebar-only theming and font-scaling helpers (`_update_tools_sidebar_theme`, `_update_main_tabs_fonts`) already isinstance-check for the sidebar widget, so they cleanly no-op against the standard tab widget – no other code touched.
- **TMs section streamlined: Statistics and Maintenance sub-tabs folded into TM List.** The TMs section had four sibling sub-tabs (TM List, Browse All, Statistics, Maintenance); two of them were essentially noise (Statistics duplicated information already shown in the TM List entries column; Maintenance was two buttons living on a permanently-visible tab). Now TM List shows a live "📊 N translation units across M TMs" summary line at the top, and the bottom toolbar gets two new buttons – **🧹 Maintenance…** opens a focused cleanup dialog (the underlying `TMManagerDialog` instance has its other tabs hidden via `setTabVisible(False)`, so the user lands on cleanup actions with no surrounding noise), and **📊 Copy stats** copies a markdown TM statistics report (totals, averages, per-TM breakdown sorted by entry count, ISO datetimes, share percentages) to the clipboard for pasting into reports / notes.
- **Superlookup menu and right-click actions now correctly land in Sidekick.** When Superlookup moved from a Tools sub-tab to a Sidekick pane, the navigation method `_go_to_superlookup()` was left trying to switch to a sub-tab that no longer exists – the for-loop fell through silently. Edit menu's Superlookup item, Tools menu's Superlookup item (Ctrl+K), and the source-cell / target-cell right-click "Search in SuperLookup" actions all funneled through this dead method. `_go_to_superlookup()` now routes through the FloatingAssistant, calling `assistant.show_superlookup()` to bring Sidekick to the foreground and select its SuperLookup pane, then optionally invokes `search_with_query()` on the Sidekick's widget when a query is provided.

### Fixed
- **TM Maintenance cleanup operations no longer freeze the UI.** Both "Remove identical source/target pairs" and "Remove duplicate sources (keep newest only)" used to run synchronously on the main thread, which meant Workbench would hang for minutes on TMs with hundreds of thousands of entries (and "Remove duplicate sources" was especially bad – it issued one SELECT and one DELETE per duplicated source hash, so on a 877k-row TM with many duplicates it could mean tens of thousands of synchronous round-trips with the UI marked Not Responding throughout). Cleanup now runs on a `TMCleanupThread` worker that opens its own SQLite connection inside `run()` (WAL mode lets it coexist with the main thread's reader), with a `QProgressDialog` driven by `progress(done, total, status)` signals from the worker. "Remove identical pairs" shows an indeterminate progress dialog with running status text ("Counting…", "Deleting N pairs…"); "Remove duplicate sources" shows a determinate progress bar that ticks through duplicate-hash groups so the user can see the operation isn't stuck. Cancel button intentionally omitted – a partial cleanup mid-DELETE leaves the user wondering which rows survived; better to run to completion and report a deterministic outcome.
- **Dotted focus rectangles suppressed app-wide.** The Windows Vista QStyle (and to varying degrees the macOS and Fusion styles) draws a dotted focus rectangle on focused tabs, buttons, and item views via `QStyle.drawPrimitive(PE_FrameFocusRect, …)`. CSS `outline: none` only suppresses this when the stylesheet engine has fully taken over rendering for the widget – it doesn't on widgets without per-widget stylesheets. The Workbench had been adding `QTabBar::tab:focus { outline: none; }` per widget across a dozen-plus call sites to work around this, and any new widget without that boilerplate would leak the rectangle (most recently visible on the new horizontal Tools sub-tab strip). Replaced with a single `QProxyStyle` subclass that intercepts `PE_FrameFocusRect` and skips the draw, installed once via `app.setStyle(_NoFocusRectStyle(app.style()))`. Catches every widget at the QStyle level – tabs, buttons, list/tree/table items, future widgets – without per-widget styling. The redundant per-widget rules are left in place as belt-and-braces.

---

## v1.9.389 - April 26, 2026

### Added
- **Find on page in Sidekick web views.** Every embedded browser view in SuperLookup → Web Resources now has a permanently visible find toolbar pinned to the bottom (live search as you type, ▲/▼ + Enter/Shift+Enter to cycle matches, "Match case" toggle, "X of N" status, ✕ to clear). Ctrl+F focuses the input and selects existing text; Esc clears the search and returns focus to the page. The toolbar is always shown rather than hidden-behind-a-shortcut so users discover it without needing to know Ctrl+F exists. Implemented via `QWebEnginePage.findText()` with a result callback for live match counts; the shortcut uses `WidgetWithChildrenShortcut` context so it doesn't collide with the segment editor's Ctrl+F.
- **"Open page in default browser" and "Open link in default browser" right-click items.** When you right-click anywhere in an embedded web view, the context menu now offers "Open page in default browser" (always available for any real page); right-clicking on a hyperlink additionally offers "Open link in default browser". Both route through `QDesktopServices.openUrl` to your system default browser. Useful when you want to leave the embedded view for things the embedded Chromium doesn't handle as well as your real browser (extensions, persistent logins, multi-tab workflows). Implemented by subclassing `QWebEngineView` and overriding `contextMenuEvent` to append items to the standard Chromium menu rather than replacing it.

---

## v1.9.388 - April 25, 2026

### Changed
- **Supervertaler Sidekick now uses the canonical Sv brand icon.** The floating assistant window previously had no window icon at all (so it fell back to Qt's generic default in the taskbar and alt-tab switcher) and its custom title bar showed only the "Supervertaler Sidekick" text. Now it sets `setWindowIcon(assets/icon.ico)` – the same file the main Workbench window uses – and the title bar leads with a 24×24 render of `assets/icon_24.png` before the title text. Title bar height bumped 36→40 px so the 24×24 icon (and the equally-sized window buttons) sit on an 8 px top/bottom margin rather than hugging the edges. The route to getting this looking right ran through five commits to untangle a Qt stylesheet cascade – the final fix was an explicit `border-radius: 0` override on the icon QLabel to stop the parent bar's `border-top-*-radius` rule from triggering Qt's styled-background painter on the label (which was shifting the pixmap a pixel down and making the circle look clipped at the bottom).

---

## v1.9.387 - April 24, 2026

### Changed
- **Sidekick "Special Characters" and "Personal Snippets" are now file-backed and user-editable.** Each entry lives as its own `.md` file under `user_data/snippet_library/`, with folder structure mapping to tree categories in the Sidekick menu (e.g. `snippet_library/Special Characters/Arrows.md` appears under "Special Characters → Arrows"). File format mirrors the prompt library's `.md` + YAML-ish front matter convention (`type: snippet`, `name`, `category`, `default: true`, `read_only: true`) so the two libraries can be unified behind a single editor in a later release without a data migration. Default snippets are seeded on first launch and re-seeded only when absent – user edits, renames, and deletions are preserved. Users can already customise today by editing or adding .md files directly under `snippet_library/`; an in-app editor will follow in a subsequent release. "Text Conversions" remains hardcoded for now – those are logic entries (casing, wrap-in-X, strip-U+00AD), not static payloads, and don't fit the file-backed model.
- **Removed a hardcoded real phone number (`07475771720`) that was shipping in the default Personal Snippets.** The category now seeds with a single "Example snippet" placeholder that explains how to add your own entries. Anyone who has already installed a previous release will still have the old hardcoded entry in their Sidekick menu – deleting the in-memory hardcoded version is immediate with this release; the new .md-file example will appear on next launch. No one should have typed that number into an email from inside their CAT tool, but here we are, so: fixed.

### Added
- **`modules/snippet_library.py` – new minimal loader class `SnippetLibrary`.** Mirrors `UnifiedPromptLibrary` in shape (recursive `rglob("*.md")` walk, YAML-ish front-matter parsing, `ensure_defaults()` for idempotent first-run seeding) but without the prompt-specific metadata (no variables, no system/user distinction, no model binding, no quicklauncher flags). Default snippet definitions live in the module-level `DEFAULT_SNIPPETS` constant, migrated verbatim from the pre-v1.9.387 hardcoded entries in `floating_assistant.py` (minus the phone number, minus the two placeholder one-character snippets that looked like ad-hoc test entries).

### Changed (follow-up, same day)
- **Default snippet labels now show the actual characters in the Sidekick menu.** The front-matter `name:` field (already supported – `label = meta.get('name') or md.stem` on the load side) was previously set identical to the filename in the bundled defaults, which wasted its purpose. `DEFAULT_SNIPPETS` now supplies both `filename` (ASCII-safe, stays stable across Windows/macOS/Linux and cloud sync) and `name` (unicode glyph preview shown in the menu), and `ensure_defaults()` reads `filename` explicitly rather than sanitising `name`. So the Arrows entry, for example, is `Arrows.md` on disk but `← → ↑ ↓ ⇄ ↔` in the menu. Users who already had v1.9.387's original snippet files will still see the plain labels – delete the `user_data/snippet_library/Special Characters/` folder (or any individual file) and relaunch to pick up the new labels; user-added and user-edited files are unaffected.

---

## v1.9.386 - April 24, 2026

### Removed
- **Supercleaner tool removed from the Tools tab, Tools menu, and the "Import monolingual DOCX" options dialog.** The DOCX tag cleaner (inspired by TransTools Document Cleaner / Unbreaker / CodeZapper) has been retired. It was not safe to use in its current state and would need substantial work to reach the quality bar TransTools has already set – time better spent on translator-facing features elsewhere. Continuing the simplification series that started with Superbrowser in v1.9.385. `modules/supercleaner.py`, `modules/supercleaner_ui.py`, the "🧹 Supercleaner" Tools-tab sub-tab, the Tools-menu entry, and the "🧹 Clean document before import" checkbox in the DOCX import dialog (including its cleanup handler) have all been deleted. Translators wanting pre-import DOCX cleanup should use TransTools (or similar) directly and then import the cleaned file into the Workbench as normal. The glossary entry has been removed; agent-archive references are kept for historical accuracy.

### Changed
- **Compact UI chrome is now the default on new / unset installs.** The toggle (added in v1.9.385's follow-up commit under Settings → View Settings → 📐 Compact UI Chrome) now defaults to enabled. Tightens padding on the menu bar, main tab strip (Grid / Resources / AI / Tools / Settings), and the right-panel tab strip (Match Panel / AI Assistant / Preview / Segment note / …) to give the translation grid more room. Users who previously toggled the setting off keep that preference; only users who have never touched it are affected on next launch. Still a toggle – nothing has been locked in. The startup hook now runs at 0 ms instead of 100 ms, so the compact styling lands in the same event loop iteration the widgets are constructed in – no visible flash of uncompact chrome before the restyle on fresh installs.

---

## v1.9.385 - April 24, 2026

### Removed
- **Superbrowser tool removed from the Tools tab and Tools menu.** The multi-chat AI browser (ChatGPT / Claude / Gemini in three resizable columns, with persistent login sessions) has been retired. First of a series of planned simplifications – the goal is to strip out functionality that isn't being used so the Workbench stays focused on what translators actually reach for. Users who want the same multi-chat workflow can open each service in their own browser window, or use a dedicated multi-chat tool. The `modules/superbrowser.py` module and `docs/technical/SUPERBROWSER_GUIDE.md` guide have been deleted; the "🌐 Superbrowser" entries in the Tools menu and Tools tab, the `superbrowser_profiles` migration list entry, the `.gitignore` rule, the Workbench landing-page feature card, and the glossary entry have all been cleaned up. `PyQt6-WebEngine` stays in `requirements.txt` because SuperLookup still uses it for its embedded web browser. Any `workbench/superbrowser_profiles/` folder on disk is left in place – it's harmless orphan data and can be deleted manually if you want to reclaim the space.

---

## v1.9.384 - April 24, 2026

### Changed
- **Floating panel renamed from "Supervertaler Assistant" to "Supervertaler Sidekick".** The new name disambiguates the Workbench's floating panel (Chat + SuperLookup tabs + Workbench Tools / Prompts / Special Characters / Personal Snippets / Text Conversions menu) from the "Supervertaler Assistant" panel inside Supervertaler for Trados, which keeps that name. The two products are commonly installed together by the same user and were sharing a title, which was getting confusing. Renamed across user-facing strings in the Workbench codebase (`Supervertaler.py`, `modules/floating_assistant.py`, `modules/chat_message_delegate.py`, `modules/unified_prompt_manager_qt.py`, `modules/shortcut_manager.py`), help docs under `docs/help/supervertaler/`, and `README.md`. Internal Python identifiers (file names like `floating_assistant.py`, any `Assistant`-named classes) are kept as-is – they are not user-facing. Historical CHANGELOG entries from before this rename keep the old name for accuracy.

---

## v1.9.383 - April 22, 2026

### Fixed
- **Bulk-translate (Ctrl+Shift+T) with a Custom Endpoint provider no longer errors out asking for a Custom API key.** The single-segment path (Ctrl+T) correctly treated the API key as optional for `custom_openai` providers, but the batch path only special-cased Ollama and fell through to the generic "API key missing" error for custom endpoints. Now the batch path matches the single-segment path: the key is optional for custom endpoints. Thanks to [@jaychen-xilinx](https://github.com/jaychen-xilinx) for the report ([#182](https://github.com/Supervertaler/Supervertaler-Workbench/issues/182)).

### Changed
- **Trados package (SDLPPX) import now shows progress throughout and is cancellable.** Large packages (hundreds of SDLXLIFFs, 200k+ words) used to look frozen for the entire import. Now there is per-stage progress feedback and a Cancel button:
  - **Extraction:** "Extracting package.sdlppx..." while the zip is unpacked.
  - **SDLXLIFF parsing:** "Parsing SDLXLIFF: file.sdlxliff (N / total)" – the biggest time sink for large packages, previously completely silent before the info dialog appeared.
  - **Segment build:** "Building segments: file.sdlxliff (N / total)" – the post-dialog loop that maps parsed segments into Workbench's internal format.
  - Cancel at any stage cleans up the temp extraction directory and returns to the project you had open. ([#185](https://github.com/Supervertaler/Supervertaler-Workbench/issues/185))

---

## v1.9.382 - April 17, 2026

### Added
- **Claude Opus 4.7 support.** Anthropic's new flagship model (released 2026-04-16) is now selectable in Settings → LLM Models under the Claude provider and in the OpenRouter model list (`anthropic/claude-opus-4.7`). Opus 4.7 has a 1M-token context window, 128k max output, and is Anthropic's most capable generally available model. Pricing is $5 / input MTok, $25 / output MTok – the same as Opus 4.6. Sonnet 4.6 remains the recommended default for most translation work; reach for Opus 4.7 when you need top-tier reasoning or long-context jobs. See [What's new in Claude Opus 4.7](https://platform.claude.com/docs/en/about-claude/models/whats-new-claude-4-7) for details.

### Fixed (cost estimates)
- **Corrected stale pricing for Claude Opus 4.6 and Haiku 4.5.** The internal pricing table in `llm_pricing.py` and the `CLAUDE_MODELS` metadata in `llm_clients.py` had Opus 4.6 at the pre-4.6 rate of $15 / $75 per MTok – Anthropic dropped Opus pricing to $5 / $25 with the 4.6 release. Haiku 4.5 was listed at $0.80 / $4.00, corrected to the current $1.00 / $5.00. Cost estimates in the LLM Leaderboard and Reports panels were over-stating Opus usage and under-stating Haiku usage – now accurate.

### Note on Opus 4.7 tokenizer
- Claude Opus 4.7 uses a new tokenizer that can use **~1.0×–1.35× more tokens** for the same text compared to earlier models. Cost estimates based on output-token counts are accurate; estimates based on the `chars / 4` heuristic for unsent text will under-estimate Opus 4.7 costs by a similar margin. Actual billing is based on Anthropic's token counts.

---

## v1.9.381 - April 17, 2026

### Added
- **Silent launch – no more terminal window next to the app.**
  - Pip users: running `supervertaler` no longer opens a black console window alongside the Qt UI. The command is now registered under `[project.gui-scripts]` (Windows GUI subsystem). A new `supervertaler-debug` command runs the same app but keeps a terminal attached for live stdout/stderr.
  - Source users: new `run-silent.cmd` sits next to `run.cmd`. Double-click the silent one for a clean launch; `run.cmd` stays for live debugging.
  - The production `Supervertaler.exe` and Start Menu shortcut were already console-less – unchanged.
- **Diagnostic log file** – every launch (silent or not) now tees stdout and stderr into a rolling log at `<user_data>/workbench/logs/supervertaler.log`. Rotates once past ~2 MB to `supervertaler-previous.log`. The log captures all `[LOG]` lines, diagnostic prints, and full tracebacks on unhandled exceptions. Works even under pythonw where there's no console at all – crashes now leave a paper trail instead of disappearing silently. Implementation guards against pythonw's detached std handles so silent mode can't break logging. ([Supervertaler.py: `_setup_diagnostic_log`](Supervertaler.py)).
- **Help menu entries: 📄 Open Diagnostic Log and 📁 Open Log Folder** – one-click access to the log file (opens in the OS default editor) or the folder (opens in Explorer/Finder) for users reporting issues.
- **Help → 📋 Copy Version Info** now also includes the diagnostic-log path, so a copy-paste into an email or GitHub issue always tells support exactly which file to ask for.
- **QuickTrans is now the first sub-tab of SuperLookup.** Clicking 🔍 SuperLookup with selected text auto-fires QuickTrans (same behaviour as Ctrl+Alt+Q) and lands on the populated QuickTrans results – no Translate-button click needed. TMs and Termbases populate in parallel on the other sub-tabs.

### Changed
- **Removed the separate "⚡ QuickTrans" action from the right-hand Workbench Tools menu.** Clicking "🔍 SuperLookup" with selected text now does everything QuickTrans used to do (opens SuperLookup, switches to the QuickTrans sub-tab, fires MT across all enabled providers), so the dedicated entry was redundant.
- **QuickTrans moved from a top-level outer tab into a SuperLookup sub-tab** (at position 0, before TMs). Outer tabs are now just **Chat** and **SuperLookup** – one click less to reach the translation tools, and the three result types (TMs, Termbases, QuickTrans) sit next to each other.
- **First-pass rename: Superlookup → SuperLookup** in user-visible strings. Tab labels, menu actions, dialog titles, header labels, and tooltips now consistently use the camel-cased spelling. Internal class names, module filenames, and debug prints are scheduled for later passes (explicit "slowly" scope).

### Fixed
- **Web resource searches (IATE, Linguee, ProZ.com, BabelNet) fired with the wrong language pair when the From/To filters were "Any".** The URL builder hard-coded a fallback of English → Dutch. Now falls back to the current project's language pair (and the saved default pair when no project is loaded), matching what QuickTrans already uses. Same source of truth throughout the app.
- **Linguee URLs use a canonical slug order** (English is always first in any English↔X pair; `source=auto` handles the query direction). Our URL builder was sending `dutch-english` when the project direction called for it, which Linguee redirects and QWebEngine renders as a blank page. Now we put English first when it's present, or fall back to alphabetical for non-English pairs – matches Linguee's actual URL scheme. Direction of results is unaffected (the query word is auto-detected).
- **Maximise button on a secondary monitor** no longer sends the window flying to the primary monitor. Was calling `QApplication.primaryScreen()` unconditionally; now uses `self.screen()` (the window's current monitor) with a `screenAt(frameCenter)` fallback.

---

## v1.9.380 - April 16, 2026

### Added
- **Language pair now persists across restarts – automatically.** Changing the Source or Target language in Settings → Language Pair auto-saves to disk on each change; no more clicking "💾 Save Language Settings". The button is kept for users who want a visible confirmation dialog. This fixes the long-standing complaint that the defaults reverted to English → Dutch on every restart.
- **Dynamic base-language column headers in Superlookup.** The TMs and Termbases result tables now show the project's actual base source/target language names (e.g. "Dutch | English | TM") instead of the generic "Source | Target". Region variants like `nl-NL` or "English (US)" are stripped to just "Dutch" / "English" to keep headers compact.
- **Base-language column headers in the Termbases terms table** (Resources → Termbases). When you select a termbase, the "Source Term" / "Target Term" columns now show the termbase's actual languages.

### Changed
- **Renamed Resources tab "Glossaries" to "Termbases"** for consistency with the Trados plugin and the rest of the product. The section header, description, search placeholder, help panel, and error messages inside this tab now all say "Termbases" / "termbase".
- **Removed the three "Enable … search in Superlookup" master checkboxes** from the Superlookup Settings sub-tabs (TMs, Termbases, Web Resources). Replaced with a simpler model: **everything starts checked; uncheck items you don't want; uncheck every item of a type to disable that type entirely.** The master checkboxes were confusing (they looked washed out because of a stylesheet conflict with CheckmarkCheckBox) and redundant with the per-item lists.
- **TM and Termbase resource lists now auto-populate when Superlookup first opens** – users no longer have to click "🔄 Refresh List" before seeing anything. Resolves a disconnect where searches were running (and returning results via the "empty = search all" fallback) but the Settings panel looked empty.
- **Superlookup tables: headers bold, body cells non-bold** – proper visual hierarchy. Enforced via `QHeaderView::section { font-weight: bold; }` stylesheet on top of `setFont` so Qt themes can't strip the bold.

### Fixed
- **Web Resources tab was blank after every search.** `display_mt_results()` referenced `mt_results_table`, which no longer exists (MT was moved to QuickTrans). The resulting `AttributeError` aborted `perform_lookup()` before the web search could run, so views were created but never got their URLs set. Same fix guards `on_results_tab_changed()` against a missing `supermemory_engine`.
- **Stale Settings-tab index in `on_results_tab_changed`** – was hard-coded to `index == 5` (the position before MT and Supermemory tabs were removed). Now looks up the Settings tab by its title, so adding/removing tabs in the future won't break the refresh trigger.
- **Language pair load race condition** – `load_language_settings()` ran *after* the Language Pair combo boxes were built, so the UI showed the hard-coded defaults (English/Dutch) while `self.source_language` / `self.target_language` held the correct loaded values in memory. The next dropdown change then wrote a half-stale value back to disk. Split the load into an early `_load_language_pair_from_disk()` (called before UI construction) and a later spellcheck-only pass.
- **Bare `except: pass` in `load_language_settings`** silently swallowed any error (including spellcheck failures) and reverted the in-memory language pair to hard-coded defaults. Now uses explicit try blocks with diagnostic logging.

---

## v1.9.379 - April 14, 2026

### Added
- **Native text selection and copy in Superlookup tables.** Source and target cells in both the TMs and Termbases tables now use read-only `QTextEdit` widgets with a proper `QSyntaxHighlighter` (same pattern as the main grid). You can click-and-drag to select individual words, double-click to select a word, right-click → Copy, and use Ctrl+C – all while keeping the yellow search-term highlighting intact. This replaces the earlier QLabel-based approach from v1.9.378, which did not support text selection inside cells.
- **Settings (gear) button in the Supervertaler Assistant title bar.** Opens the main Workbench Settings tab directly from the floating assistant – brings the Workbench window to the foreground.
- **Right-click "Open in TM browser"** on TM results – opens the TM Manager's Concordance tab pre-filled with the source text and auto-runs the search, jumping straight to the matching segment for editing. The Workbench window is brought to the foreground.
- **Multi-monitor positioning fix** – the floating assistant now remembers which monitor it was on and reopens there, and clamps its size to the available screen if the saved geometry would push it off-screen.

### Changed
- **Default global hotkey for launching the assistant** changed from `Ctrl+Alt+A` to `Ctrl+Shift+A` (clashes less with memoQ and other CAT tools). User custom bindings are preserved.
- **Supervertaler Assistant is no longer always-on-top** – it now behaves like a normal window; clicking another program brings that program forward. Bring the assistant back via `Ctrl+Shift+A`, `Ctrl+Q`, or its taskbar icon.
- **Source and target cells are now bold** in the Termbases and TMs tables for better readability.
- **Removed the "Match %" column** from the TM results table – every concordance match was always 100%, making the column pointless. Table is now 3 columns (Source, Target, TM).
- **Removed "Type: concordance"** from the vertical TM results view – redundant noise.
- **Row heights in results tables** now correctly measure the actual rendered widget content – short entries get compact rows, long entries expand only as much as they need.
- **"Edit in Termbase" right-click action** on Termbase results now brings the Workbench window to the foreground before navigating to the termbase entry (previously just navigated in the background). Also renamed from "Edit in Glossary".
- **Renamed Superlookup "Settings" tab** to "Superlookup Settings" to distinguish it from the general Workbench settings.

---

## v1.9.378 - April 14, 2026

### Added
- **Right-click "Copy source → target"** action on TM results – copies both source and target tab-separated, convenient for pasting into spreadsheets or glossaries. Alongside existing Copy Source / Copy Target items.
- **Context-aware status bar** at the bottom right of the floating assistant – shows a short tip describing what the active tab (Chat / QuickTrans / Superlookup) does.
- **Superlookup search syncs to QuickTrans** – when a search runs in Superlookup, the search text is automatically copied to the QuickTrans input field so you can switch tabs and run the same query with MT/AI engines without retyping.

### Changed
- **Removed the embedded Superlookup tab from the main Workbench Tools section.** Superlookup is now only accessible via the floating Supervertaler Assistant (third tab) and the Ctrl+Alt+L global hotkey. The `SuperlookupTab` instance is still created in the background to register the global hotkey, but it no longer occupies a slot in the Tools sidebar.
- **Removed the "Superlookup" header and info text** inside the floating assistant to save vertical space. The active tab label and the context-aware status bar at the bottom identify the feature.
- **Removed Machine Translation tab from Superlookup** – MT is already handled by QuickTrans; having it in both places was redundant. The Web Resources tab is kept.
- **Removed Type column from TM results horizontal table** – every Superlookup result is a concordance match by definition; the column just repeated "concordance" for every row.
- **Renamed Glossaries tab to Termbases** in Superlookup results for consistency with the rest of the product.

### Fixed
- **Superlookup table rendering (first pass)** – source and target columns in TMs and Termbases tabs switched to lightweight QLabel cell widgets with `WA_TransparentForMouseEvents`, fixing the UI freeze that occurred when switching tabs or clicking results. Search term highlighting (yellow) is preserved. *Note: this was superseded in v1.9.379 by a proper `QTextEdit` + `QSyntaxHighlighter` approach that also supports individual-word text selection.*
- **Superlookup Ctrl+Alt+L hotkey** – now correctly opens the floating Supervertaler Assistant with the Superlookup tab active instead of bringing the main Workbench window to the foreground.
- **Screen artifact on main window** – the hidden SuperlookupTab (kept for global hotkey registration) was rendering as a child widget on top of the main window; now explicitly hidden.

---

## v1.9.377 - April 13, 2026

### Added
- **Superlookup tab in the Supervertaler Assistant.** Superlookup is now available as a third tab (Chat | QuickTrans | Superlookup) in the floating Supervertaler Assistant window. Search your TMs and termbases from anywhere – inside Trados, from any application, or from within Workbench itself. The global hotkey Ctrl+Alt+L now opens Superlookup directly in the floating assistant instead of bringing the main Workbench window to the foreground.
- **Maximise button on the Supervertaler Assistant.** A new maximise/restore button (☐) sits between the minimise and close buttons in the assistant's title bar. Click it to fill the screen – useful when browsing Superlookup results – and click again to restore the previous size.

### Changed
- **Superlookup action in the assistant's action tree** now switches to the Superlookup tab within the assistant instead of hiding the assistant and opening the main window.

---

## v1.9.376 - April 13, 2026

### Changed
- **TSV export uses standard headers.** Export now writes "Source" / "Target" as column headers instead of "Source Term" / "Target Term", matching the Trados plugin format for cross-product compatibility.
- **TSV import confirmation dialog improved.** The confirmation dialog now shows the filename, row count, termbase name, and language pair before importing – making it easier to catch mistakes.

---

## v1.9.375 - April 13, 2026

### Added
- **QuickTrans tab** – QuickTrans is now embedded as a second tab inside the Supervertaler Assistant (Chat | QuickTrans). Results appear in a compact GT4T-style list with real provider logos. Select text anywhere, press **Ctrl+Alt+Q**, and translations from all enabled providers appear instantly
- **QuickTrans input field** – the QuickTrans tab has its own input area with a Translate button; type or paste text and press Enter to translate without needing the global hotkey
- **Provider icons** – real logos for Google Translate, DeepL, Microsoft, Amazon, ModernMT, MyMemory, OpenAI, Claude, Gemini, Mistral, Ollama, and OpenRouter replace the old generated letter icons
- **QuickTrans keyboard navigation** – press 1–9 to instantly select a translation; use arrow keys + Enter to navigate; click any result to copy

### Changed
- **QuickTrans AI prompts** – LLM providers (Claude, OpenAI, Gemini, etc.) now use strict translation-only prompts, eliminating explanatory text, alternatives, and commentary from results
- **Global QuickTrans shortcut** – changed from Ctrl+Alt+M to **Ctrl+Alt+Q**
- **Chat tab auto-selected** – the Chat tab is always selected when the assistant opens; QuickTrans tab is only shown when triggered via its specific shortcut
- **Assistant always opens** – pressing the global hotkey without text selected now opens the assistant with an empty input instead of doing nothing

### Removed
- **AI tab header** – removed the "✨ AI" header and description text from the AI tab to save vertical space

---

## v1.9.374 - April 12, 2026

### Added
- **Supervertaler Assistant** – new floating AI assistant window (Ctrl+Q inside Supervertaler, Ctrl+Alt+A from any application). Features a chat panel on the left and an expandable action menu on the right with Workbench Tools, Prompts, Special Characters, Personal Snippets, and Text Conversions
- **System-wide assistant** – select text in any application, press Ctrl+Alt+A, and use AI tools, text conversions, or snippets. Direct actions (snippets, text conversions) instantly paste the result back over your selection
- **Chat backend architecture** – extracted shared ChatBackend class so all chat views (grid panel, AI tab, floating assistant) share the same conversation state and stay in sync via Qt signals
- **AI tab chat view** – restored the AI Assistant as a third sub-tab under the AI tab, providing a full-width chat experience without needing a project open
- **Model selector** – clickable model indicator next to the Send button opens a hierarchical menu to switch LLM provider/model on the fly (Anthropic, OpenAI, Google, Mistral, Ollama, OpenRouter)
- **Token usage and cost tracking** – assistant responses now show model name, input/output token counts, estimated cost, and response time below each message
- **Context chips** – toggleable chips (Document, TMs, Termbases, Files) above the chat input replace the old vertical context panel. Right-click any chip for details: browse TMs, termbases, or attach files
- **Image paste** – Ctrl+V pastes clipboard images into the chat with thumbnail preview; images are sent to vision-capable models (GPT-4o, Claude, Gemini)
- **File attachment** – right-click the Files chip to attach documents (.txt, .md, .docx, .pdf, .tmx, .sdlxliff, etc.) to the AI context
- **Text Conversions menu** – uppercase, lowercase, title case, sentence case, curly quotes, brackets, soft hyphen removal, double-to-single quotes, HTML bold wrapping
- **Special Characters menu** – quick-insert symbols, arrows, primes, dashes, currency, legal, maths, and bullet characters
- **LLM pricing module** – token cost estimation for all supported providers

### Changed
- **Chat visual refresh** – lighter user bubble colours (#D6EBFF), avatar labels ("You" / "Supervertaler Assistant"), timestamps on all messages, model/cost info line on assistant responses – ported from Supervertaler for Trados plugin styling
- **Thinking indicator** – replaced emoji bubble with animated "Thinking..." text that cycles dots, matching Trados plugin behaviour
- **Markdown rendering** – added heading support (H1/H2/H3), horizontal rules, and numbered lists to chat message rendering
- **QuickLauncher replaced** – the old QMenu-based QuickLauncher popup is replaced by the floating Supervertaler Assistant window with persistent state, keyboard navigation, and expandable categories
- **Global hotkey** – changed from Ctrl+Alt+Q (often claimed by other apps) to Ctrl+Alt+A
- **Window focus** – floating assistant uses AttachThreadInput for reliable keyboard focus capture, even from applications like Trados Studio that aggressively reclaim focus
- **Floating assistant geometry** – window size, position, and splitter proportions are remembered across sessions

### Removed
- **Available Context panel** – replaced by the compact context chips row; no longer consumes vertical space
- **Verbose debug logging** – removed prompt library tree debug output that was flooding the terminal and session log

---

## v1.9.373 - April 6, 2026

### Removed
- **Tracked Changes** – removed the unimplemented placeholder tab from the Tools panel and its menu entry. This feature will not be built

---

## v1.9.372 - April 3, 2026

### Added
- **Compact tag view mode** – new "Compact" button in the WYSIWYG/Tags toolbar shortens verbose XML/mxliff tags (e.g. `<bmk id="0" name="_Toc219208699" transform="open">`) to numbered placeholders like `{1}`, `{/1}` in both source and target columns. Cycle with Ctrl+T: Tags → Compact → WYSIWYG
- **Compact tag insertion** – Ctrl+, now inserts compact placeholders (not full tags) when in Compact view mode; full tags are transparently restored on save
- **Collapsible Available Context** – the "Available Context" section in the AI Assistant tab can now be collapsed/expanded to give more space to the chat area

### Changed
- **AI Assistant chat layout** – significantly reduced vertical spacing, font sizes, padding, and bubble dimensions for a more compact chat experience
- **Auto-resize rows on mode switch** – row heights automatically adjust when switching between Tags, Compact, and WYSIWYG view modes

---

## v1.9.371 - April 2, 2026

### Added
- **OpenRouter provider** – access 200+ models from OpenAI, Anthropic, Google, Mistral, and others with a single API key via openrouter.ai. Includes a curated dropdown of 8 recommended models (Claude Sonnet/Opus, GPT-5.4/Mini, Gemini 3.1 Pro/Flash, Mistral Small, Qwen 3.6 Plus Free) plus an editable model field for typing any OpenRouter model ID

---

## v1.9.370 - March 31, 2026

### Changed
- **QuickLauncher shortcut** – in-app QuickLauncher changed from Alt+K to **Ctrl+Q**; global QuickLauncher changed from Ctrl+Alt+K to **Ctrl+Alt+Q**

### Fixed
- **Default prompt QuickLauncher flags** – QuickLauncher default prompts now correctly have `quicklauncher: true` and `quicklauncher_grid: true` written to their YAML frontmatter, so "Show in QuickLauncher (in-app)" and "Show in QuickLauncher (global)" are pre-selected
- **Default prompt read-only flag** – all default prompts now include `read_only: true` in their YAML frontmatter
- **Category-based QuickLauncher detection** – prompts with subcategories like `QuickLauncher/Default` are now correctly detected as QuickLauncher prompts (was previously checking for exact match only)
- **Existing default prompts updated on startup** – default prompts created by v1.9.369 are automatically rewritten on next launch to include the new flags

---

## v1.9.369 - March 31, 2026

### Added
- **Default prompt system** – six default prompts (Default Translation, Default Proofreading, UK to US English Localisation, Assess Translation, Define, Explain Selection) are now auto-created on startup if missing; default prompts are greyed out in the tree, read-only in the editor, and cannot be deleted; a new `restore_default_prompts()` method resets all defaults to their original content; the `default: true` YAML field is shared with Supervertaler for Trados (also accepts legacy `built_in: true`)

---

## v1.9.368 - March 31, 2026

### Removed
- **Tags feature removed** – the `tags` YAML field and all related search/create/update logic have been removed from the Prompt Library; existing `tags` frontmatter will be silently ignored

---

## v1.9.367 - March 31, 2026

### Removed
- **Favorites feature removed** – the Favourites tree section, star indicators, context menu toggle, and AI action handlers have been removed from the Prompt Library; prompts with existing `favorite` frontmatter will have it silently ignored

---

## v1.9.366 - March 27, 2026

### Added
- **Mistral AI support** – Mistral Large, Mistral Small, and Mistral Nemo are now available as a first-class provider alongside OpenAI, Claude, Gemini, and Ollama

---

## v1.9.365 - March 26, 2026

### Fixed
- **Duplicate YAML frontmatter on save** – saving a prompt in the Prompt Manager no longer duplicates the YAML frontmatter block; the save path now strips any accidental frontmatter from the content before writing

---

## v1.9.364 - March 26, 2026

### Changed
- **Prompts now saved as `.md`** – prompt files use standard Markdown (`.md`) extension instead of `.svprompt`; existing `.svprompt` files are auto-migrated to `.md` on startup; a new `type: prompt` YAML field identifies prompt files
- **QuickMenu renamed to QuickLauncher** – all YAML fields (`quickmenu` → `quicklauncher`, `quickmenu_grid` → `quicklauncher_grid`, `quickmenu_label` → `quicklauncher_label`), Python methods, variables, shortcut IDs, and UI strings updated; backward compat preserved for old field names
- **Website badge updated** – "Make Trados fun again!" with yellow circle badge
- **Getting Started screencast** added to Trados website page
- **RWS App Store link** added to Trados website page

---

## v1.9.363 - March 25, 2026

### Changed
- **Feature renamed: AutoPrompt** – "Analyze Project & Generate Prompts" button and all references renamed to **AutoPrompt**

---

## v1.9.362 - March 24, 2026

### Added
- **Unified prompt library schema** – prompts now use a consistent YAML frontmatter format (`category`, `app`) shared between Supervertaler Workbench and Supervertaler for Trados
- **App-specific prompt filtering** – prompts tagged `app: "trados"` are hidden in Workbench; prompts tagged `app: "workbench"` are hidden in Trados; `app: "both"` (default) shows everywhere
- **App dropdown in prompt editor** – new "App" field lets you choose whether a prompt is for Both, Workbench only, or Trados only

### Changed
- **User data folder restructured** – Workbench files now live under `workbench/` subfolder; shared resources (prompt_library, resources) remain at root; auto-migrated on first startup
- **Prompt YAML keys standardised** – `domain` → `category`, `sv_quickmenu`/`quick_run` → `quickmenu`; legacy keys are still accepted for backward compatibility
- **Prompt library cleaned up** – removed empty placeholder folders, fixed YAML frontmatter, standardised variable names
- **Default folder seeding updated** – startup now only creates `Translate/`, `Proofread/`, `QuickLauncher/` (removed legacy empty folders)

---

## v1.9.361 - March 24, 2026

### Changed
- **User data folder restructured** – all Workbench-specific files now live under `~/Supervertaler/workbench/` (with `settings/`, `dictionaries/`, `voice_scripts/`, `ai_assistant/`, `superbrowser_profiles/`, `web_cache/`, `projects/` subfolders); shared resources (`prompt_library/`, `resources/`) remain at the root; auto-migrated on first run
- **Website updates** – trial period updated to 14 days, live Lemon Squeezy checkout links, reduced pricing note added to Trados pricing section

---

## v1.9.360 - March 23, 2026

### New Features
- **Superlookup in QuickLauncher** – the system-wide QuickLauncher menu (Ctrl+Alt+K) now includes a Superlookup link that searches your TMs and termbases for the selected text

### Fixed
- **QuickLauncher window activation** – launching Superlookup or other actions from the QuickLauncher now correctly brings the Supervertaler Workbench window to the foreground instead of the terminal; uses cross-platform approach (Win32 API on Windows, Qt raise/activate on macOS and Linux)

---

## v1.9.359 - March 23, 2026

### New Features
- **Delete prompt folders** – right-click any folder in the prompt library and select "Delete Folder" to remove it and all prompts inside (with confirmation dialog)

### Changed
- **Dev mode uses standard user data path** – developer mode now uses `~/Supervertaler/` (same as regular users) instead of a separate `user_data_private/` folder, enabling testing of the shared data folder experience with Supervertaler for Trados

---

## v1.9.358 - March 19, 2026

### New Features
- **Opt-in anonymous usage statistics** – on first launch, a dialog asks if you'd like to share anonymous usage data (app version, OS, Python version, locale) to help improve Supervertaler. A single lightweight ping is sent once per session. No personal data, translation content, or termbase info is ever collected. You can change your preference at any time in Settings > General > Privacy. See [Usage Statistics](https://help.supervertaler.com/settings/usage-statistics) for full details.

---

## v1.9.357 - March 18, 2026

### Changed
- **Style Guides folder removed from default prompt library** – the Style Guides folder is no longer created on first run; existing installs are unaffected

---

## v1.9.356 - March 17, 2026

### Improvements

- **QuickLauncher folder detection** – prompts placed in a folder named `QuickLauncher` inside the prompt library are now automatically treated as QuickLauncher items without needing `sv_quickmenu: true` in their YAML frontmatter, matching the behaviour of Supervertaler for Trados
- **`category: QuickLauncher` support** – setting `category: QuickLauncher` in a `.svprompt` file's YAML frontmatter now marks it as a QuickLauncher prompt, consistent with the Trados plugin
- **`quicklauncher_label` YAML field** – the optional short label for the QuickLauncher menu can now be set with `quicklauncher_label:` (preferred) or the legacy `quickmenu_label:` (still supported)

---

## v1.9.355 - March 14, 2026

### New Features

- **Optional Status column position** – New checkbox in View Settings > Grid Display Options: "Show Status column before Target column". When enabled, the Status column appears between Source and Target instead of after Target. Uses Qt's visual column reorder so all existing code continues to work unchanged.

### Performance

- **Save View Settings is now instant** – Previously, clicking "Save View Settings" could freeze the UI for 30+ seconds. Root cause: `apply_theme()` was called on every save regardless of whether the UI scale changed, forcing Qt to restyle every widget in the application. Now only runs when the scale actually changes. Additionally, five expensive grid loops (invisible char color, focus border, tag colors, alternating row colors, source column refresh) now use change-detection guards and only run when their respective setting was modified.

---

## v1.9.354 - March 13, 2026

### New Features

- **Auto-confirm 100% TM matches ([#138](https://github.com/Supervertaler/Supervertaler-Workbench/issues/138))** – New checkbox in the batch pre-translation dialog: "Auto-confirm 100% TM matches". When enabled, segments with exact TM matches are automatically set to Confirmed status instead of TM 100%, so you can skip manual review of trusted TM content. The option is only available when TM is selected as the translation provider. The completion log and dialog show how many segments were auto-confirmed.

---

## v1.9.353 - March 13, 2026

### Improvements

- **AI Assistant moved to the right panel** – The Supervertaler Assistant is now available as a tab in the right-side panel (next to Match Panel), so you can chat with the AI while working in the translation grid. Previously it was tucked away under the AI tab, requiring a full tab switch. The Prompt Manager and Variables tabs remain under the AI tab.

---

## v1.9.352 - March 7, 2026

### Bug Fixes

- **Fixed Ollama timeout for AI Assistant and large prompts ([#177](https://github.com/michaelbeijer/Supervertaler/issues/177))** – The AI Assistant's "Analyze Project & Generate Prompts" feature timed out when using Ollama because: (1) model size detection used hardcoded strings that missed models like `translategemma:12b`, giving them the minimum 180-second timeout instead of 300s; (2) the AI Assistant sends a ~14K character prompt expecting an ~18K character response, which needs far more time than a simple segment translation. Fixed with regex-based model size detection, automatic timeout boost for large prompts (≥600s), and streaming support for Ollama – tokens now arrive incrementally instead of waiting for the complete response, eliminating timeout issues for large requests entirely.

---

## v1.9.351 - March 6, 2026

### Bug Fixes

- **Fixed HTTP proxy not applied to most LLM calls** – The HTTP proxy setting (Settings > HTTP Proxy) was only wired through the centralized `create_llm_client()` helper, leaving 13 other direct `LLMClient` instantiations (batch pre-translation, proofreading, real-time LLM matches, concordance search, QuickLauncher, Quick prompt dialog, Superbench, and AI Assistant) bypassing the proxy entirely. All LLM call sites now correctly pass `http_proxy`.

### Improvements

- **Auto-generated prompts saved to dedicated folder** – "Analyze Project & Generate Prompts" now saves generated prompts to a new "Supervertaler Assistant Prompts" folder instead of the generic "Project Prompts" folder.

---

## v1.9.350 - March 6, 2026

### Bug Fixes

- **Fixed "Ollama is not running" false alarm (issue #177)** – When users configured their Ollama endpoint with a trailing `/api` or `/v1` suffix (e.g. `http://localhost:11434/api`), the status check constructed double-path URLs like `/api/api/tags`, causing a 404. All Ollama endpoint usages now sanitize the URL by stripping trailing `/`, `/api`, and `/v1` suffixes. Affects status checks, translation calls, model downloads, connection tests, and the keep-warm ping.
- **Fixed keep-warm ping ignoring custom Ollama endpoint** – The Ollama keep-warm ping was hardcoded to `http://localhost:11434` instead of using the user's configured endpoint.
- **Improved Ollama error messages** – When Ollama is reachable but the API path returns an error, the message now explains the likely cause (misconfigured endpoint URL) instead of the misleading "Ollama is not running".

---

## v1.9.349 - March 6, 2026

### Bug Fixes

- **Fixed AI prompt generation stripping its own content** – The `_clean_translation_response()` post-processor aggressively removed lines containing translation-related keywords like "professional translation", "Your task is to", etc. When generating a *prompt about translation*, this stripped ~95% of the content, leaving only 2-3 lines. Fixed by centralizing the cleaning in `translate()` with a new `skip_cleaning` parameter, bypassed for prompt generation requests.

### Improvements

- **Multi-file support for standalone SDLXLIFF import** – Importing 2+ standalone `.sdlxliff` files now activates the full multi-file system: file boundary banners in the grid, file filter dropdown, and Manage Views dialog. Previously only SDLPPX packages and folder imports supported this. Each segment now tracks its `file_id` and `file_name`.
- **File-aware AI prompt generation** – "Analyze Project & Generate Prompts" now detects multi-file projects and runs per-file domain/tone analysis using `DocumentAnalyzer`. The generated prompt includes a multi-file guidance section listing each file with its detected domain, tone, formality, and word count, instructing the AI to adapt register and terminology when translating different files.
- **File-structured project context** – For multi-file projects, the document content sent to the AI for prompt generation is now organized by file with headers, instead of a flat segment list.
- **Improved "Current Document" display** – The Supervertaler Assistant context panel now shows file count for multi-file projects and segment count as fallback, instead of the misleading "No document" label.

---

## v1.9.348 - March 5, 2026

### Bug Fixes

- **Fixed AI prompt generation failure** – The "Analyze project and generate prompts" feature was failing because the LLM had to produce thousands of words of prompt content inside a JSON string, requiring perfect escaping of all quotes and newlines. Switched from fragile ACTION JSON format to delimiter-based output (`===PROMPT_START===` / `===PROMPT_END===`), with the prompt created programmatically from the extracted content. Includes ACTION block fallback for robustness.
- **Fixed chat line break rendering** – Phase status lines in the AI assistant chat now display on separate lines instead of running together.

---

## v1.9.347 - March 5, 2026

### Improvements

- **Expert-level AI prompt generation** – The "Analyze project and generate prompts" feature now produces dramatically better translation prompts. The system pre-analyzes documents with `DocumentAnalyzer` to detect domain (patent, legal, medical, technical, financial, marketing), then selects a domain-specific template with tailored rules, mandatory sections, and constraint language. Generated prompts now include: translation mandate (NON-NEGOTIABLE), anti-hallucination/anti-truncation controls, terminology consistency hierarchy, preflight self-check, post-translation integrity assertion, project context summaries, and strong constraint language (LOCKED, ABSOLUTE). Termbase extraction upgraded from 20-50 terms to all terms (up to 500). TM reference pairs upgraded from 10 truncated entries to 30 full-text style anchors.

---

## v1.9.346 - March 4, 2026

### Improvements

- **Removed per-term priority column** – Completed the glossary simplification started in v1.9.343 by fully removing the per-term priority system. The Priority column is no longer exported to TSV, displayed in the termbase editor, shown in tooltips, or stored in match metadata. Priority is now determined solely at the glossary level (Project vs Background). The database column is retained for backward compatibility but is no longer read or written.

---

## v1.9.345 - March 4, 2026

### Improvements

- **Horizontal tabs for Resources** – The Resources tab (TMs, Glossaries, Non-Translatables, Image Context) now uses horizontal tabs instead of a vertical sidebar, saving screen space for only 4 items.

---

## v1.9.344 - March 4, 2026

### Bug Fixes

- **Fixed Alt+Up/Down quick-add shortcuts** – The quick-add to Project/Background glossary shortcuts (Alt+Up, Alt+Down, Ctrl+Shift+1, Ctrl+Shift+2) were broken because background glossaries with `priority=NULL` were indistinguishable from non-activated glossaries. Now uses explicit activation check before reading priority.

---

## v1.9.343 - March 4, 2026

### Improvements

- **Simplified glossary priority system** – Replaced the multi-level numeric priority system (Priority #1, #2, #3...) with a simple on/off "Project glossary" checkbox. One glossary can be designated as the Project glossary (pink, highest priority) per project; all other active glossaries are Background (blue). The Priority column in the glossary settings pane is now a checkbox labeled "Project", and only one glossary can be checked at a time (exclusive).
- **Removed per-term priority** – The Priority column has been removed from the glossary terms panel (right side). Individual term entries no longer carry priority levels – priority is now determined solely at the glossary level.
- **Fixed glossary deduplication in fast path** – When the same term existed in both a Project and Background glossary, the in-memory fast-path search was not deduplicating, causing the background (blue) match to sometimes display instead of the project (pink) match. Both the fast path and fallback path now correctly deduplicate, keeping the Project glossary match.
- **Instant cache update on quick-add** – When adding a term to the Project glossary via Ctrl+Shift+1 or Alt+Up that already exists in a Background glossary, the old entry is replaced immediately in the cache so the pink match appears without needing F5.
- **Updated quick-add labels** – Context menu items renamed from "Quick Add to Priority 1/2 Glossary" to "Quick Add to Project/Background Glossary" for clarity.

---

## v1.9.342 - March 3, 2026

### New Features

- **Okapi Framework sidecar integration** – Supervertaler now includes a built-in Okapi Framework sidecar, a lightweight Java microservice that runs in the background and provides industry-standard file filters for document import and export. The Okapi Framework is a battle-tested open-source localization toolkit that shares its origins with the SDL/Trados ecosystem and is available as a plugin for OmegaT, making it the closest thing the industry has to a universal standard for file filter technology. Currently supports DOCX with full round-trip fidelity – the exported translation is an exact replica of the original document in terms of formatting, layout, colors, fonts, and styles.
- **Semantic inline formatting tags** – The Okapi import now produces human-readable formatting tags (`<b>` for bold, `<i>` for italic, `<u>` for underline, `<s>` for strikethrough, `<sup>`/`<sub>`, and `<cf color="...">` for colored text) instead of opaque internal codes. Translators can see and preserve formatting at a glance.
- **Faithful format preservation on export** – The Okapi merge engine reconstructs original inline codes from the semantic display tags in your translations, preserving bold, italic, colors, fonts, and all other formatting in the exported document. No manual formatting cleanup needed.

### Improvements

- **Import engine selection dialog** – When importing a DOCX file, a dialog lets you choose between the standard Python-based engine and the Okapi Framework engine. The dialog text now accurately describes the capabilities of each engine.
- **Okapi formatting detection via Code.getType()** – The Java sidecar now reads formatting properties from Okapi's code type descriptors (e.g., `x-bold;fonts:Arial;`) in addition to raw OOXML data, enabling correct detection of style-based formatting in all DOCX documents.
- **Improved subDocument filtering** – Okapi extraction now correctly filters header/footer content while preserving all body text, fixing an issue where segments from the main document body were incorrectly skipped.

---

## v1.9.341 - March 1, 2026

### New Features

- **Proofreading cancel button** – The proofreading operation (Edit > Bulk Operations > Proofread Translation) now runs in a background thread, keeping the UI fully responsive. A cancel button lets you stop proofreading at any time instead of waiting for all batches to complete.
- **Prompt Library integration for proofreading** – The proofreading dialog now has a prompt dropdown that loads prompts from the `Bulk Operations/Proofreading` folder in the Prompt Library. Create custom proofreading prompts in this folder and they appear in the dropdown automatically.
- **Read-only prompts** – Prompts can now be marked as read-only via a checkbox in the Prompt Editor. Read-only prompts cannot be edited or overwritten, protecting built-in and curated prompts from accidental changes.
- **Default Bulk Operations folder** – A new `Bulk Operations/Proofreading` folder is created by default in the Prompt Library, containing a built-in (read-only) default proofreading prompt.

### Improvements

- **QuickLauncher checkboxes take effect immediately** – Toggling "Show in QuickLauncher (in-app)" or "Show in QuickLauncher (global)" and saving now updates the QuickLauncher menus immediately, without requiring a restart.
- **Default folders for new users** – New installations now get a useful starting set of Prompt Library folders: Domain Expertise, Project Prompts, Proofreading, Style Guides, Translation Help, and Bulk Operations/Proofreading.
- **Prompt Library tree header renamed** – The folder tree header now reads "Prompt Library" to distinguish it from the "Prompt Manager" tab name.

### Bug Fixes

- **Fixed custom_openai provider for proofreading** – The proofreading operation previously crashed with a NameError when using a custom OpenAI provider due to an undefined `settings` variable. Now properly resolved.

---

## v1.9.340 - March 1, 2026

### New Features

- **Supervertaler Assistant** – Access the AI Assistant directly from any QuickLauncher menu. Selected text is inserted into the chat input for conversational follow-up. Press Escape to return to the Grid, or back to the external app you launched from via Ctrl+Alt+K.
- **Escape-to-return from Assistant** – Pressing Escape in the Supervertaler Assistant returns you to where you came from: back to the Grid when launched in-app (Alt+K or right-click), or back to the external application (e.g. Notepad, Word, browser) when launched via the Ctrl+Alt+K global hotkey.
- **QuickLauncher menu heading** – All QuickLauncher menus now display a bold "Supervertaler QuickLauncher" heading at the top, making it immediately clear what the menu is.

### UI Renames

- **Main tab: "Prompt Manager" → "AI"** – The main tab is now simply called "✨ AI", which is cleaner and more inviting for translators exploring AI features.
- **Sub-tab: "Prompt Library" → "Prompt Manager"** – The prompt management sub-tab has been renamed to better reflect its full capabilities (creating, organizing, and running prompts).
- **Sub-tab: "Placeholders" → "Variables"** – Renamed to avoid confusion with the CAT tool concept of placeholders (inline formatting tags). All internal labels, column headers, and tips text updated accordingly.

### Documentation

- **New QuickLauncher help page** – Added a comprehensive help page for the QuickLauncher feature covering all access methods, QuickTrans, Supervertaler Assistant, custom prompts, context variables, and hotkey customization.
- **Help docs updated for new naming** – Updated all references across the help system: Prompt Library → Prompt Manager, AI Assistant → Supervertaler Assistant, Prompt Manager tab → AI tab (27 renames across 6 files).

---

## v1.9.339 - February 28, 2026

### New Features

- **Global QuickLauncher hotkey (Ctrl+Alt+K)** – Launch the QuickLauncher from any application on your computer. Select text in any app, press Ctrl+Alt+K, and a floating menu appears with QuickTrans, the Supervertaler Assistant, and all your QuickLauncher prompts. Choose "Run (show response)" to view the result, or "Run and paste into app" to insert the LLM response directly into the external application.
- **QuickTrans consolidated into QuickLauncher** – QuickTrans is now the first item inside the QuickLauncher submenu (both in the right-click context menu and the Alt+K hotkey menu), streamlining the menu structure.
- **Customizable global hotkeys** – All three global hotkeys (Superlookup, QuickTrans, QuickLauncher) can now be customized in Settings → Keyboard Shortcuts under the "Global" category.
- **Renamed QuickMenu → QuickLauncher** – The QuickMenu feature has been renamed to QuickLauncher across the entire application for better clarity.

### Improvements

- **QuickLauncher response dialog now renders Markdown** – The "Run (show response)" dialog now displays formatted Markdown (headings, bold, italic, lists, code blocks) instead of raw plain text.
- **Improved clipboard timing** for the global QuickLauncher hotkey, ensuring reliable text capture in slower applications like emEditor.

---

## v1.9.337 - February 28, 2026

### Improvements

- **Lock/Unlock segments from right-click menu** – You can now right-click selected segments in the grid to lock or unlock them. Segments imported as locked from Trados (`translate="no"`) can now be unlocked directly in Supervertaler without going back to Trados. The context menu adapts to show the appropriate action based on the current lock state of the selection.
- **Website: renamed "Docs" to "Help"** in the navigation bar for clarity.

---

## v1.9.336 - February 27, 2026

### Major Improvements

- **SDLXLIFF confirmation status now imported correctly** – Segments confirmed in Trados Studio (`conf="Translated"`) are now recognized as Confirmed in Supervertaler instead of being silently ignored. Approved and rejected statuses are also preserved on import.
- **Renamed "Translated" status to "Draft"** – The status for segments that have content but are not yet confirmed is now called "Draft", which better reflects its meaning and avoids confusion with Trados Studio's "Translated" (which means confirmed). Existing projects are automatically migrated.
- **Removed "TR Confirmed" status** – This status had no equivalent in Trados or memoQ and has been merged into "Confirmed", giving a clean 1:1 mapping: Confirmed = Trados "Translated" / memoQ "Confirmed", Proofread = Trados "Translation Approved" / memoQ "Reviewer 1 confirmed", Approved = Trados "Sign-off Approved" / memoQ "Reviewer 2 confirmed".
- **Fixed SDLXLIFF export losing approved status** – Segments with "Approved" status were incorrectly exported as Trados `conf="Translated"` instead of `conf="ApprovedTranslation"`. Both export paths (text-based and XML-based) now preserve the full status hierarchy.

### Documentation

- **New help page: Segment Statuses** – Added a comprehensive reference page to the online help explaining match origins, workflow statuses, and how they map to Trados Studio and memoQ.

---

## v1.9.335 - February 27, 2026

### Improvements

- **Locked segments now show a 🔒 lock icon** – Locked segments in the grid now display a lock icon in the Status column instead of the regular status icon, making them immediately identifiable at a glance.
- **Locked segment background color more visible** – The background color for locked rows has been changed from a subtle grey (`#E0DDD8`) to a warmer peach (`#F0D9C8`) that stands out clearly against the normal alternating row colors.

---

## v1.9.334 - February 27, 2026

### New Feature: SDLXLIFF Comment Round-Trip

Supervertaler now fully supports **Trados Studio comments** in SDLXLIFF files – both import and export.

- **Comment import** – Existing Trados Studio comments (`<cmt-def>` / `<mrk mtype="x-sdl-comment">`) are parsed during SDLXLIFF import and displayed in the Notes panel, prefixed with the original author name (e.g. `[mbeijer] comment text`).
- **Comment export** – Any text entered in the Notes field is written back as a standard Trados Studio comment when exporting to SDLXLIFF or SDLRPX. Comments appear correctly in Trados Studio's Comments pane with proper author attribution, timestamp, and severity metadata.
- **Full round-trip** – Import → edit/add comments → export → re-import preserves all comments. Double round-trips also work correctly.

### New Feature: User Identity Settings

A new **👤 User Identity** tab in Settings lets you configure the translator name that appears in exported files.

- **Translator name** – Set your preferred name or alias. This is used as the author in SDLXLIFF comments, the `PackageCreatedBy` field in Trados return packages (SDLRPX), and the default creator ID when creating new TMX files.
- **Privacy-friendly fallback** – If left empty, falls back to your system username. The app name is never stamped into exported files.

### Improvements

- **SDLXLIFF segment ID robustness** – Segment IDs for SDLXLIFF round-trip export are now stored in a dedicated internal field (`sdl_segment_id`) instead of being embedded in user-editable notes. This prevents accidental breakage if users edit or clear the notes field. Old projects with metadata-in-notes are still supported via a fallback parser.

---

## v1.9.333 - February 27, 2026

### New Feature: Okapi Framework Integration (Experimental)

Supervertaler now ships with an **Okapi Framework sidecar** – an industry-standard, open-source file filter engine used by professional translation tools worldwide. This lays the foundation for significantly improved document import fidelity and support for additional file formats in future releases.

- **Okapi sidecar architecture** – A lightweight Java-based REST service (`okapi-sidecar.jar`) runs locally on `127.0.0.1:8090` as a background process alongside Supervertaler. All document processing happens on the user's machine – no files are sent over the internet. The sidecar starts automatically (with a 1.5s deferred launch) and shuts down cleanly when Supervertaler exits.
- **Bundled minimal JRE** – The sidecar includes a custom 44 MB Java runtime (built with `jlink`) so users do not need to install Java separately. PyInstaller specs are updated to include the sidecar JAR + JRE in Windows and macOS builds.
- **Optional DOCX import engine** – When importing a DOCX, users are now presented with a choice dialog:
  - **Standard (recommended)** – the existing built-in python-docx handler with full formatting tag support
  - **Okapi Framework (experimental)** – industrial-strength extraction via the Okapi sidecar with SRX sentence segmentation

  The dialog only appears if the sidecar is running; otherwise the standard engine is used silently.
- **Intelligent segment filtering** – The Okapi extraction path filters out non-body content (headers, footers, textbox names) using sub-document tracking, keeping the segment grid clean.
- **Formatting tag extraction** – The sidecar detects inline formatting from OOXML run properties and converts them to Supervertaler's `<b>`, `<i>`, `<u>`, `<sup>`, `<sub>`, `<s>` tag system.
- **SRX segmentation** – The Okapi engine uses industry-standard SRX rules for sentence segmentation, correctly handling abbreviations (Dr., Mr., etc.), decimal numbers, and other tricky boundaries.
- **Seven REST endpoints** – The sidecar exposes: `/health`, `/filters`, `/extract`, `/merge`, `/tmx/read`, `/tmx/validate`, `/segment` – designed for future integration beyond DOCX import.
- **Graceful degradation** – If the sidecar JAR or Java runtime is not present, Supervertaler functions exactly as before with no errors or warnings.

> **Note:** The Okapi import engine is marked experimental. The standard import engine remains the recommended choice for production work. The Okapi path will be refined in upcoming releases with improved formatting fidelity and broader file format support.

---

## v1.9.332 - February 26, 2026

### Critical Bug Fixes

- **Fix DOCX import silently dropping paragraphs** – The DOCX import filter used Python `id()` values to distinguish body paragraphs from table cell paragraphs. Because Python reuses memory addresses after garbage collection, body paragraphs could be falsely identified as table cells and silently skipped. The number of dropped paragraphs varied per run (non-deterministic), making this especially hard to notice. Fixed by building a stable element→Paragraph mapping once and removing the fragile `id()`-based table check entirely – body-level `<w:p>` elements can never be table paragraphs by definition.

### Improvements

- **Word count verification on DOCX import** – After importing a DOCX, Supervertaler now compares the raw XML word count against the imported segments. If the difference exceeds 5%, a warning dialog alerts the user that text may have been lost during import. This serves as a safety net against future import bugs.
- **TMX export now uses standard `<bpt>`/`<ept>` pairs** – Exported TMX files now convert inline formatting tags (`<b>`, `<i>`, `<u>`, etc.) to proper TMX 1.4 `<bpt>`/`<ept>` paired elements instead of escaping them as literal text. This ensures compatibility with Trados Studio, memoQ, and other CAT tools.

### Bug Fixes

- **Fix `<b>`/`<i>` tags not hidden by "hide outer wrapping tags"** – When a formatting tag like `<b>` wrapped the entire segment, the "hide outer wrapping tags in grid" option did not strip it. Formatting tags are now included in the strippable set alongside structural tags.
- **Fix false `<li-o>` tags on non-list paragraphs** – Paragraphs with `numId=0` in the OOXML (Word's explicit "no numbering" override) were incorrectly treated as list items. Also tightened the fallback list detection to require a proper digit+delimiter pattern (e.g. "1. " or "2) ") rather than just a leading digit.

---

## v1.9.331 - February 26, 2026

### Improvements

- **AI prompt generation: full language names** – The "Analyze project and generate prompts" feature in the AI Assistant now always uses full language names (e.g. "Dutch → English") instead of ISO codes (e.g. "nl → en") in generated prompts, for better LLM comprehension and consistency.
- **AI prompt generation: no-space currency rule** – Generated translation prompts now instruct the LLM to write currency symbols directly against the number with no space (e.g. €4,255 not € 4,255).

### Bug Fixes

- **Fix TM and glossary activation leaking between projects** – When importing a new document or creating a new project, TMs and glossaries from a previous project could still appear with "Write" checked, because the read-only flag was stored globally rather than being reset per project. Both Read and Write states are now fully cleared on every new project.
- **Fix SuperLookup TM/glossary checkboxes defaulting to checked** – The SuperLookup panel's TM and glossary lists hardcoded all checkboxes to checked on every refresh. They now start unchecked, matching the "clean slate" design.

---

## v1.9.330 - February 26, 2026

### Bug Fixes

- **Fix SDLRPX export overwriting TM metadata on pre-translated segments** – When exporting a return package, Supervertaler previously set `origin="interactive"` and stripped `percent` and `text-match` attributes on *all* segments with target text, including segments that were already translated by the TM when the package was loaded. Now only segments the user actually translated in the current session have their metadata updated; TM-matched segments retain their original `origin="tm"`, `percent="100"`, and `text-match` attributes. A new `modified` flag on `SDLSegment` tracks whether a segment was edited by the user.
- **Fix return package .sdlproj filename** – The `.sdlproj` file inside the SDLRPX return package included the target language code suffix (e.g. `_nl-NL`), which Trados Studio does not produce. The suffix is now stripped to match the Trados naming convention.

---

## v1.9.329 - February 26, 2026

### New Features

- **Term & NT insert popup (Ctrl tap)** – Pressing the Ctrl key alone (a lone tap, not a chord) now opens a memoQ-style floating popup listing all glossary matches and non-translatables for the current segment, numbered 1–9 for instant keyboard insertion. Glossary items show source → target; NT items are highlighted in yellow with a 🚫 icon. Navigate with ↑↓, insert with 1–9 or Enter, dismiss with Esc. The popup is smart: if there is exactly one NT and no glossary terms, it inserts immediately without showing the popup. If the active segment has nothing to offer, Ctrl tap is silently ignored.

### Bug Fixes

- **Fix RuntimeError flood on app close** – The lone-Ctrl event filter (installed on `QApplication`) kept firing events after the main window's C++ object was deleted during shutdown, causing a flood of `RuntimeError: wrapped C/C++ object has been deleted` errors in the terminal and making the app jump/flicker. The filter now catches `RuntimeError`, self-uninstalls from `QApplication`, and goes silent for the rest of the shutdown sequence.

---

## v1.9.328 - February 25, 2026

### Improvements

- **QuickTrans: full provider names in result badges** – QuickTrans result badges now show the full provider name ("Google Translate", "Claude", "Gemini", "OpenAI", etc.) instead of short codes (GT, CL, GEM, GPT). Badge width is now content-driven.
- **QuickTrans: updated and expanded LLM model lists** – OpenAI list now includes GPT-5, o3, o3-mini; Gemini list now includes 2.5 Flash Lite, 3.1 Pro, 3 Pro, 2.0 Flash. Lists are filtered by the new AI Models settings.
- **QuickTrans: Ollama support** – Ollama is now available as an LLM provider in QuickTrans. Enable it in Settings > QuickTrans; model name is a free-text field (no API key required).
- **New Settings tab: AI Models** – A new "AI Models" tab (directly below AI Settings in the sidebar) lets you check/uncheck individual models per provider to control which appear in QuickTrans dropdowns. Includes OpenAI, Claude, Gemini, and Ollama model lists.
- **Model discovery dialog now works** – The "New Models Found" dialog's "Add Selected Models" button previously did nothing. Selected models are now saved to AI Models settings and appear in QuickTrans dropdowns after reopening Settings.
- **Fix: Gemini grayed out in QuickTrans settings** – QuickTrans only checked for the key name `gemini`, but many users stored it as `google`. Both are now checked.
- **Fix: Google/Gemini API key naming standardized** – The canonical key name is now `gemini` everywhere (Settings UI label, `api_keys.example.txt`, internal key lookup). Keys stored as `google` are migrated automatically on load. The confusing bidirectional sync that kept both `google` and `gemini` alive simultaneously is replaced with a clean one-way migration.

---

## v1.9.327 - February 25, 2026

### Improvements

- **Bulk Operation: Copy Source to Target (No Letters)** – New bulk operation under Edit > Bulk Operations that copies source to target for segments containing no alphabetic characters (numbers, codes, punctuation, symbols). Only acts on empty targets. Segments are marked as Translated. Inspired by CafeTran's "Transfer segments with no letters to target". The underlying transform function is designed as an extension point for future locale-aware number conversion (e.g. decimal comma ↔ period).

---

## v1.9.326 - February 25, 2026

### Bug Fixes

- **Fix system prompt edits lost between sessions** – User-saved system prompts (via Settings > System Prompts) were written to `system_prompts_layer1.json` but never loaded back on startup, causing all edits to revert to defaults. The load order now correctly prioritises user-saved prompts over legacy files and hardcoded defaults.
- **Fix "System Prompts" button navigating to wrong tab** – The button in Prompt Manager > Prompt Library was opening the wrong Settings sub-tab (View Settings instead of System Prompts) due to stale hardcoded tab indices. Now searches by tab label text for robustness.
- **Fix "Reset to Default" system prompt button** – The reset handler referenced a non-existent attribute (`unified_prompt_manager`), preventing the reset from working. Now correctly uses `prompt_manager_qt`.

### Improvements

- **"Edit in Settings" button on View System Prompt dialog** – The system prompt preview dialog (Prompt Manager > View System Prompt) now includes an "Edit in Settings" button that navigates directly to Settings > System Prompts for editing.
- **Soften default system prompt language** – Replaced domain-specific "technical/medical translation as a licensed service for a commercial translation company" with generic "professional translation work" that covers all domains. The safety context is retained to prevent LLMs from refusing specialised content.

---

## v1.9.325 - February 25, 2026

### Improvements

- **Advanced Filters: match rate up to 102%** – Match Rate spinboxes now go up to 102% so you can filter for CM (101%) and PM (102%) segments from imported SDLXLIFF files.
- **Advanced Filters: match origin status checkboxes** – Added PM (102%), CM (101%), TM 100%, TM Fuzzy, Repetition, and MT checkboxes under a new "Match Origin" section in the Row Status group.
- **Bulk Operations act on filtered segments** – Confirm Segments, Change Status, Clear Translations, and Copy Source to Target now all fall back to filtered (visible) segments when no rows are manually selected. A confirmation dialog warns before acting on filtered segments.
- **Bulk Operations menu shows filter state** – The Edit > Bulk Operations menu dynamically shows the count of filtered segments (e.g. "Bulk Operations (380 filtered)") when filters are active.

---

## v1.9.324 - February 24, 2026

### Bug Fixes

- **Fix Claude empty response crash** – Added safety check in `_call_claude()` for when the Claude API returns an empty content list, preventing an `IndexError` crash. Now raises a clear error message instead.

### Improvements

- **SuperBench: tighter segment filter** – Raised minimum benchmarkable segment thresholds from 3→10 word characters and 5→15 total characters. Filters out trivial segments like "V (m/s)", "Tabel 1", etc. that waste API calls without testing real translation quality.
- **SuperBench: two-column layout** – Moved the log window from the bottom into the right-side column below Model Selection, making better use of screen space.
- **SuperBench: default to Current Project** – The "Current Project" dataset source is now pre-selected when opening SuperBench instead of "Pre-defined Datasets".
- **SuperBench: sync model dropdowns with AI Settings** – Model dropdowns now initialize to whatever models are currently selected in Settings > AI Settings > Model Selection.

---

## v1.9.323 - February 24, 2026

### Bug Fixes

- **Fix Combined Prompt Preview showing raw JSON** – The "Preview Combined" dialog in the Prompt Manager now correctly renders custom prompt content as readable text instead of displaying the raw JSON structure of `.svprompt` files. Fixed both library-loaded and externally-loaded prompts.

### Improvements

- **SuperBench: use project's custom prompt for benchmarking** – SuperBench now uses the custom prompt attached in the Prompt Manager (if any) instead of a generic built-in prompt. This means benchmark results reflect your actual translation workflow. The log indicates which prompt is being used.
- **SuperBench: custom prompt in exported reports** – The exported Excel report now includes the custom prompt name on the About sheet and a new "Prompt" sheet containing the full assembled prompt that was sent to the AI. JSON exports also include prompt information.
- **SuperBench: dynamic model labels** – The model selection checkboxes now dynamically update to show the currently selected model (e.g. "OpenAI (GPT-5)" instead of the static "OpenAI (GPT-4o)"), matching the same pattern recently added to AI Settings.
- **SuperBench: rename exported report file** – Export filename changed from `LLM_Leaderboard_...` to `Superbench_Translation_Quality_Report_...` for clarity.

### Housekeeping

- Removed unused legacy file `superbench_ui.py` (superseded by `llm_superbench_ui.py`).

---

## v1.9.322 - February 24, 2026

### Improvements

- **Resources tab: vertical sidebar navigation** – The Resources tab (TMs, Glossaries, Non-Translatables, Image Context) now uses the same vertical sidebar layout as the Tools and Settings tabs, replacing the old horizontal tab bar for a more consistent and spacious UI.

---

## v1.9.321 - February 24, 2026

### Improvements

- **SuperBench: Filter non-text segments from datasets** – The dataset creation now automatically skips segments that contain only numbers, very short fragments, or text with fewer than 3 letters. This ensures benchmarks test actual translation quality rather than trivial numeric segments. The log and info dialog report how many segments were skipped.
- **SuperBench: Add Clear Dataset button** – After creating a dataset, a "Clear" button now appears next to the confirmation label, allowing you to reset and create a new dataset with different settings.

---

## v1.9.320 - February 24, 2026

### Improvements

- **Dynamic provider labels in AI Settings** – The LLM Provider Selection radio buttons now show the currently selected model in brackets (e.g. "Google Gemini (Gemini 3.1 Pro)") instead of hardcoded generic model names. The label updates automatically when you change the model in the Model Selection dropdowns below.

---

## v1.9.319 - February 24, 2026

### New Features

- **Update Superbench model lists** – Added latest models to LLM Superbench dropdowns for all three providers:
  - **OpenAI:** Added `o3-mini` reasoning model
  - **Claude:** Added `claude-sonnet-4-6` (latest Sonnet) and `claude-opus-4-6` (was missing from legacy UI)
  - **Gemini:** Added `gemini-3.1-pro-preview` and `gemini-3-pro-preview`
- Updated both `llm_superbench_ui.py` and `superbench_ui.py` model dropdowns and display name mappings

---

## v1.9.318 - February 24, 2026

### Bug Fixes

- **Fix "Check for New Models" not detecting any models** – The model version checker was looking up API keys using incorrect names (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY`) instead of the actual key names returned by `load_api_keys()` (`openai`, `claude`, `google`/`gemini`). All three provider checks silently returned "No API key" and no new models were ever found. Fixed the key name mapping so the checker now correctly authenticates with all configured providers.

### New Features

- **Add Gemini 3.1 Pro Preview model** – Added Google's latest `gemini-3.1-pro-preview` model to the Settings model dropdown, known models list, and vision models registry. Described as the smartest Gemini model for complex tasks.

---

## v1.9.317 - February 24, 2026

### Bug Fixes

- **Fix non-breaking space imported as literal `\~` from memoQ RTF (GitHub #171)** – Non-breaking spaces (`\~`), optional hyphens (`\-`), and non-breaking hyphens (`\_`) in memoQ bilingual RTF files were imported as literal text instead of being converted to the correct Unicode characters (U+00A0, U+00AD, U+2011). These RTF control symbols are now properly decoded during import. The same fix was applied to the Déjà Vu RTF handler.

---

## v1.9.316 - February 24, 2026

### Bug Fixes

- **Fix memoQ "Edited" status mapping (GitHub #139 follow-up)** – memoQ "Edited" segments (indicating the translator actively edited the segment) were incorrectly mapped: "Edited (101%)" was displayed as CM (context match), and other "Edited" variants like "Edited (98%)" or "Edited (68%)" fell through to "Pre-translated". All "Edited" segments now correctly map to "Translated" status with their match percentage preserved. Also fixed the status matching order so that specific equivalents like "Pre-translated (101%)" are matched before generic ones like "Pre-translated", ensuring CM/PM/TM 100% statuses are correctly distinguished from generic pre-translated.

---

## v1.9.315 - February 24, 2026

### New Features

- **TM match rate display for all import formats (GitHub #139)** – When importing bilingual files that contain TM match information, the match percentage is now shown in the status column with color-coded display (green for 100%+, blue for 90–99%, dark blue for fuzzy). Match rates can also be used for filtering and sorting. Previously this data was present in the source files but not carried through to the UI.
  - **SDLXLIFF / SDLPPX (Trados)** – Match percentages from the SDL `percent` attribute are now displayed: TM matches show their actual percentage, Context Matches show 101%, Perfect Matches show 102%.
  - **memoQ bilingual DOCX** – Match percentages are extracted from the status column text (e.g. "Pre-translated (97%)") and the proper memoQ status is now mapped to Supervertaler's status hierarchy.
  - **memoQ bilingual RTF** – Same as DOCX: match percentages from the status column are now parsed and displayed.
  - **memoQ XLIFF (.mqxliff)** – Match percentages are extracted from the `mq:percent` attribute on trans-units when available.

---

## v1.9.314 - February 23, 2026

### Bug Fixes

- **Show correct keyboard shortcuts on Mac/Linux (GitHub #167)** – Fixed remaining hardcoded "Ctrl+" references in user-visible text that weren't using the platform-aware `format_shortcut_for_display()` function. On macOS, shortcuts now correctly display with ⌘/⌥/⇧ symbols instead of Ctrl/Alt/Shift. Fixes: glossary quick-add dialog ("Ctrl+E" → "⌘E" on Mac), and the HTML keyboard shortcuts cheatsheet export (both shortcut column and description text now formatted for the current platform).

---

## v1.9.313 - February 23, 2026

### New Features

- **Optional locked segment loading for all Trados imports** – When importing SDLPPX packages, standalone SDLXLIFF files, or SDLXLIFF folders that contain locked/non-translatable segments, users are now asked whether to include them in the grid for context. For SDLPPX packages, a checkbox appears in the info dialog showing the locked count (e.g. "Include locked/non-translatable segments for context (40,300 segments)"). For standalone and folder imports, a Yes/No confirmation dialog appears when locked segments are detected. Default is to exclude them (fast). Lock TU structural containers are always excluded regardless.
- **Locked row visual distinction** – When locked segments are included, they display with a muted tan/grey background (`#E0DDD8` light, `#3a3a3a` dark) and their target cells are read-only, so they're clearly distinguishable from translatable segments. The existing Advanced Filters "Locked Status" filter also works to show/hide them post-import.

---

## v1.9.312 - February 23, 2026

### New Features

- **SDLPPX multi-file views** – When importing a Trados Studio package (SDLPPX) containing multiple SDLXLIFF files, the multi-file views system now activates automatically. Each file appears in the file filter dropdown, letting you select individual files to work on or create custom views – just like the existing multi-file folder import. Previously, all files were lumped together with no way to filter by file.

### Improvements

- **memoQ submenu in Import/Export menus** – The three memoQ import formats (Bilingual Table DOCX, Bilingual Table RTF, XLIFF .mqxliff) and their corresponding exports are now grouped under a "memoQ" submenu, matching the existing Trados Studio submenu pattern. Declutters the Import and Export menus.
- **SDLPPX import performance** – Locked and non-translatable segments (lock TUs, `translate="no"` structural segments) are now filtered out before loading into the UI grid. A package with ~42,000 total segments but only ~1,700 translatable ones now loads in seconds instead of 5–10 minutes.

### Bug Fixes

- **SDLPPX import: fix "Total segments: 0"** – SDLXLIFF files inside Trados packages are stored in subdirectories (e.g. `nl-NL/import/...`), but the loader used non-recursive `.glob('*.sdlxliff')` which only searched the top level. Changed to `.rglob('*.sdlxliff')` so files in nested folders are found correctly.
- **SDLXLIFF export: lock TU handling for partial-lock segments** – When exporting translations for segments containing inline lock elements (`<x id="lockedN" xid="lockTU_UUID"/>`), each target context now gets its own unique lock TU with a fresh UUID-based xid. Previously, new targets either lost their lock xids entirely or reused the seg-source xids, which could cause Trados Studio to reject the file. Already-translated segments preserve their existing target lock xids.
- **SDLXLIFF export: fix self-closing mrk regex cross-matching** – The mrk regex `[^>]*>` could match through the `/>` of a self-closing `<mrk ... />` and grab the next `</mrk>` as its closing tag, causing incorrect content substitution in TUs with mixed self-closing and regular mrk elements. Fixed with a negative lookbehind `(?<!/)` before `>`.
- **Unicode crash on Windows console** – A `→` (Unicode arrow) character in a log message caused `UnicodeEncodeError` on Windows cp1252 consoles, crashing `load_package()`. Replaced with ASCII `->`.

---

## v1.9.311 - February 23, 2026

### Bug Fixes

- **SDLXLIFF export: fix missing translations for self-closing mrk segments** – When Trados SDLXLIFF files contained self-closing `<mrk mtype="seg" mid="N" />` tags in the `<target>` element (used for empty/untranslated segments), Supervertaler's export would silently skip them because the regex only matched the `<mrk ...>content</mrk>` open+close form. This caused translated segments to appear empty when re-opened in Trados Studio. The export now handles both forms correctly via a second replacement pass for self-closing tags.

---

## v1.9.310 - February 22, 2026

### Improvements

- **Tools tabs converted to vertical sidebar** – The 11 tool modules (AutoFingers, Superconverter, PDF Rescue, Superbench, Superbrowser, Supercleaner, Superlookup, Supervoice, Text Encoding Repair, TMX Editor, Tracked Changes) now use the same vertical sidebar navigation as the Settings tab instead of horizontal tabs. This saves vertical space, looks cleaner, and works much better on smaller screens. Dark mode and font scaling are fully supported.

---

## v1.9.309 - February 22, 2026

### Improvements

- **Web Resources sidebar is now scrollable** – The resource list in the Superlookup Web Resources tab is now wrapped in a scroll area, so all resources remain accessible on smaller screens or when the panel height is limited. Button padding was also tightened to fit more items in view.
- **Updated Beijerterm search URL** – The Beijerterm web resource now uses the MediaWiki full-text search URL (`/w/index.php?search=…`) instead of the old homepage query parameter. Settings label updated from "michaelbeijer.co.uk" to "Beijerterm".

---

## v1.9.308 - February 22, 2026

### Improvements

- **Termview respects line breaks from source text** – When the source segment contains line breaks (e.g. multi-line content), the Termview flow layout now starts a new row at each `\n`, matching the visual structure of the source grid cell instead of flowing all words into a single continuous block.

---

## v1.9.307 - February 22, 2026

### New Features

- **Toggle TM Source / TM Target layout: horizontal ↔ vertical** – Right-click on the TM Source or TM Target pane in the Match Panel and select "↕ Stack TM Source / Target Vertically" to switch from side-by-side to vertically stacked layout (giving each pane the full width). Also available via View > Match Panel > "Stack TM Panes Vertically". The preference is persisted across sessions.

---

## v1.9.306 - February 22, 2026

### Bug Fixes

- **Fixed: glossary/termbase matching broken when Show Invisibles is active** – Multi-word glossary terms like "hinge load" failed to match when invisible space markers (`·\u200B`) were active, because several code paths passed display text (containing markers) instead of clean `segment.source` to the matching functions. Added defensive `reverse_invisible_replacements()` guards in `find_termbase_matches_in_source()` and `_update_both_termviews()` to ensure the termbase search and Termview tokenizer always receive clean text regardless of the source.
- **Fixed: Ctrl+Shift+F filter searches for marker-containing text** – When selecting text in the grid with Show Invisibles active (e.g. "Wall·soffits") and pressing Ctrl+Shift+F, the filter searched for the marker-containing text instead of "Wall soffits". Added `reverse_invisible_replacements()` to strip markers before using selected text as filter criteria.
- **Fixed: source cell termbase highlighting with invisible markers** – The `highlight_termbase_matches()` method in `ReadOnlyGridTextEditor` searched for clean glossary terms in display text containing invisible markers. Rewrote to build a clean-text-to-display-text position mapping so term positions are found in clean text and then mapped back to display positions for cursor highlighting.
- **Fixed: Copy Source to Target wrote invisible markers into segment data** – The "Copy Source to Target" functions (both menu and Ctrl+Shift+S keyboard shortcut) read from `source_widget.toPlainText()` which included invisible character markers (·, →, °, ↵, \u200B), corrupting `segment.target`. Fixed to use clean `segment.source` instead.
- **Fixed: QuickTrans (Ctrl+M) sent invisible markers to MT providers** – The QuickTrans fallback path read source text from the widget display instead of clean segment data. Fixed to use `segment.source` and strip any markers from selected text before sending to translation APIs.
- **Fixed: various toPlainText() data contamination paths** – Several code paths that saved widget display text back to segment data or export files could include invisible markers. Added `reverse_invisible_replacements()` guards to match insertion, term extraction, and Trados bilingual export paths.

---

## v1.9.305 - February 22, 2026

### Bug Fixes

- **Fixed: ↵ line break markers not showing in grid cells** – After loading a project, `_refresh_grid_display_mode()` (for WYSIWYG/Tags toggle) overwrote the grid cell text with raw `segment.source`/`segment.target` that had no invisible-character markers applied. The ↵ markers set during `load_segments_to_grid()` were immediately lost. Fixed by applying `apply_invisible_replacements()` in `_refresh_grid_display_mode()` before calling `update_display_mode()` on each cell.
- **Fixed: double ↵↵ markers in TM Target pane** – When a TM entry had been saved with stale invisible markers (e.g. `↵\n` already baked into the stored target text), `apply_invisible_replacements()` would convert the surviving `\n` into another `↵\n`, producing `↵↵\n`. Fixed by calling `reverse_invisible_replacements()` on all TM text before re-applying the current display settings, and by stripping markers in `save_segment_to_activated_tms()` to prevent marker leakage into the TM in the first place.

---

## v1.9.304 - February 22, 2026

### Bug Fixes

- **Fixed: line breaks not visible in TM Source / TM Target panes** – When a TM entry contained line breaks (`\n`), the Match Panel's TM Source pane collapsed them into spaces because the diff-highlighting tokeniser used `.split()` which strips newlines, then `.join(' ')` which replaced them with spaces. Rewrote the tokeniser with `re.split(r'(\n)', text)` to preserve `\n` as explicit tokens; the renderer now calls `cursor.insertBlock()` for newline tokens and optionally shows the ↵ marker when Show Invisibles line breaks are enabled. The TM Target pane and all fallback paths now also apply `apply_invisible_replacements()` so ↵ markers are shown consistently. Toggling Show Invisibles now also refreshes the Match Panel TM display in real time.

---

## v1.9.303 - February 22, 2026

### Bug Fixes

- **Fixed: double-clicking in target box selects entire segment instead of individual word** – Changing the Show Invisibles defaults (tabs, non-breaking spaces, line breaks ON) activated the custom double-click handler in `EditableGridTextEditor`, but its word-delimiter set was missing the regular space character (`' '`). When spaces weren't displayed as markers, the scan found no delimiters and selected the entire segment text. Fixed by adding `' '` to the `DELIMITERS` set in both `EditableGridTextEditor` and `ReadOnlyGridTextEditor`. Also rewrote `ReadOnlyGridTextEditor.mouseDoubleClickEvent()` to match the improved pattern (checks `any(invisible_display_settings.values())` instead of only spaces).

---

## v1.9.302 - February 22, 2026

### New Features

- **Right-click Edit / Delete TM entries from the Match Panel** – Right-clicking the TM Source or TM Target pane in the Match Panel now shows "Edit TM Entry" and "Delete TM Entry" options alongside the standard Copy / Select All actions. Edit opens a dialog with the source (read-only) and an editable target field; changes are written to the database immediately and reflected live in the panel. Delete removes the entry after a confirmation prompt. Both actions work on all TM types (project TM, imported TMs, etc.).

### Improvements

- **Show Invisibles: better defaults** – Tabs, non-breaking spaces, and line breaks are now visible by default for new users. Spaces remain hidden. Users can still change any combination via the Show Invisibles dropdown.
- **Segment number column auto-sizes to fit content** – The # column in the grid no longer caps at 55 px; it now grows to accommodate any number of digits, so projects with hundreds or thousands of segments display the full segment number.

### Bug Fixes

- **Fixed: TM Source pane in Match Panel lost line breaks** – When a TM entry contained line endings (e.g. multi-line segments from Trados Studio), the TM Source pane in the Match Panel collapsed them into spaces because the diff-highlighting tokeniser used `.split()` which eats newlines. Fixed by tokenising with a regex that preserves `\n` as explicit tokens and emitting `insertBlock()` in the QTextEdit cursor when a newline token is encountered.
- **Fixed: blurry right-click context menu on TM Source / TM Target panes** – The QTextEdit widgets use `background-color: transparent` for seamless rendering inside their coloured container, but this style leaked into the context menu, causing fuzzy text on high-DPI displays. The context menu now gets an explicit opaque palette-based stylesheet.

---

## v1.9.301 - February 21, 2026

### Improvements

- **Ollama model list audited and updated for February 2026** – Replaced the recommended local LLM list with the best currently available models. Key changes:
  - **Added TranslateGemma** (4B / 12B / 27B) – Google's purpose-built translation model (Jan 2026), fine-tuned on Gemma 3 with reinforcement learning. Supports 55 languages including Dutch. The 12B model outperforms the general-purpose Gemma 3 27B on translation benchmarks. Now the recommended default Ollama model.
  - **Upgraded Qwen 2.5 → Qwen 3** (4B / 8B / 14B / 32B) – Alibaba's latest generation with 100+ language support and improved quality across the board.
  - **Upgraded Gemma 2 → Gemma 3 12B** – Google's latest general-purpose model with 140+ languages and multimodal support.
  - **Kept Aya Expanse 8B** – Still the best Dutch-specialist model at its size, no newer version available.
  - **Removed 12 outdated models**: Qwen 2.5 3B/14B/32B/72B, Llama 3.2 3B, Llama 3.1 8B, Mistral 7B, Gemma 2 9B, Mixtral 8x7B, Command R 35B, OpenEuroLLM Dutch, GEITje 7B Ultra, StableLM 2 12B – all surpassed by newer alternatives.

---

## v1.9.300 - February 21, 2026

### Bug Fixes

- **Fixed: TM/MT/LLM matches not appearing when grid filters are active** – When a Source or Target text filter was applied, navigating to a segment would skip all Translation Memory, MT, and LLM lookups as a performance optimisation. This meant the Match Panel and Translation Results panel stayed empty, even when pressing F5 (Force Refresh). Fixed by replacing the hard skip with a longer debounce timer (250ms vs 10ms in normal mode): rapid navigation through filtered results stays snappy, but settling on a segment now triggers the full TM/MT/LLM lookup pipeline as expected. Glossary/TermView updates from cache still happen immediately.

---

## v1.9.299 - February 21, 2026

### Bug Fixes

- **Fixed: TM fuzzy matching misses segments that differ only by an inline line break** – Trados Studio and memoQ sometimes embed a heading or label as a line break within a segment (e.g. `"Door stops↵\nIf it is necessary to use a door stop…"`). When this multi-line segment was compared against the single-line body text stored in the TM (`"If it is necessary…"`), `SequenceMatcher` treated the `\n` characters as literal content differences, significantly lowering the similarity score and causing matches to fall below the 75% threshold. Fixed by normalising line breaks to spaces in `calculate_similarity()` before computing the ratio, consistent with the existing behaviour of `_normalize_for_matching()` used for exact matching.

---

## v1.9.298 - February 21, 2026

### Improvements

- **Glossaries terms table: all columns now resizable** – Columns Priority, Domain, Notes, Project, Client, and Forbidden were fixed-width and could not be resized by dragging the column header dividers. Changed their resize mode from Fixed to Interactive so all columns except the delete button (col 8) can be resized. Source and Target columns retain their Stretch behaviour and share the remaining space.

---

## v1.9.297 - February 21, 2026

### Bug Fixes

- **Fixed: invisible markers saved into glossary terms when Show Invisibles is active** – When "Show Invisibles" was enabled (e.g. Spaces showing as `·`), selecting text to add to the termbase would include the display markers in the saved term (e.g. `Door·closers` instead of `Door closers`). The marker-containing term would not match the clean segment text during lookup, so the term appeared to be missing from TermView. Fixed by applying `strip_invisible_markers()` (or `reverse_invisible_replacements()`) to all `selectedText()` calls in the six add-to-glossary code paths: `_handle_add_to_termbase` (Ctrl+E), `_handle_quick_add_to_termbase` (Ctrl+Q / Alt+Left) on both `GridTextEditor` and `EditableGridTextEditor`, and `_quick_add_term_with_priority` (Alt+Up/Down).

---

## v1.9.296 - February 21, 2026

### Improvements

- **Show Invisibles: line break marker changed from ¶ to ↵** – The pilcrow (¶) conventionally denotes a paragraph end; the correct symbol for a soft return / inline line break (as used by Trados Studio, Word, and most CAT tools) is ↵ (U+21B5). Updated the display marker, menu label ("Line Breaks (↵)"), highlighter, word-delimiter sets, and all reverse-stripping logic. Legacy ¶ markers are still stripped on load for backwards compatibility with any files saved by earlier versions.

---

## v1.9.295 - February 21, 2026

### Bug Fixes

- **Fixed: Batch translation drops line breaks in multi-line segments** – When Trados Studio (or memoQ) stores multiple lines within a single segment (e.g. a list of door specs), the batch translator was silently discarding all lines after the first. Root cause: the response parser split the LLM output on `\n` and only kept lines matching `^\d+\.` – continuation lines without a leading number were thrown away. Fixed with a state-machine parser that appends un-numbered lines to the translation of the most recently matched numbered segment, preserving all internal line breaks. The batch prompt instructions were also updated to explicitly tell the LLM to preserve source line breaks (previously the instructions said "do NOT split into multiple lines", which discouraged preservation of legitimate breaks).
- **Fixed: Show Invisibles pilcrow (¶) not shown immediately after Shift+Enter** – Pressing Shift+Enter to insert a line break while "Line Breaks (¶)" was active in Show Invisibles would insert a raw `\n` into the cell without the pilcrow marker. The marker only appeared after toggling Show Invisibles off and back on. Fixed: `on_target_text_changed` now re-applies invisible markers to the widget display whenever the markers-applied form differs from the current widget text, preserving cursor position by mapping through the clean (marker-free) text.

---

## v1.9.294 - February 20, 2026

### Bug Fixes

- **Fixed: Show Invisibles markers permanently saved into .svproj files** – The root cause of invisible-character markers (·, →, ¶, U+200B) appearing permanently in certain segments even after toggling Show Invisibles off was that the markers had been written into the `.svproj` JSON file on disk as part of `segment.target`. Once saved, every subsequent project load would populate `segment.target` with the corrupted text, making the markers reappear regardless of display settings. Fixed with a three-layer defence: (1) a new module-level `strip_invisible_markers()` function is called inside `Segment.from_dict()` on every project load – any markers in existing saved files are stripped immediately on load; (2) `save_project_to_file()` now sanitises all `segment.target` values through the same function just before writing the JSON, so markers can never reach disk even if one slipped through the editor pipeline; (3) the `.svproj` file for the active LANDEXX project was cleaned directly to immediately fix the three affected segments (3, 4, 5).

---

## v1.9.293 - February 20, 2026

### Bug Fixes

- **Fixed: invisible-character markers stick in target cells after toggling off** – After disabling "Show Invisibles", the middle-dot space markers (·), tab arrows (→), etc. remained visible in target cells that had been previously edited. Three root causes fixed: (1) `reverse_invisible_replacements()` was gated on whether each setting was currently *on*, so it couldn't strip markers from a cell when the setting was toggled off – it now always strips all marker types unconditionally; (2) `refresh_grid_invisibles()` was calling `blockSignals(False)` after each `setPlainText()`, which caused Qt to deliver a queued `textChanged` event that wrote the stale marker text back into `segment.target` – fixed by raising `_suppress_target_change_handlers` during the refresh and resetting `_initial_load_complete` per widget so the queued event is discarded; (3) `refresh_grid_invisibles()` now always reads from `segment.target` (the canonical clean text) rather than from the widget's current display text, so edited segments are not corrupted.
- **Fixed: double-click word selection broken when Show Invisibles is active** – Double-clicking to select a word for glossary lookup would select only a fragment of the word because the zero-width space (U+200B) that is inserted alongside each `·` space-marker was listed as a word delimiter. Removed U+200B from the delimiter set in `mouseDoubleClickEvent`; it is now treated as a transparent word-wrap hint. Also generalized the override to activate whenever *any* invisible setting is on, not only when "Spaces" is on.

---

## v1.9.292 - February 20, 2026

### Bug Fixes

- **Fixed: Show Invisibles toggle causes invisibles to immediately disappear** – Toggling any "Show Invisibles" option (Spaces, Tabs, Non-breaking Spaces, Line Breaks, or Toggle All) would briefly render the invisible-character markers, which then vanished within a few seconds. Root cause: `refresh_grid_invisibles()` was calling `load_segments_to_grid()` – a full grid rebuild that overwrote the rendered markers. Fixed by rewriting `refresh_grid_invisibles()` to update cell widgets **in-place** (iterating existing `QTextEdit` widgets and calling `setPlainText()` with signals blocked) instead of triggering a full grid reload. Additionally fixed a cascade bug in "Toggle All" where `setChecked()` on the four menu actions was firing the `triggered` signal for each, causing up to 4 extra full grid reloads and incorrect toggle state. Signals on those actions are now blocked during the checkbox update.

---

## v1.9.291 - February 20, 2026

### Bug Fixes

- **Fixed: resource bleed between projects (TMs, glossaries, NT lists)** – When starting a new project via **Import → Phrase Bilingual**, **Import → Déjà Vu Bilingual**, **Import → Trados Studio → SDLPPX Package**, or **Import (Multi-file)**, previously activated translation memories, glossaries, and NT lists from an earlier project were not being cleared. This could cause lookups against stray resources that belong to a completely different project. Fixed by calling `_deactivate_all_resources_for_new_project()` at the start of each of these import paths, consistent with the behaviour already in place for DOCX, plain-text, memoQ bilingual, and SDLXLIFF imports.

---

## v1.9.290 - February 20, 2026

### New Features

- **Status column short labels** – The status column now shows a compact text abbreviation to the right of the status icon for statuses where it adds clarity: `CM` (context match), `PM` (perfect match), `MT` (machine translated), `Rep` (repetition), `Pre` (pre-translated), `Fuz` (fuzzy), `100%` (exact TM match), `TRC` (TR confirmed), `PR` (proofread), `Rej` (rejected), `App` (approved). When a numeric match percentage is already shown (e.g. `101%` from a TM lookup), the short label is suppressed to avoid redundancy. The label is styled in small bold text and has the same tooltip as the icon.

---

## v1.9.289 - February 20, 2026

### Bug Fixes

- **Fixed: file boundary banner overlapping the last segment of the previous file** – The extra 20px row height added for banner spacing was incorrectly added to the first row of the *new* file instead of the last row of the *previous* file. The banner (positioned at the top of the new file's row) therefore sat on top of segment content rather than in the empty gap below it. Fixed by adding the padding to the correct row.

---

## v1.9.288 - February 20, 2026

### New Features

- **File boundary banner labels in multi-file projects** – In multi-file projects (SDLXLIFF folder import, DOCX folder import, etc.), a dark blue full-width banner now appears above the first segment of each new file in the segment grid, showing the filename (e.g. `📄 Belastungswerttabellen_SW_20260217_afb.xlsx.sdlxliff`). The banners are floating overlays on the table viewport – they scroll with the grid, reposition on column resize, and disappear automatically when a file is filtered out using the file filter dropdown. The old subtle 2px blue border line between files is replaced by this more prominent and descriptive header.

---

## v1.9.287 - February 20, 2026

### Bug Fixes

- **Fixed: file filter dropdown not appearing after SDLXLIFF folder import** – The file filter dropdown (and Views system) was correctly built in v1.9.286 but never made visible because `_update_file_filter_combo()` was not being called at the end of `import_sdlxliff_folder()`. One missing call added; the dropdown now appears immediately after import.

---

## v1.9.286 - February 20, 2026

### New Features

- **Multi-file UI for SDLXLIFF folder imports** – When importing a folder of `.sdlxliff` files via **Import → Trados Studio → Bilingual XLIFF Folder**, the project is now correctly treated as a multi-file project. Each file gets its own `file_id`, so the full multi-file UI activates automatically: a **blue separator line** appears in the segment grid at each file boundary, the **file filter dropdown** in the toolbar lets you show only segments from a specific file, and the **Views system** (Manage Views…) is available for grouping files into custom views. Previously all segments were imported into a flat single-file project with no visual distinction between files.

---

## v1.9.285 - February 19, 2026

### New Features

- **Import folder of SDLXLIFF files** (GitHub issue #80) – A new **Import → Trados Studio → Bilingual XLIFF Folder (.sdlxliff)...** menu item lets you point Supervertaler at a folder and automatically import all `.sdlxliff` files found inside it, including those in subfolders. This covers workflows where clients send a directory of SDLXLIFF files instead of a packaged `.sdlppx`. After translating, export all files back via the existing **Export → Trados Studio → Bilingual XLIFF - Translated (.sdlxliff)...** menu.

---

## v1.9.284 - February 19, 2026

### New Features

- **Subscript and superscript formatting support** – `<sub>` and `<sup>` tags are now fully supported end-to-end alongside the existing `<b>`, `<i>`, `<u>` inline formatting. Subscript/superscript text is correctly imported from memoQ bilingual RTF (`{\sub V}` brace groups), memoQ bilingual DOCX, and monolingual DOCX files. Tags render visually in WYSIWYG grid mode and the Preview panel, round-trip cleanly to RTF and DOCX on export, and are properly handled by all utility functions (tag detection, stripping, and run parsing).

### Bug Fixes

- **Fixed: memoQ bilingual RTF subscript/superscript rendered as `{V}` instead of `V`** – RTF brace-scoped formatting groups like `{\sub V}` were not handled, leaving stray braces in the extracted text (e.g. `C{V}` instead of `CV`). These groups are now parsed and converted to `<sub>`/`<sup>` tags.

---

## v1.9.283 - February 19, 2026

### New Features

- **Alt+Up / Alt+Down – Quick-add term pair to Priority 1 / Priority 2 glossary** – Two new keyboard shortcuts (also available in the right-click context menu of source and target cells) let you instantly add the selected source+target text as a term pair to your highest- or second-priority glossary without any dialog. Works identically to the existing Alt+Left "Quick Add to last-used glossary" shortcut. These are handled via an application-level event filter to bypass Qt's `QAbstractItemView` arrow-key interception.
- **Ctrl+Up / Ctrl+Down – Navigate to previous / next segment** – Segment navigation has been reassigned from Alt+Up/Down (which never worked reliably due to Qt table arrow-key interception) to Ctrl+Up/Down. Navigation is now handled via the same app-level event filter for consistent, reliable behaviour regardless of which cell has focus.

### Bug Fixes

- **Fixed: Glossary highlights and TermView not updating in filtered mode** – When navigating between segments while a text filter was active, glossary matches were not highlighted in the source cell and the TermView pane was not updated (requiring F5 to refresh). Root cause: the filtered-mode early exit in `on_cell_selected` skipped all termbase/glossary processing. Fixed by adding `_on_cell_selected_glossary_only()` which runs the cheap, cache-based termbase highlighting and TermView update even in filtered mode, while still skipping the expensive TM/MT/LLM lookups.
- **Fixed: Legacy match-cycling shortcuts (Ctrl+Up/Down) causing silent shortcut conflicts** – The old Translation Results panel (now removed) had registered Ctrl+Up/Down as QShortcuts for cycling through matches. These were never cleaned up and caused Qt's "ambiguous shortcut" behaviour, silently disabling any other shortcut registered with the same key. Cleared the legacy defaults and removed the duplicate QShortcut registrations.

### UI/UX

- **Renamed "termbase" → "glossary" in shortcuts settings** – The Keyboard Shortcuts settings dialog now reads "Add selected term pair to glossary (with dialogue)" and "Quick add term pair to the last used glossary" instead of the old "termbase" wording.

---

## v1.9.282 - February 18, 2026

### New Features

- **Match Panel Font Settings in View Settings** – A new "📊 Match Panel Font Settings" section has been added to the View Settings tab (alongside the existing Termview Font Settings). Users can now configure Font Family, Font Size (6–18 pt), and Bold for the TM Source and TM Target boxes in the Match Panel. Settings persist across restarts. The font size spinner here is equivalent to the View → Match Panel zoom shortcuts.

### Bug Fixes

- **Fixed: Match Panel font zoom truly not persisting across restarts** – The v1.9.280 fix was incomplete. While the QTextEdit stylesheet was updated correctly, TM Source content rendered via `QTextCursor.insertText()` (the diff-highlighted view) ignores the stylesheet and uses the document's internal default font instead. Fixed by also calling `text_edit.document().setDefaultFont()` in `_apply_match_panel_font_size()`, and by setting the document default font during widget creation in `_create_compare_panel_box()`. This ensures that both plain-text and rich-text (diff-highlighted) content render at the correct size.
- **Fixed: Glossary highlights missing in source cell on cache-hit navigation** – When navigating to a segment that was already in `translation_matches_cache` (cache hit), glossary highlights were sometimes not applied to the source cell even though the TermView showed the correct matches. Root cause: the grid highlighting code read exclusively from `termbase_cache`, which could be empty if the prefetch worker populated `translation_matches_cache` before the batch termbase worker had processed that segment, or after a cache clear. Fixed by falling back to the TB matches already present in `cached_matches["Termbases"]` when `termbase_cache` is empty, and backfilling `termbase_cache` so future navigations are instant. Previously required pressing F5 to force-refresh highlighting.

---

## v1.9.281 - February 18, 2026

### New Features

- **HTTP proxy support for all AI and MT service connections** (GitHub issue #168) – A new "🌐 HTTP Proxy Settings" section has been added to the AI Settings tab. Users can enter a proxy host, port, and optional username/password. When enabled, all outbound API requests are routed through the configured proxy:
  - **OpenAI & Claude**: custom `httpx.Client(proxy=...)` passed to each SDK constructor
  - **Ollama**: `proxies=` dict passed to `requests.post()`
  - **Gemini**: `HTTPS_PROXY` / `HTTP_PROXY` environment variables set at startup and on every settings save (the Gemini SDK does not support per-client proxy configuration)
  - **Google Translate, Microsoft Translator, ModernMT, MyMemory**: `proxies=` passed to every `requests` call
  - **DeepL**: `proxy=` passed to `deepl.Translator()` constructor
  - Settings persist between sessions in `settings/settings.json`. If no proxy is configured, all behaviour is unchanged.

---

## v1.9.280 - February 18, 2026

### Bug Fixes

- **Fixed: Match Panel zoom level not remembered across restarts** – The zoom level (set via View → Match Panel → Zoom In/Out, or Ctrl+Alt+=/−) was correctly saved to `general_settings.json`, but lost on restart. Root cause: the right-panel UI was constructed with `font-size: 10px` hardcoded in the QTextEdit stylesheet, before the saved font size was loaded from disk. The later `_apply_match_panel_font_size()` call (which patches the stylesheet via regex) was a fragile fallback that could silently fail. Fixed by: (1) pre-loading the saved `match_panel_font_size` from preferences *before* `_create_match_panel()` runs, so the stylesheet is baked with the correct size from the start; (2) making the stylesheet use the live `SupervertalerQt.match_panel_font_size` class variable instead of a hardcoded `10`.

---

## v1.9.279 - February 18, 2026

### New Features

- **Find & Replace: Auto-adjust case option** – Added an "Auto-adjust case" checkbox to the Find & Replace dialog. When enabled (and "Case sensitive" is off), the replacement text is automatically adjusted to match the case pattern of each matched string: ALL CAPS → uppercased, all lowercase → lowercased, Title Case → title-cased, Sentence case → first letter uppercased. Works in "Replace this", "Replace all", and F&R Sets batch operations. The case pattern is stored per-operation in saved F&R Sets for full round-trip fidelity.

---

## v1.9.278 - February 18, 2026

### Bug Fixes

- **Fixed: shorter glossary terms inside longer phrases now always show in TermView and source highlighting** – When a long project-glossary phrase (e.g. "de gekarakteriseerde luchttoevoerkleppen en de gekarakteriseerde luchtafvoerkleppen") matched first, its character positions were claimed and shorter individual terms sharing those characters (e.g. "gekarakteriseerde") were silently suppressed in both the TermView and the source-cell highlighting. Root cause: both the TermView tokenizer and the source highlighter sorted terms longest-first and then skipped any shorter term whose positions were already claimed – ignoring the "Hide shorter glossary matches" setting entirely. Fixed by wiring the `termbase_hide_shorter_matches` setting to both code paths: when the checkbox is **off** (default), individual glossary words always appear even inside a longer matched phrase; when **on**, the overlap suppression is applied as intended.

### Removed

- **Removed "Glossary match display order" setting** – The dropdown (Order of appearance / Alphabetical / By length) was only ever wired to the now-defunct Translation Results Panel and had no effect on the TermView. The TermView's design principle is to mirror the source text left-to-right, so order-of-appearance is always correct and the setting was meaningless. Removed from Settings UI, all save/load paths, project settings, and the dead panel code.

---

## v1.9.277 - February 18, 2026

### New Features

- **Added Claude Sonnet 4.6 support** – Claude Sonnet 4.6 (`claude-sonnet-4-6`) is now available across all model selectors and is set as the new default Claude model. Pricing is identical to Sonnet 4.5 ($3/$15 per million tokens). The model version checker's known-models list has been updated so it won't flag Sonnet 4.6 as a "new model" on next check.

### Improvements

- **Updated startup banner** – Terminal launch message now reads "Supervertaler - AI-enhanced CAT tool" instead of "AI Translation Tool".

---

## v1.9.276 - February 17, 2026

### Bug Fixes

- **Fixed SQLite "database is locked" errors during termbase operations** – Activating, deactivating, or changing read-only status of termbases would intermittently fail with `sqlite3.OperationalError: database is locked`. Root cause: multiple SQLite connections to the same database file (main thread, TM search threads, prefetch worker, pre-translation worker) competing for locks under the default DELETE journal mode, which blocks all readers while a writer is active. Fixed by enabling WAL (Write-Ahead Logging) journal mode and setting a 15-second busy timeout on all connections. Also added rollback on error in termbase manager to release locks after failed commits.
- **Removed leftover debug file writes that crashed macOS Finder launch** – `theme_debug.txt` and `superlookup_debug.txt` were being written to the current working directory, which is `/` (read-only) when launched from Finder, causing an immediate `OSError: [Errno 30] Read-only file system` crash.

---

## v1.9.275 - February 17, 2026

### Bug Fixes

- **macOS: app now launches when double-clicked from Finder** – PyInstaller-built  bundles would bounce in the dock and close when launched via Finder (double-click or right-click > Open), while working perfectly from Terminal. Root cause: macOS Finder launches apps with a minimal environment where  and  are not set, causing locale-dependent code to fail silently. Fixed by adding  to the  in  to ensure UTF-8 locale is always available. Also added diagnostic crash logging to  for frozen macOS builds, since Finder-launched apps have no stdout/stderr.
- **macOS build: added ad-hoc code signing instructions** – Updated  with a free ad-hoc  step that helps macOS treat the app as legitimate without requiring a paid Apple Developer account.

---

## v1.9.274 - February 17, 2026

### Bug Fixes

- **Find & Replace: uppercase history no longer overwrites lowercase input** – The `HistoryComboBox` in the F&R dialog had an auto-created `QCompleter` with case-insensitive matching, which forced previously searched uppercase terms (e.g. "COMMISSIONING") over lowercase input. Disabled the completer; the dropdown history is still accessible via the arrow button. Resolves [#163](https://github.com/michaelbeijer/Supervertaler/issues/163).
- **Standalone builds now show the correct version number** – PyInstaller `.exe` builds always displayed "v1.9.227" because `pyproject.toml` was not bundled, causing `_read_version()` to fall back to a hardcoded string. Fixed by: (1) adding `pyproject.toml` to the `datas` list in both Windows and macOS `.spec` files, (2) adding an `importlib.metadata` fallback for `pip install` users, (3) fixing `create_release_zip.py` to read the version from `pyproject.toml` instead of regex-parsing `Supervertaler.py`, and (4) making the macOS `.spec` read `CFBundleVersion` dynamically from `pyproject.toml` instead of a hardcoded stale value. Resolves [#165](https://github.com/michaelbeijer/Supervertaler/issues/165).

---

## v1.9.273 - February 17, 2026

### Bug Fixes

- **TM matches now shown consistently when revisiting segments** – When navigating back to a previously visited segment, the Match Panel (TM Source/Target comparison boxes) showed no TM hits despite the matches being cached. Root cause: on first visit, TM results were fetched on-demand (prefetch skips TM for thread-safety) and cached; on revisit, the cache-hit path updated the Translation Results panel but never called `set_compare_panel_matches()` to update the Match Panel. The cache-hit path now converts cached `TranslationMatch` objects to the Match Panel format and displays them immediately. Resolves [#164](https://github.com/michaelbeijer/Supervertaler/issues/164).

---

## v1.9.272 - February 17, 2026

### Bug Fixes

- **Fixed Trados return package with missing `<target>` elements** – Trados Studio 2024 creates SDLXLIFF files where untranslated segments have no `<target>` element at all (not even an empty one). The export engine now detects this and creates `<target>` by cloning the `<seg-source>` structure with translations inserted into each `<mrk>` marker. Also handles the case where `<target>` exists but contains no `<mrk>` tags. Resolves [#161](https://github.com/michaelbeijer/Supervertaler/issues/161).
- **Fixed XML entity escaping in SDLXLIFF export** – Source text containing `&`, `<`, or `>` characters (e.g. "AT&T", "A<B") was written unescaped into the XML output, causing Trados Studio to reject the return package with "An error occurred while parsing EntityName". The `_markers_to_xml()` function now properly escapes XML entities in text content while preserving marker tags.
- **`conf="Translated"` attribute now added when missing** – The `<sdl:seg>` element in Trados SDLXLIFF sometimes lacks a `conf` attribute entirely (not just an empty value). Previously only existing `conf` values were updated; now the attribute is added if absent, ensuring Trados Studio recognizes segments as translated.
- **Grid-to-segment sync now reverses invisible characters** – The `_sync_grid_targets_to_segments()` function now calls `reverse_invisible_replacements()` before export, converting display characters (middle dots, arrows) back to actual whitespace. Also restores stripped outer wrapping tags when `hide_outer_wrapping_tags` is enabled.

### Improvements

- **Enhanced Trados export diagnostics** – The export pipeline now logs segment counts, translation counts, and warns when file content is unchanged after replacement (indicating translations may not have been inserted).

---

## v1.9.271 - February 16, 2026

### Bug Fixes

- **Termbase highlighting now appears immediately on segment navigation** – When navigating to a segment with cached termbase matches (the common path after the background batch processor runs), the source cell was not highlighted with glossary term formatting. The TermView panel showed the matches correctly, but the bold/colored highlighting in the source column was missing until an F5 refresh. Root cause: the cache-hit code path updated the TermView and Match Panel but skipped the `highlight_source_with_termbase()` call that the cache-miss path had.
- **Termbase highlighting now refreshes immediately after adding a term** – After adding a glossary term (Ctrl+E or Ctrl+Q quick-add), the source cell highlighting was not updated because the re-highlight code was commented out as a performance optimization. Replaced the expensive `rehighlight()` call with the lightweight `highlight_source_with_termbase()` which only applies termbase + NT formatting without re-running spellcheck or tag detection.
- **Green checkmark icons sized to match red X icons** – The confirmed-status checkmark (✔) was noticeably smaller than the not-started emoji (❌) because it used the archaic `<font size="2">` HTML attribute. Replaced with CSS `font-size:14px` and `line-height:1`, zeroed out vertical margins and label padding to prevent clipping on single-line rows.

---

## v1.9.270 - February 16, 2026

### New Features

- **Vertical sidebar navigation for Settings panel** – The 13 Settings sub-tabs are now displayed as a vertical sidebar list instead of horizontal tabs. Tab labels are no longer truncated on smaller screens or at larger font scales. The sidebar supports light/dark theme switching, hover/selected states, and respects the global UI font scale. All existing keyboard shortcut and programmatic navigation to Settings tabs continues to work unchanged. Resolves [#144](https://github.com/michaelbeijer/Supervertaler/issues/144).

---

## v1.9.269 - February 16, 2026

### Bug Fixes

- **Fixed Trados return package exporting empty SDLXLIFF** – The regex-based SDLXLIFF save engine assumed a fixed XML attribute order (`mtype` before `mid`) on `<mrk>` segment markers. If Trados Studio produced attributes in a different order, translations were silently dropped during export. The regex now uses lookaheads to match both `mtype="seg"` and `mid="N"` in any attribute position. Also fixed `<sdl:seg>` attribute matching for the same issue. Resolves [#161](https://github.com/michaelbeijer/Supervertaler/issues/161).
- **Unsegmented trans-unit support in SDLXLIFF export** – Trans-units without `<seg-source>` / `<mrk>` elements (rare but valid) now have their `<target>` content correctly replaced during export.
- **More robust segment ID extraction** – The SDLRPX export now iterates through all pipe-separated note fields to find the `Segment:` part, instead of assuming it's always at index 1. Matches the approach already used by standalone SDLXLIFF export.

### Improvements

- **Diagnostic logging for Trados export** – The SDLRPX export pipeline now logs translation dict size, handler match counts, and per-file segment/translation counts. When zero matches occur, sample segment IDs are logged for diagnosis.

---

## v1.9.268 - February 15, 2026

### Performance

- **Threaded TM fuzzy search** – TM fuzzy matching (SequenceMatcher on up to 500 FTS5 candidates) now runs in a background QThread instead of blocking the main UI thread. Eliminates beachball/freeze when navigating segments with very large TMs (1M+ entries). Previous searches are automatically cancelled when moving to a new segment.
- **Streaming TMX import** – Replaced `ET.parse()` (loads entire file into memory) with `ET.iterparse()` for streaming XML processing. Multi-GB TMX files now import without excessive memory usage. Processed elements are freed immediately.
- **Batch SQLite commits for TMX import** – New `add_translation_units_batch()` method commits chunks of 5,000 entries at once instead of one `commit()` per row. Dramatically reduces disk I/O for large imports.
- **Streaming language detection** – `detect_tmx_languages()` now uses `iterparse` and stops after 100 TUs instead of loading the entire TMX file just to find language codes.

---

## v1.9.267 - February 15, 2026

### New Features

- **Standalone SDLXLIFF import/export** – Translators can now import `.sdlxliff` files directly, without needing a Trados package (`.sdlppx`). Supports multi-file selection, full SDL status mapping (PM, CM, 100%, fuzzy, MT, repetitions), and round-trip export with BOM-preserving text-based replacement. Menu items added under File > Import/Export > Trados Studio. Resolves the most requested bilingual format gap.

### Improvements

- **Shared SDLXLIFF status mapping** – Extracted the 13-level SDL segment status mapper (`_map_sdlxliff_segment()`) into a reusable helper, shared by both SDLPPX package import and standalone SDLXLIFF import. Eliminates 50 lines of code duplication.
- **Refactored SDLXLIFF save engine** – The core save functions (`_markers_to_xml`, `_replace_target_content`, `_replace_seg_attributes`) are now module-level functions in `sdlppx_handler.py`, reusable by both the package handler and the new standalone handler.

---

## v1.9.266 - February 14, 2026

### Bug Fixes

- **Fixed empty segments still showing in grid** – Empty structural segments (from blank lines in TXT/MD imports) were appearing in the translation grid despite being marked as hidden. The root cause was that `_apply_pagination_to_grid()` overrode the hiding on every page change, filter, or grid reload. Empty segments are now consistently hidden across all visibility control paths: pagination, text filters, file filters, find/replace, empty targets filter, and advanced filters.

---

## v1.9.265 - February 14, 2026

### Improvements

- **Sentence segmentation for TXT/MD imports** – New "Split lines into sentences" checkbox in both single-file and multi-file import dialogs. When enabled, long lines are split into individual sentences for easier translation, and automatically rejoined on export. Setting is persisted between imports. Partially addresses [#149](https://github.com/michaelbeijer/Supervertaler/issues/149).
- **Markdown-aware sentence segmenter** – When importing `.md` files with sentence segmentation enabled, a specialized `MarkdownSegmenter` protects markdown constructs (links, images, code spans, bare URLs, HTML tags) from being incorrectly split. A subtle note in the import dialog informs users about this.

### Bug Fixes

- **Empty lines now always preserved in TXT/MD imports** – Removed the "Skip empty lines" checkbox. Empty lines are now always imported as structural segments (preserving file structure for export fidelity) but hidden from the translation grid. Previously, the multi-file import always stripped empty lines with no option – this is now fixed. Resolves [#149](https://github.com/michaelbeijer/Supervertaler/issues/149).
- **Navigation skips hidden rows** – Alt+Up/Down now correctly skips over hidden rows (empty structural segments, filtered rows) instead of landing on invisible segments.
- **Pre-translation skips empty segments** – Batch translation no longer wastes API calls on empty structural segments.
- **Multi-file TXT import now tracks line metadata** – `paragraph_id` and `document_position` are now set for TXT/MD files in multi-file imports, matching single-file import behavior.

---

## v1.9.264 - February 14, 2026

### Improvements

- **Direct Markdown (.md) export support** – When exporting a project that was imported from a `.md` file, the save dialog now defaults to Markdown format with the correct `.md` extension. Both `.md` and `.txt` are available as export options for all text-based projects. Resolves [#150](https://github.com/michaelbeijer/Supervertaler/issues/150).

---

## v1.9.263 - February 12, 2026

### Bug Fixes

- **Fixed voice command keystroke sending on Windows** – The `CrossPlatformKeySender.send_keystroke()` method had no Windows implementation (only Linux/pynput), so all voice commands of type "keystroke" silently did nothing. It now routes to AHK on Windows, matching the proven pattern used by `send_copy()`/`send_paste()`.
- **Fixed AHK v1/v2 syntax mismatch in voice commands** – All inline AHK voice commands (memoQ confirm, glossary, tag next, Trados confirm) used AHK v1 syntax (`Send, ^{Enter}`) but the script runner wrapped them in `#Requires AutoHotkey v2.0`, causing silent failures. Updated all defaults and saved commands to AHK v2 syntax (`Send "^{Enter}"`). Added a v1→v2 auto-converter as a safety net for user-created commands.
- **Added AutoFingers voice command support** – The "fingers" voice command now directly calls AutoFingers' `process_single_safe()` method as an internal handler, instead of trying to simulate the `Ctrl+Alt+P` hotkey via AHK (which was intercepted by Supervertaler's own global hotkey registration). Also registered `autofingers_loop` and `autofingers_stop` internal handlers.

---

## v1.9.262 - February 12, 2026

### Improvements

- **Model version checker now uses Anthropic's /v1/models API** – The "Check for New Models" feature previously used a hardcoded probe approach that tried guessing model names, which is why it never found anything new. It now uses the official Anthropic Models API endpoint (`/v1/models`) to dynamically discover all available models. Falls back to the old probe method if the SDK is too old.
- **Added Claude Opus 4.6** – Claude Opus 4.6 (released February 4, 2026) is now available as a model option in all model selection dropdowns, the Superbench benchmarking system, and the vision-capable models list.
- **Updated known Claude models list** – Added Claude Sonnet 4, Claude Opus 4, and Claude 3.5 Haiku to the known models list so the version checker correctly identifies them as existing models.

### Bug Fixes

- **Fixed incorrect Claude Opus 4.1 model ID** – The MTQL provider configuration had `claude-opus-4-1-20250924` (wrong date) instead of the correct `claude-opus-4-1-20250805`.

---

## v1.9.261 - February 12, 2026

### Performance

- **TM fuzzy matching completely rewritten for massive speedup** – Fuzzy matching during batch pre-translation now loads all TM candidates into memory with a single SQL query instead of running individual FTS5 queries per segment. A three-tier pre-filter cascade (length ratio → word overlap → `quick_ratio()`) eliminates most candidates before the expensive `SequenceMatcher.ratio()` is called. Result: fuzzy matching that previously took 5–7 minutes now completes in seconds.
- **Progress feedback during fuzzy matching** – Added `progress_callback` throughout the fuzzy matching pipeline (`database_manager` → `translation_memory` → main UI) with `processEvents()` every 10 segments, preventing "Not Responding" dialogs during large batch operations.

### Improvements

- **TM match status icons in grid** – After batch pre-translation, segments now show proper status icons: ✅ for exact TM matches (100%) and 🔶 for fuzzy TM matches (<100%), with the match percentage displayed next to the icon. Previously all pre-translated segments showed ❌ (not started).
- **Match Panel shows batch TM results** – Clicking a pre-translated segment now immediately displays the TM source/target match in the Match Panel. The batch match data is stored on each segment and displayed without waiting for a background TM lookup.
- **Compact status column layout** – Reduced spacing between status icon and match percentage for a cleaner, less distracting grid appearance.

### Bug Fixes

- **Match Panel no longer overwrites batch TM data** – Fixed an issue where the background TM lookup (`_schedule_mt_and_llm_matches`) would overwrite the Match Panel's batch match data with empty results. Segments with batch TM match data now skip the redundant background lookup.

---

## v1.9.260 - February 12, 2026

### Performance

- **TM batch pre-translation is up to 30× faster** – Batch pre-translation from Translation Memory has been completely rewritten to use batch SQL queries instead of per-segment lookups. A 912-segment document that previously took ~2 minutes now completes in ~4 seconds (exact match mode) or ~12 seconds (fuzzy matching mode). Key optimizations:
  - **Batch exact matching** – All source-text hashes are pre-computed and looked up in a single SQL query with `IN` clause, replacing 900+ individual queries. Language variant filters are computed once instead of per segment.
  - **Single commit** – Usage-count updates are batched into a single `COMMIT` instead of one per match, eliminating fsync overhead.
  - **Source deduplication** – Repeated segments (headers, footers, boilerplate) are looked up only once and the result is applied to all duplicates.
  - **Batch fuzzy matching** – Exact matches are found first (via batch), then only unmatched segments go through fuzzy matching with pre-computed language filters and pre-compiled tag-stripping regex.
  - **Reduced UI overhead** – `QApplication.processEvents()` is now called every 20 segments instead of every segment; grid updates are deferred until the batch completes.
  - All three TM code paths optimized: primary batch dialog, TM pre-check before API calls, and secondary TM-only path.

### Improvements

- **Batch translate dialog labels clarified** – The TM checkbox now reads "Pre-translate from activated TMs (exact + fuzzy ≥75%)" and the exact-only sub-option reads "Exact matches only (100% matches only – fastest)" to make the distinction between fuzzy and exact matching modes clearer.

---

## v1.9.259 - February 12, 2026

### Bug Fixes

- **Editing confirmed segments now always resets status** – Fixed a bug where certain edits to a confirmed (or T&R confirmed / proofread / approved) segment did not reset the status back to "translated". Two sub-issues were resolved:
  - **Copy Source to Target** (Ctrl+Shift+S, bulk menu, and grid context button) now correctly resets confirmed-like statuses. Previously, these code paths used `blockSignals()` or set the segment data before the widget update, both of which bypassed the change-detection handler. ([#154](https://github.com/michaelbeijer/Supervertaler/issues/154))
  - **All confirmed-like statuses covered** – The status-reset check now covers `confirmed`, `tr_confirmed`, `proofread`, and `approved` (previously only `confirmed` was checked), so edits to segments in any of these higher statuses are properly detected. ([#154](https://github.com/michaelbeijer/Supervertaler/issues/154))
- **Fixed `.strip()` mismatch between confirm and edit paths** – The Ctrl+Enter confirm handler and bulk-confirm sync previously applied `.strip()` to the target text before storing it, while the text-change handler compared against the unstripped widget text. This could cause false status resets or missed resets. Both paths now store the text consistently without stripping.

---

## v1.9.258 - February 12, 2026

### Bug Fixes

- **Title bar now shows correct version when installed via pip** – Fixed the version display showing a stale "v1.9.227" in the title bar (and About dialog) when running a pip-installed copy. The version reader now falls back to `importlib.metadata` when `pyproject.toml` is not present (i.e. pip installs), so the title bar correctly reflects the installed package version. ([#157](https://github.com/michaelbeijer/Supervertaler/issues/157))

---

## v1.9.257 - February 12, 2026

### Bug Fixes

- **Batch translation with Custom OpenAI provider now works** – Fixed a bug where batch pre-translation (Translate All / Translate Empty) failed with *"Please configure your Custom_Openai API key in Settings"* even though the API key was correctly configured in the custom profile. Single-segment translation (Ctrl+T / Cmd+T) was not affected. The batch code path now correctly reads the API key from the active custom endpoint profile instead of requiring a top-level `api_keys.txt` entry. ([#157](https://github.com/michaelbeijer/Supervertaler/issues/157))

---

## v1.9.256 - February 11, 2026

### New Features

- **`{{TARGET_TEXT}}` placeholder in Prompt Manager** – A new placeholder is now available in the Placeholders tab. Use `{{TARGET_TEXT}}` in your prompts to reference the current segment's existing translation. Ideal for review/proofreading prompts, e.g. *"Review this translation: {{SOURCE_TEXT}} → {{TARGET_TEXT}}"*. Works in system prompts, library prompts, and QuickMenu custom prompts. Empty string if the segment has no translation yet.

---

## v1.9.255 - February 11, 2026

### Improvements

- **Copy Source to Target now works on selected segments** – The Ctrl+Shift+S shortcut (Cmd+Shift+S on macOS) now supports multi-segment selection. Select multiple segments (Ctrl+Click or Shift+Click), press the shortcut, and source text is copied to target for all selected segments at once. Single-segment behavior is unchanged. ([#141](https://github.com/michaelbeijer/Supervertaler/issues/141))

---

## v1.9.254 - February 11, 2026

### Bug Fixes

- **Settings: macOS global hotkey labels corrected** – The Keyboard Shortcuts settings tab and QuickTrans settings now display the correct ⌃⌘L / ⌃⌘M (Ctrl+Cmd) instead of the old ⌘⌥L / ⌘⌥M (Cmd+Option) on macOS.

---

## v1.9.253 - February 11, 2026

### Improvements

- **macOS global hotkeys: Ctrl+Cmd+L / Ctrl+Cmd+M** – Global hotkeys for Superlookup and QuickTrans now use ⌃⌘L and ⌃⌘M on macOS (previously tried to register Ctrl+Alt which didn't work). Uses pynput with the correct key mapping. The in-app Superlookup shortcut also updated to ⌃⌘L on Mac.
- **macOS Accessibility permission guidance** – When global hotkeys fail to register on macOS, Supervertaler now prints a clear message directing users to grant Accessibility permission in System Settings → Privacy & Security → Accessibility.
- **Fixed Meta symbol display on macOS** – The `Meta` modifier now correctly displays as ⌃ (Control) instead of ⌘ (Command) in shortcut labels.

---

## v1.9.252 - February 11, 2026

### New Features

- **QuickTrans (Ctrl+Alt+M) now pastes translation at cursor** – When invoking QuickTrans from an external app (browser, Trados, text editor, etc.) and selecting a translation (1-9 or Enter), the selected translation is now copied to the clipboard AND pasted at the cursor position in the original app, replacing the selected text. Works cross-platform: AHK/PowerShell on Windows, osascript on macOS, pynput on Linux.

### Bug Fixes

- **QuickTrans no longer flashes taskbar icon** – The QuickTrans popup now uses a Tool window type when invoked via the global hotkey, preventing the Supervertaler taskbar icon from flashing on Windows.
- **Trados bilingual review: self-closing tags now exported correctly** – Self-closing tags like `<255/>` (standalone elements such as page breaks) were written as plain text instead of with the Tag character style when exporting to Trados bilingual DOCX. Trados flagged these as tag errors on re-import. The tag regex now matches all three tag forms: opening (`<11>`), closing (`</11>`), and self-closing (`<255/>`).
- **Superlookup (Ctrl+Alt+L) now brings window to foreground** – When pressing Ctrl+Alt+L from another application (e.g. Trados Studio), the Supervertaler window now reliably comes to the foreground instead of just flashing in the taskbar. Uses platform-native window activation: `AttachThreadInput` + `SetForegroundWindow` on Windows, `osascript` on macOS, `wmctrl`/`xdotool` on Linux.

---

## v1.9.250 - February 10, 2026

### Bug Fixes

- **TM and Glossary preselection fixed for new projects** – When creating a new project (via any import method, including Trados bilingual DOCX), TMs and glossaries from the previous project were still shown as selected in the UI. The database was correctly deactivated but the TM/Glossary tab checkboxes were not refreshed. Now properly refreshes both panels after deactivation.

### Improvements

- **Trados Bilingual Review dialog updated** – Improved preparation instructions: replaced incorrect Ctrl+A shortcut (which doesn't work in Trados) with proper segment selection method (click first segment number, Shift+click last). Added new step explaining that Supervertaler's exported file must be renamed to match the original Trados export name for re-import.

---

## v1.9.249 - February 10, 2026

### Improvements

- **Trados return package now matches Studio output** – Compared against a genuine Trados Studio return package and aligned Supervertaler's output:
  - **Byte-perfect SDLXLIFF preservation** – Switched from ElementTree round-trip to text-based regex replacement. Preserves UTF-8 BOM, double-quote XML declaration, original namespace prefixes, and all whitespace exactly as in the original file. Only `<target>` content and `sdl:seg` attributes are modified.
  - **Source language SDLXLIFF included** – Return package now includes the `en-gb/` source SDLXLIFF (unchanged) alongside the translated `nl-nl/` target, matching Studio's structure.
  - **CreatedBy scoped correctly** – `PackageCreatedBy` is updated without clobbering `ManualTask.CreatedBy`, `FileVersion.CreatedBy`, and other unrelated `CreatedBy` attributes in the `.sdlproj`.
  - **AutomaticTask and TermbaseConfiguration stripped** – Removed from `.sdlproj` as Studio does, keeping return packages lean.

---

## v1.9.248 - February 10, 2026

### Bug Fixes

- **Trados return package (SDLRPX) overhaul** – Fixed four issues with return package generation:
  - **Inline tags corrupted on export** – `<g>` formatting tags (e.g. superscript ¹⁷⁷Lu) were escaped as literal text (`&lt;14&gt;177&lt;/14&gt;`) instead of proper XML elements. Tags are now correctly reconstructed, including alphanumeric IDs like `qSuperscript`.
  - **PackageType wrong** – `.sdlproj` was copied verbatim with `PackageType="ProjectPackage"` instead of `"ReturnPackage"`. Now correctly sets ReturnPackage, updates timestamps, CreatedBy, ConfirmationStatistics (Draft→Translated), and marks the ManualTask as Completed.
  - **Extra files in package** – Source language folder (`en-gb/`) and `Reports/` were included. Return packages now only contain `.sdlproj` + target language SDLXLIFF.
  - **Segment origin not updated** – Translated segments now get `origin="interactive"` with stale TM/MT attributes removed.

---

## v1.9.247 - February 10, 2026

### Bug Fixes

- **Trados/SDLXLIFF tag insertion (Ctrl+,) fixed** – The "Insert next tag" shortcut now recognizes Trados Studio numeric tags (`<92>`, `</92>`, etc.) from imported SDLXLIFF packages. Previously the tag extraction regexes only matched letter-starting tags (`<b>`, `</i>`) and silently ignored numeric tags. Wrapping selected text with Trados tag pairs also works now.

---

## v1.9.246 - February 9, 2026

### Bug Fixes

- **memoQ bilingual RTF: missing segments fixed** – Segments containing mqInternal inline tags (e.g. `[1]`, `[1}...{2]` paired tags) were silently dropped during import. The row-matching regex now uses brace-aware matching to handle nested RTF brace groups. Previously 11 of 155 segments were lost in affected files.
- **memoQ bilingual RTF: formatting preserved on export** – Bold, italic, and underline formatting is now correctly converted back to RTF control words (`\b`, `\i`, `\ul`) when exporting translations. Previously these appeared as literal `<b>`, `<i>`, `<u>` text when re-imported into memoQ.

---

## v1.9.245 - February 9, 2026

### New Features

- **Cross-platform support**: Added `modules/platform_helpers.py` with cross-platform utilities for file opening, subprocess flags, global hotkeys, and keystroke automation. Supervertaler now runs on macOS and Linux in addition to Windows.
- **Native global hotkey system**: Replaced the AutoHotkey-based hotkey system with a native implementation – Windows `RegisterHotKey` API with AHK-based keystroke injection for Ctrl+C, pynput `GlobalHotKeys` on macOS/Linux. No more signal file watcher or polling timer.
- **Global Hotkeys settings moved**: The Global Hotkeys (Superlookup & QuickTrans) settings section has moved from the General tab to the Keyboard Shortcuts tab.

### Improvements

- **Cross-platform file opening**: Replaced all unguarded `os.startfile()` calls with `platform_helpers.open_file()` (uses `open` on macOS, `xdg-open` on Linux).
- **Cross-platform subprocess flags**: Guarded all `subprocess.CREATE_NO_WINDOW` usage behind platform checks so voice commands and other subprocess calls work on macOS/Linux.
- **Renamed "Universal Lookup" to "Superlookup"** in the Keyboard Shortcuts action column, sidebar, and shortcut cheatsheet for consistency.

### Dependencies

- Added `pynput>=1.7.6` as a cross-platform dependency for global hotkeys and keystroke automation.

---

## v1.9.244 - February 9, 2026

### Bug Fixes

- **Add to Glossary defaults:** The "Save to Glossary(s)" checklist now defaults to active glossaries for the current project instead of stale previous selections.
- **Immediate Termview update after glossary add:** After Add/Quick Add, the in-memory termbase cache and index are rebuilt before refresh so newly added terms appear immediately in Termview.

---

## v1.9.243 - February 9, 2026

### Bug Fixes

- **Glossary Termview refresh (Match Panel):** Fixed a regression where adding terms via "Add to Glossary" or "Quick Add" saved correctly but did not immediately refresh Termview in the Match Panel. Refresh paths now update when either Termview widget is available.

---

## v1.9.242 - February 8, 2026

### UI Improvements

- **macOS shortcut labels**: Normalized user-facing shortcut text to native macOS symbols (⌘, ⌥, ⇧) across context menus, tooltips, insert hints, and shortcut settings displays.
- **Branding consistency**: Restored **QuickTrans** and **QuickMenu** capitalization across user-facing UI and documentation.

---
## v1.9.241 - February 8, 2026

### Bug Fixes

- **QuickTrans LLM API keys on macOS/custom data paths**: Fixed QuickTrans incorrectly reporting missing API keys for Claude, OpenAI, and Gemini when keys were present in the configured user data location. QuickTrans now uses the app's unified key loader (`parent_app.load_api_keys()`), supports Gemini `google`/`gemini` aliases consistently, and reuses shared LLM client wiring.
- **LLM key path resolution fallback**: Updated `modules/llm_clients.load_api_keys()` to read the cross-platform config pointer and resolve keys from the actual configured user data folder (`settings/settings.json`), with legacy fallbacks retained.

---

## v1.9.240 - February 8, 2026

### New Features

- **Unified settings system**: Merged `general_settings.json`, `ui_preferences.json`, `feature_settings.json`, and `api_keys.txt` into a single `settings/settings.json` file. All settings files (themes, shortcuts, find/replace history, etc.) are now organized in a `settings/` subfolder within the user data folder. Existing installations are automatically migrated on first launch – old files are renamed to `.migrated` as a safety net.
- **Inline API key editing**: API keys can now be entered directly in the Settings UI instead of editing a text file. AI provider keys (OpenAI, Claude, Google/Gemini, Ollama endpoint) are in the AI Settings tab; machine translation keys (Google Translate, DeepL, Microsoft, Amazon, ModernMT, MyMemory) are in the MT Settings tab. All key fields are password-masked with a show/hide toggle.

---

## v1.9.239 - February 8, 2026

### 🐛 Bug Fixes

- **macOS: tabs now left-aligned**: Fixed all tab bars (main tabs, settings sub-tabs, resource tabs, etc.) being centered/expanded on macOS. Qt's native `QMacStyle` ignores `setExpanding(False)`, so the app now uses the Fusion style on macOS which respects all Qt widget properties while keeping the app's custom stylesheets intact.
- **macOS: combo box dropdowns no longer blank**: Fixed a macOS Qt issue where QComboBox popup menus (pagination "Per page", language selectors, model dropdowns, etc.) rendered with blank/empty item text. The Fusion style renders combo items with Qt's own paint code instead of the native NSMenu.

---

## v1.9.237 - February 7, 2026

### ✨ New Features

- **Custom OpenAI-Compatible API profiles**: The custom provider now supports named profiles, allowing users to save multiple endpoint configurations (e.g., "Volcengine Doubao", "DeepSeek", "My Local vLLM") and switch between them from a dropdown in Settings > AI Settings. Each profile stores its own endpoint URL, model name, and API key (password-masked). Existing single-endpoint configurations are automatically migrated. The `api_keys.txt` `custom_openai` key still works as a fallback.

---

## v1.9.236 - February 7, 2026

### ✨ New Features

- **Custom OpenAI-Compatible API provider** ([#155](https://github.com/michaelbeijer/Supervertaler/issues/155)): Added support for any OpenAI-compatible API endpoint – Volcengine (Doubao), Alibaba Tongyi (Qwen), DeepSeek, Mistral, Groq, and more. Configure endpoint URL, model name, and API key in Settings > AI Settings. Works for single-segment translation, batch translation, and QuickTrans.

---

## v1.9.235 - February 7, 2026

### 🐛 Bug Fixes

- **Version display stuck at 1.9.227 for pip-installed users**: Fixed `_read_version()` to use `importlib.metadata` as a fallback when `pyproject.toml` is not present (pip wheels don't include it). Previously, all pip-installed users saw version 1.9.227 regardless of the actual installed version.

---

## v1.9.234 - February 7, 2026

### ✨ New Features

- **Multi-file export: "Original Format" option**: The multi-file export dialog now defaults to "Original Format", which exports each file back to its source format (`.txt`, `.md`, or `.docx`). This is especially useful for mixed-format projects where different files were imported with different types.

---

## v1.9.233 - February 7, 2026

### 🐛 Bug Fixes

- **Batch pre-translation: termbase glossary injection crashed with SQLite thread error**: Fixed "SQLite objects created in a thread can only be used in that same thread" error during batch translation. The worker thread was calling back to the main thread's SQLite connection to fetch AI-inject glossary terms. Terms are now pre-fetched on the main thread and passed to the worker.

---

## v1.9.232 - February 7, 2026

### ✨ New Features

- **Saved Views for multi-file projects**: Create named views that filter the translation grid to show only selected files. Views persist in the project file and are accessible from the file filter dropdown. Manage Views dialog allows creating and deleting views.
- **File boundary separators**: Multi-file projects now display a blue separator line between segments from different files, making it easy to see where one file ends and the next begins.
- **Markdown (.md) support in multi-file import/export**: Multi-file folder import now recognizes `.md` files alongside `.docx` and `.txt`. Multi-file export adds a Markdown format option that preserves syntax.

### 🔧 Improvements

- **Tabbed Project Info dialog**: The Project Info dialog now uses a tabbed layout with Overview and File Progress tabs. The standalone File Progress dialog has been merged in – click the file count in the status bar to jump directly to the File Progress tab.

---

## v1.9.231 - February 7, 2026

### 🐛 Bug Fixes

- **memoQ Bilingual RTF: Quotation marks, Unicode characters, and special characters lost during import**: Fixed a bug where Unicode escapes (`\uc0\u8220`, `\uc0\u8221`, etc.) and RTF character control words (`\ldblquote`, `\rdblquote`, etc.) were stripped by the generic control word cleanup regex, causing quotation marks to disappear and adjacent text to concatenate (e.g. `"de" en "het" refereren` became `deen hetrefereren`). All Unicode escapes, hex escapes, and named character control words are now decoded before the generic strip.
- **memoQ Bilingual RTF: Combined formatting (bold+underline) not extracted for headings**: The pair-matching regex `[^\\]+` could not match text containing other formatting control words, so segments with combined `\b \ul TEXT\b0 \ul0` (e.g. headings) lost all formatting. Replaced with direct marker-to-tag conversion that handles any combination of bold, italic, and underline.
- **memoQ Bilingual RTF: Missing formatting options dialog**: The RTF import was skipping the "memoQ Bilingual Import Options" dialog (Ignore inline formatting / Smart formatting transfer) that the DOCX import shows. The dialog is now shown for RTF imports as well, and the tag view is auto-enabled when smart formatting is selected.

---

## v1.9.228 - February 7, 2026

### 🐛 Bug Fixes

- **TM Read/Write settings now persist correctly across restarts** (#143): Fixed a bug where TM activation (Read checkbox) and write status (Write checkbox) could revert to wrong values after closing and reopening a project. The root cause was stale global TM activations overriding project-specific settings. Project-specific TM settings now always take priority over global defaults.

## v1.9.227 - February 7, 2026

### 🎨 UI Improvements

- **Settings Panel Reorganized** : The "AI Translation Preferences" settings section has been reorganized with clear sub-headings: *Single-Segment Translation*, *Batch Translation*, *Translation Memory*, and *Document Context*. Previously these were mixed together without clear grouping.
- **TM Check Label Corrected**: The "Check TM before AI translation" setting was incorrectly labeled as applying only to single-segment translation. It actually applies to both single-segment and batch translation, and the label now reflects this.

### 🔧 Internal

- **Version auto-read from pyproject.toml**: `__version__` in Supervertaler.py now reads automatically from `pyproject.toml`, eliminating a manual version sync step.

---

## v1.9.226 - February 6, 2026

### 🐛 Bug Fixes

- **Import Dialogs Ignore Saved Language Pair** ([#143](https://github.com/michaelbeijer/Supervertaler/issues/143)): Fixed the Text/Markdown import dialog and Folder/multi-file import dialog always defaulting to English → Dutch, ignoring any previously saved language pair. Both dialogs now read and save the last used language pair from `general_settings.json`, matching the existing DOCX import behavior. All three import dialogs now share the same language memory.

---

## v1.9.225 - February 6, 2026

### 🐛 Bug Fixes

- **WYSIWYG/Tags Toggle Corrupts Target Text** ([#142](https://github.com/michaelbeijer/Supervertaler/issues/142)): Fixed the WYSIWYG/Tags view mode toggle permanently destroying whitespace and indentation in target text. The root cause was that `get_formatted_html_display()` generated HTML without whitespace preservation, so Qt's HTML renderer collapsed spaces and indentation. Switching back to Tags mode then showed the corrupted text. Fix: added `white-space: pre-wrap` CSS to the HTML output, and added `_suppress_target_change_handlers` guard during display mode refresh to prevent any edge-case data corruption.

---

## v1.9.224 - February 6, 2026

### 🐛 Bug Fixes

- **Per Page Dropdown Empty on macOS** ([#136](https://github.com/michaelbeijer/Supervertaler/issues/136)): Fixed the "Per page" dropdown in the grid toolbar appearing empty on macOS. The dropdown popup (`QComboBox QAbstractItemView`) had no explicit styling, causing macOS to render text as invisible against the platform-default background. Added explicit background, text color, and selection styling for dropdown popups.

---

## v1.9.223 - February 6, 2026

### ✨ New Features

- **memoQ Bilingual RTF Support** ([#145](https://github.com/michaelbeijer/Supervertaler/issues/145)): Added full import/export support for memoQ bilingual RTF files. This enables users with older memoQ versions (or those who prefer RTF) to use the same bilingual table workflow as DOCX. The RTF format uses the identical 5-column structure (ID, Source, Target, Comment, Status) as DOCX.
  - New menu options: File → Import → memoQ Bilingual Table (RTF)
  - New menu options: File → Export → memoQ Bilingual Table - Translated (RTF)
  - Preserves formatting (bold, italic, underline) from source segments
  - Handles Unicode text and special characters correctly

---

## v1.9.222 - February 5, 2026

### 🐛 Bug Fixes

- **"Exact matches only" Not Finding 100% Matches** ([#140](https://github.com/michaelbeijer/Supervertaler/issues/140)): Fixed batch TM pre-translation with "Exact matches only" option not finding 100% matches even when they exist. The bug was caused by `TMDatabase.get_exact_match()` returning only the target text (string) while the batch translation code expected a dictionary. Now returns the full match dictionary, allowing exact match mode to work correctly.

---

## v1.9.221 - February 5, 2026

### 🐛 Bug Fixes

- **TM Not Working After Re-import** ([#140](https://github.com/michaelbeijer/Supervertaler/issues/140)): Fixed Translation Memory not working after re-importing the same document. The root cause was that re-importing created a new project with a new ID, orphaning the TM activation records from the previous project ID. Now when re-importing the same file, users are prompted with a dialog offering two options:
  - **"Re-import into Current Project"**: Preserves the project ID and TM/glossary settings, ensuring TM continues to work
  - **"Create New Project"**: Creates a fresh project with clean slate (no TMs/glossaries pre-selected)

### ✨ Enhancements

- **Re-import Confirmation Dialog**: Added user-friendly confirmation dialog when importing a file that's already loaded in the current project. Applies to all import handlers: DOCX, memoQ bilingual, memoQ XLIFF, CafeTran, Trados, and TXT/Markdown.

---

## v1.9.220 - February 5, 2026

### 🐛 Bug Fixes

- **Reverted Auto-Activation of TMs**: Reverted incorrect fix from v1.9.219 that auto-activated all TMs when importing documents. The intended design is that new projects start with a **clean slate** - no TMs or glossaries pre-selected. Users should explicitly choose which resources to use for each project.

---

## v1.9.219 - February 5, 2026

### 🐛 Bug Fixes

- **Status Tooltip Rendering**: Fixed status icon tooltips appearing as black rectangles. Qt's built-in tooltip system was being affected by transparent widget backgrounds and theme stylesheets. The fix uses a custom QLabel popup widget with explicit styling (#f5f5f5 background, #333333 text), completely bypassing Qt's QToolTip system.

---

## v1.9.217 - February 4, 2026

### 🐛 Bug Fixes

- **Status Tooltip Rendering (partial)**: Attempted fix for black tooltip issue using `QToolTip.showText()`. This approach was insufficient; see v1.9.218 for the complete fix.

---

## v1.9.216 - February 4, 2026

### ✨ Enhancements

- **Status Icon Tooltips**: Hovering over status icons in the grid now displays tooltips showing the status name (e.g., "Not started", "Translated", "Confirmed"). Match percentage badges also show tooltips with details like "Context match from memoQ (101% or better)" or "Exact match from memoQ (100%)".

- **Standardized "Not started" Terminology**: The Quick Filter menu now uses "Not started" instead of "Not translated" to match the terminology used elsewhere in the application (Grid, Advanced Filters, menus). This aligns with memoQ's terminology and provides consistency throughout the UI.

---

## v1.9.215 - February 4, 2026

### 🐛 Bug Fixes

- **Quick Filters Not Working on macOS** ([#137](https://github.com/michaelbeijer/Supervertaler/issues/137)): Fixed Quick Filters (Empty segments, Not translated, Confirmed, Locked, Not locked, Commented) not working properly on macOS. The issue was that quick filters weren't integrating with the pagination system, causing any UI refresh event to override the filter. Now quick filters use the same mechanism as text filters, ensuring they persist correctly across all platforms. Additionally, the segment count label now shows the filtered count (e.g., "Showing 42 of 430 segments") when a filter is active.

---

## v1.9.214 - February 3, 2026

### ✨ Enhancements

- **DOCX Export Language Setting**: Exported target DOCX documents now have the correct language setting based on the project's target language. Previously, exported documents defaulted to American English regardless of the translation language pair. Now, when exporting EN→NL translations, the document language is correctly set to Dutch (Netherlands), enabling proper spellcheck and proofing in Word and other applications.

---

## v1.9.213 - February 3, 2026

### 🐛 Bug Fixes

- **Wrapping Tags in TM and TMX Export**: Fixed structural wrapping tags (e.g., `<li-b>`, `<li-o>`, `<p>`, `<td>`) being saved to Translation Memory and exported TMX files even when "Hide outer wrapping tags" was enabled in settings. All TM save and TMX export functions now properly strip these tags when the setting is active, improving TM leverage and ensuring cleaner exports.

---

## v1.9.212 - February 3, 2026

### 🐛 Bug Fixes

- **QuickTrans Project Languages**: Fixed QuickTrans not using the current project's source/target languages for MT and LLM providers. Now directly reads from `current_project.source_lang` and `current_project.target_lang` when a project is open, ensuring the correct language pair is sent to all translation providers.

---

## v1.9.211 - February 3, 2026

### 🐛 Bug Fixes

- **Batch Translate: Exact Matches Only**: Fixed the "Exact matches only" option in Batch Translate not finding any TM matches. The issue was that exact matching used hash comparison, but the hash didn't account for HTML/XML tags that may differ between stored TM entries and the search text. Now the exact match function tries multiple hash variants (with/without tags, normalized) to reliably find matches.

---

## v1.9.210 - February 3, 2026

### 🐛 Bug Fixes

- **Alt+0 TM Insert Shortcut**: Fixed the Alt+0 shortcut not inserting the TM target from the Match Panel. The shortcut previously only worked when the Match Panel tab was selected. Now it works regardless of which right panel tab is active, as long as there's a valid TM match displayed.

---

## v1.9.209 - February 3, 2026

### 🐛 Bug Fixes

- **TM/Glossary Auto-Selection on Startup**: Fixed an issue where Translation Memories and Glossaries were being auto-selected (both Read and Write checkboxes) when starting the application without a project open. Now when no project is loaded, all TMs and glossaries remain unselected as expected.

---

## v1.9.208 - February 3, 2026

### 🐛 Bug Fixes

- **DOCX Import: Hyperlink Text Missing**: Fixed an issue where text inside hyperlinks was being stripped during DOCX import. The `paragraph.text` property in python-docx doesn't include text from `w:hyperlink` elements. Now both `TagManager.extract_runs()` and `DOCXHandler.import_docx()` properly extract text from hyperlink elements, ensuring URLs and linked text are preserved.

---

## v1.9.207 - February 3, 2026

### 🔧 Maintenance

- **Version Sync**: Synchronized version number across pyproject.toml, Supervertaler.py, and PyPI package.

---

## v1.9.206 - February 3, 2026

### 🐛 Bug Fixes

- **AI Assistant Prompt Generation**: Fixed "Analyze Project & Generate Prompts" outputting placeholder text like `[Source Language]` and `[Translation]` instead of actual values. The template now pre-fills the actual language pair and segment count from project settings, with explicit instructions to not use placeholders.

---

## v1.9.205 - February 3, 2026

### 🐛 Bug Fixes

- **CRITICAL: Project Segments Corruption Fixed**: Fixed a critical bug where importing a new document would save the OLD project's segments instead of the newly imported ones. This affected all import functions (DOCX, text, memoQ, Trados, SDL package, Phrase, CafeTran, Déjà Vu). The issue was that `_original_segment_order` wasn't being updated after imports, causing the save function to overwrite new segments with old ones.

- **QuickTrans Language Mapping**: Fixed QuickTrans not respecting project source/target language settings. Machine translation providers (MyMemory, DeepL, Microsoft, ModernMT) now correctly map language names like "English" and "Dutch" to ISO codes.

- **Focus Rectangles Removed**: Fixed dotted focus rectangles appearing on buttons throughout the application. Added global stylesheet rules in theme manager and individual button fixes where custom stylesheets were used.

### ✨ Improvements

- **AI Assistant TM/Termbase Integration**: The "Click to include TM data" and "Click to include termbase data" buttons in the AI Assistant are now functional. Clicking toggles inclusion of TM matches and termbase entries in AI context.

- **AI Actions Error Messages**: Enhanced error messages for AI action debugging, showing received parameters when prompt creation fails.

---

## v1.9.204 - February 3, 2026

### 🐛 Bug Fixes

- **DOCX Export: Font Preservation**: Fixed an issue where exported DOCX files would lose the original font name (e.g., Verdana) and font size (e.g., 10pt). The export now correctly preserves font properties from the original document.

- **DOCX Export: All Caps Preservation**: Fixed an issue where text with the "All Caps" font effect (like patent titles) would export in normal case instead of uppercase. The `all_caps` font property is now preserved.

- **DOCX Export: Subscript/Superscript Support**: Fixed an issue where `<sub>` and `<sup>` formatting tags were not being processed during export. Text like "CO₂" now correctly exports with subscript formatting.

- **DOCX Export: Partial Replacement Formatting**: Fixed an issue where paragraphs containing multiple segments would lose all inline formatting (bold, italic, subscript, superscript) during export. Partial replacements now properly apply formatting tags.

### ✨ Improvements

- **Export Menu Reorganization**: Moved monolingual export formats (Target Only DOCX, Simple Text, AI-Readable Markdown) to the top of the Export menu with a separator, making them easier to find.

---

## v1.9.203 - February 3, 2026

### 🐛 Bug Fixes

- **Grid Row Height Stability**: Fixed an issue where row heights would become inconsistent ("messed up") after various actions like Ctrl+Enter confirmation, Clear Filters, or page navigation. Row heights are now properly recalculated:
  - After status cell updates (Ctrl+Enter confirmation)
  - After pagination/filter changes (sync + deferred resize)
  - After initial grid load (deferred resize for proper column widths)
  - After column resize (debounced handler for text reflow)

### ⚡ Performance Improvements

- **Faster Ctrl+Enter Navigation**: Removed wasteful MT/LLM API calls that were happening on every segment navigation. Machine translation providers (Google Translate, DeepL, Amazon, etc.) and LLM providers were being called even when the Translation Results panel was hidden. Now MT/LLM is only available via QuickTrans (Ctrl+M).

- **Removed Translation Results Panel**: The deprecated Translation Results panel has been removed from the UI. This panel was hidden by default with no way to enable it, but was still consuming resources. TM matching continues to work via the Match Panel, and MT/LLM translations are available via QuickTrans (Ctrl+M).

---

## v1.9.202 - February 2, 2026

### ✨ New Features

- **Clean TM Storage**: When "Hide outer wrapping tags" is enabled, structural tags like `<li-o>`, `<p>`, etc. are now stripped when saving to TM and when searching TM. This ensures better matching leverage - a segment `<li-o>Hello world</li-o>` will match a TM entry `Hello world` at 100%.

### 🐛 Bug Fixes

- **Find-and-Replace Tag Display**: Fixed an issue where outer wrapping tags would reappear in the grid after find-and-replace operations. All cell update operations now respect the "Hide outer wrapping tags" setting.

- **Preview Panel List Numbering**: Fixed claims/numbered lists starting at wrong number (e.g., claim #1 showing as "2." instead of "1."). The list counter now properly resets when starting a new list.

- **Preview Panel Line Breaks**: Ordered list items (like patent claims) now have proper paragraph-like spacing instead of running together.

- **Preview Section Headings**: Bold-wrapped section headings (like "CLAIMS", "TECHNICAL FIELD", "PRIOR ART") are now properly detected as headings with double line breaks before/after and bold formatting.

---

## v1.9.198 - February 2, 2026

### ✨ New Features

- **Hide Outer Wrapping Tags - Complete Implementation**: The "Hide outer wrapping tags" feature now works on **both Source and Target columns**. Tags are automatically restored when saving, making the process completely transparent to the translator.

- **WYSIWYG List Numbering & Bullets**: When "Hide outer wrapping tags" is enabled, list items now display with visual prefixes:
  - Ordered lists (`<li-o>`) show: "1. ", "2. ", "3. ", etc.
  - Unordered lists (`<li-b>`, `<li>`) show: "• "
  - These prefixes are display-only and don't affect the saved data

---

## v1.9.197 - February 2, 2026

### ✨ New Features

- **Hide Outer Wrapping Tags in Grid**: Added new option in View Settings → Grid Display Options to hide structural tags that wrap entire segments (like `<li-o>`, `<p>`, `<td>`). Since the segment type is shown in the Type column, these outer tags are redundant and can now be hidden for a cleaner grid display. Inner formatting tags like `<b>bold</b>` are preserved.

### 🐛 Bug Fixes

- **View Settings Now Preserves All Settings**: Fixed a bug where saving View Settings would overwrite unrelated settings in `general_settings.json`. Settings are now properly merged instead of replaced.

### 🏗️ UI Improvements

- **Match Panel Section Rename**: Renamed "Translation Results Pane & Tag Colors" to "Match Panel & Tag Colors" in View Settings to reflect the current UI terminology.

---

## v1.9.196 - February 2, 2026

### 🐛 Bug Fixes

- **AI Assistant "Analyze Project" Fixed** ([#132](https://github.com/michaelbeijer/Supervertaler/issues/132)): Fixed a bug where clicking "Analyze Project & Generate Prompts" in the AI Assistant would fail with "AI responded but no actions were found". The issue was that the LLM wasn't receiving a system prompt explaining the ACTION format. Now all providers (OpenAI, Claude, Gemini, Ollama) receive proper instructions on how to format their responses.

### 🎨 UI Improvements

- **Supervoice Settings Two-Column Layout**: Reorganized the Supervoice settings tab into a two-column layout. Voice commands table now displays on the right side, allowing more commands to be visible at once. Settings (Always-On Listening, Speech Recognition Model, AutoHotkey) remain on the left.

- **Green Checkmark Checkbox**: The "Enable voice commands" checkbox in Supervoice settings now uses the standard green checkmark style (CheckmarkCheckBox) for visual consistency with the rest of the application.

### 🏗️ Code Architecture

- **LLM Client System Prompt Support**: Added `system_prompt` parameter to the LLM client's `translate()` method and all provider-specific methods. This enables passing behavioral instructions to AI models for features like the AI Assistant.

---

## v1.9.195 - February 2, 2026

### 🏗️ Code Architecture

- **Module Rename**: Renamed `mt_quick_popup.py` → `quicktrans.py` to align with the QuickTrans branding. This prepares the module for future standalone distribution as a separate Windows executable.

---

## v1.9.194 - February 1, 2026

### 🎨 Branding & Naming

- **Tool Suite Naming**: Established consistent naming for Supervertaler's modular tools:
  - **Supervertaler** - Main CAT tool / translation workbench
  - **Superlookup** - Comprehensive research tool (TM, TB, MT, web, dictionaries)
  - **QuickTrans** - Instant translation popup (MT + LLM, GT4T-style)
  - **QuickMenu** - Quick access menu with various tools

- **QuickTrans** (formerly "MT Quick Lookup"): Renamed to better reflect that it provides instant translations from both MT engines AND LLMs. The tool can work standalone or integrated with Supervertaler.

- **Superlookup** (formerly "Superlookup"): Updated to CamelCase for consistency and readability. Now clearly distinguished from QuickTrans - Superlookup is for deep research, QuickTrans is for instant translations.

---

## v1.9.193 - February 1, 2026

### ✨ New Features

- **QuickTrans - LLM Support**: Added Claude, OpenAI (GPT), and Gemini as translation providers in QuickTrans. Now you can get translations from both MT engines AND LLMs in a single popup. Configure which providers to use in the QuickTrans settings (click the ⚙️ button in the popup).

- **Global QuickTrans Hotkey (Ctrl+Alt+M)**: Use QuickTrans from ANY application (memoQ, Word, browser, etc.) via AutoHotkey. Select text anywhere, press **Ctrl+Alt+M**, and the QuickTrans popup appears as an overlay at your cursor position - without switching focus to Supervertaler. Perfect for quick translations while working in your CAT tool.

- **QuickTrans Settings**: Added dedicated settings tab (Settings → QuickTrans) to configure:
  - Which MT engines to include (Google, DeepL, Microsoft, Amazon, ModernMT, MyMemory)
  - Which LLMs to include (Claude, OpenAI, Gemini) with model selection
  - Access settings directly from the popup via the ⚙️ button

### 🐛 Bug Fixes

- **View Settings Freeze Fixed**: Fixed a freeze that occurred when changing View settings (like "Termview under grid") and clicking "Save View Settings". The freeze was caused by TM saves being triggered for all confirmed segments during the update.

- **Termview Under Grid Default**: Changed "Termview/Session Log tabs under grid" to be OFF by default for new users. Setting is now properly persisted when changed.

---

## v1.9.192 - February 1, 2026

### ✨ New Features

- **QuickTrans (GT4T-style)**: Added a new popup window for instant translation suggestions, inspired by GT4T. Press **Ctrl+M** (default) or use the **right-click context menu** to open a popup showing translations from all enabled MT engines (Google Translate, DeepL, Microsoft Translator, Amazon Translate, ModernMT, MyMemory). Features include:
  - Source text displayed at the top for context
  - Numbered list of translation suggestions with provider badges
  - Press **1-9** to quickly insert a translation
  - Arrow keys to navigate, **Enter** to insert selected
  - **Escape** to dismiss
  - Translations fetched in parallel for fast results
  - Color-coded provider badges for easy identification
  - **Smart text selection**: If you have text selected in source or target, only that selection gets translated; otherwise translates the full source segment
  - Available from both source and target cell context menus ("⚡ QuickTrans")
  - **Resizable and movable**: Popup window can be dragged and resized to your preference
  - **Customizable shortcut**: Change the keyboard shortcut in Settings → Keyboard Shortcuts

---

## v1.9.191 - February 1, 2026

### 🎨 UI Improvements

- **Status Icon Enhancement**: Confirmed status checkmarks now display in green (#2e7d32) for better visual distinction. Status icons in rows with capitalized text no longer clip vertically.

- **Button Padding Standardization**: Unified button padding across all toolbar buttons (Show Invisibles, Advanced Filters, Sort, Clear Filters, Quick Filters, Spellcheck, etc.) to 3px vertical and 5px horizontal for consistent appearance and better text spacing.

- **Font Size Optimization**: Increased font size for segment numbers and type column symbols (¶, •, #1, H1, etc.) from size 8 to size 9 for better readability and visual proportion relative to grid content.

- **Column Width Optimization**: Segment number column now uses optimized width with reduced padding (12px total) and 55px maximum cap, accommodating up to 4-digit segment numbers (1-1000) without excess horizontal space.

---

## v1.9.190 - February 1, 2026

### 🎨 UI Improvements

- **Scrollbar Refinement**: Made main grid scrollbar narrower (12px) for cleaner appearance. Added visible triangle arrow icons to scrollbar buttons for better visual clarity. Removed custom floating precision scroll buttons for simpler interface.

- **Column Optimization**: Reduced Status column width from 60px to 50px to save horizontal space while maintaining full functionality. Changed table header font from bold to normal weight for more consistent typography.

### 🐛 Bug Fixes

- **Document Order Sort Fixed**: Document Order sort now correctly restores segments to their original sequence (1, 2, 3...) by sorting by segment ID. Previously, if segments were saved while sorted, the sorted order would become the "original" order, making it impossible to restore true document order.

- **Save/Load Order Preservation**: Projects now always save segments in original document order, not sorted order. When loading a project, sort state is reset to document order (no sort applied). This ensures consistent behavior and prevents sorted order from being permanently baked into project files.

---

## v1.9.189 - February 1, 2026

### 🎨 UI Improvements

- **Sort Progress Feedback**: Added progress dialog during sorting operations showing "Sorting segments, please wait..." message. Dialog only appears for operations taking longer than 500ms, providing clear user feedback without interrupting quick sorts on small projects. Automatically closes when sorting completes.

---

## v1.9.188 - February 1, 2026

### 🎨 UI Improvements

- **Sort Menu Styling**: Fixed sort dropdown menu to use clean white background matching Quick Filters menu style. Previous version incorrectly inherited orange button styling in menu items.

### ✨ Enhancements

- **Auto-Pagination on Sort**: Sorting now automatically sets pagination to "All" to display the complete sorted list. This prevents the confusing behavior where only the first page of sorted results would be visible. When you sort alphabetically, by length, by match rate, or any other criterion, you'll now see all sorted segments immediately.

---

## v1.9.187 - February 1, 2026

### ✨ New Features

- **Segment Sorting**: Added comprehensive sort dropdown button (⇅ Sort) in the toolbar, positioned after Advanced Filters. Similar to memoQ's sorting functionality, you can now sort segments by:
  - **Alphabetical**: Source/Target text (A → Z or Z → A)
  - **Text Length**: Source/Target (longer or shorter first)
  - **Match Rate**: TM match percentage (higher or lower first)
  - **Frequency**: Source/Target text occurrence count (higher or lower first)
  - **Last Changed**: Modification timestamp (newest or older first)
  - **Row Status**: Grouped by translation status (not started → draft → translated → confirmed)
  - **Document Order**: Reset to original document structure

  Sorting is applied to the entire project and reloads the grid to reflect the new order. The orange sort button with dropdown menu makes it easy to organize segments for review, identify patterns, or work systematically through similar content.

---

## v1.9.186 - February 1, 2026

### 🐛 Bug Fixes

- **Exit Crash Fixed**: Resolved Python crash that occurred when closing the program via File > Exit. The crash was caused by manual WebEngine cleanup interfering with Qt's internal shutdown sequence. Now lets Qt handle WebEngine cleanup naturally, resulting in clean program exit without crashes.

- **Import Shadowing Fixed**: Fixed UnboundLocalError crashes on startup caused by local imports shadowing global imports (QWidget, QHBoxLayout). Only QButtonGroup is now imported locally where needed.

### 🎨 UI Improvements

- **View Toggle Clarity**: Replaced confusing single "Tags OFF" toggle button with clear segmented control showing both "WYSIWYG" and "Tags" options. One button is always highlighted to show the current view mode.

- **Default View Mode**: Changed default view from WYSIWYG to Tags mode for better alignment with actual initial state. Tags button is now highlighted by default on startup.

---

## v1.9.185 - February 1, 2026

### 🎨 UI Improvements

- **Grid Zoom Shortcut**: Changed Grid Zoom In keyboard shortcut from Ctrl++ to Ctrl+= for easier access (no need to hold Shift). The = and + keys are on the same physical key, making this more intuitive and faster to use.

---

## v1.9.184 - February 1, 2026

### 🎨 UI Improvements

- **Dark Mode Refinements**: Improved text visibility in TermView panels (both bottom and right panel source text now use #FFFFFF for better contrast). HTML formatting tags (`<b>`, `</b>`, etc.) now display in light pink (#FFB6C1) in dark mode for enhanced readability.

- **TM Navigation Arrows**: Fixed navigation arrows in Match Panel and Compare Panel that were invisible or incorrectly rendered in dark mode. Now using crisp Unicode triangle symbols (◀ ▶) with theme-aware colors (white in dark mode, dark gray in light mode).

- **Table Header Font Size**: Reduced column header font size to match grid content (only bold, not larger), resulting in better visual proportions.

### 🐛 Bug Fixes

- **Prompt Library Updates**: Fixed Issue #112 where edited prompts were not immediately reflected in the Prompt Library or Preview Combined sections. Changes now update both the active primary prompt and attached prompts immediately after saving.

### 🛠️ Developer Experience

- **Windows Start Menu Shortcuts**: Added PowerShell scripts to create Start Menu shortcuts for both end users (`Supervertaler.exe`) and developers (`run.cmd`). Scripts are automatically included in release packages.

---

## v1.9.183 - January 31, 2026

### ⚡ Performance: Instant Ctrl+Enter Navigation

Major performance overhaul for segment navigation. Ctrl+Enter is now **instant** instead of taking 10-50+ seconds.

**In-Memory Termbase Index**
- Built a pre-indexed, in-memory glossary lookup system that replaces per-segment database queries
- Glossary lookups now take **<1ms** instead of **52 seconds** per segment
- Index is built once when project loads (~0.15s for 1,400 terms) and updated instantly when terms are added

**Async Auto-Confirm**
- The "auto-confirm 100% TM matches" feature now runs asynchronously
- Navigation completes instantly; TM lookup happens in the background
- If a 100% match is found, the segment is auto-confirmed after you've already moved

**Cache System Enabled by Default**
- Prefetch cache is now **enabled by default** for new installations
- Background workers pre-cache termbase and TM matches for upcoming segments
- Cache hit rate typically 95%+ after initial segment visit

### 🐛 Bug Fixes

- **TermView Updates on Ctrl+Enter**: The glossary/terminology panel now updates immediately when navigating with Ctrl+Enter, not just on mouse click

- **TM Panel Updates on Ctrl+Enter**: Translation Memory matches now appear immediately when navigating with Ctrl+Enter

- **Source Text Always Visible**: TermView now displays the source text even when there are no glossary matches, with appropriate status messages ("No matches in X words", "No glossaries activated", etc.)

- **Row Selection on Ctrl+Enter**: The target row is now properly selected (blue highlight) after Ctrl+Enter navigation, not just the previous row

- **TM Results for Correct Segment**: Fixed race condition where fast navigation could show TM results for the wrong segment; added segment validation before and after TM lookup

- **TM Lookup on Cache Hit**: Fixed issue where TM matches weren't shown when termbase was cached (prefetch worker skips TM for thread safety)

---

## v1.9.181 - January 30, 2026

### ✨ New Features

- **Expanded Language Support in New Project**: The New Project dialog now includes 53 languages (up from 10), matching the full list available in Settings. Languages now include Czech, Slovak, Romanian, Hungarian, and many more.

- **Global Language Sync**: When importing projects (memoQ, Trados, Phrase, XLIFF, etc.), the global language settings are now automatically synchronized with the imported project's source and target languages.

### 🎨 UI Improvements

- **TM/Glossary Activation Guidance**: When no TMs or glossaries are activated for a project, or when activated resources don't match the project's language pair, helpful guidance messages now appear in the Match Panel and Termview. Messages guide users to the Resources tab to activate appropriate resources.

---

## v1.9.180 - January 30, 2026

### ✨ New Features

- **Global UI Font Scale**: New user-configurable setting (50%-200%) that scales the entire application UI. Particularly useful for Linux/macOS users where Qt applications may render with smaller fonts, or for high-DPI displays. Find it in Settings → View → Global UI Font Scale.

---

## v1.9.178-beta - January 28, 2026

### 🐛 Bug Fixes

- **Slovak Language in Import Dialogs**: Slovak and other languages were missing from the Trados and Phrase import language dropdowns. Both dialogs now use the full language list matching the main New Project dialog.

- **mqxliff Target Loading for Pretranslated Files**: When importing pretranslated mqxliff files, the target translations were not being loaded. Added new `extract_bilingual_segments()` method to properly extract both source and target text from memoQ XLIFF files.

- **TM Activation for Global TMs**: Fixed issue where TMs created when no project was loaded would not save segments later. The TM activation system now properly handles global activations (project_id=0), so TMs activated before loading a project continue to work after a project is opened.

- **TM Storage Consistency**: The `get_writable_tm_ids`, `get_active_tm_ids`, and `is_tm_active` functions now include globally-activated TMs in addition to project-specific activations.

---

## v1.9.177-beta - January 28, 2026

### ✨ New Features

- **Bidirectional Termbase Matching**: Glossaries now work like memoQ and Trados - a termbase created for NL→EN will also find matches when working on EN→NL projects (and vice versa). This "direction-insensitive" behavior means you no longer need separate termbases for different language directions. When a match is found on the target side, source and target are automatically swapped in the results.

---

## v1.9.176-beta - January 28, 2026

### ✨ New Features

- **Unified Setup Wizard**: Combined the first-run data folder selection and features introduction into a single, streamlined wizard. New users now see a clear 2-step wizard (data folder → features), while existing users see a 1-step version with their current data folder displayed. The wizard is also accessible anytime from **Help → Setup Wizard**.

### 🎨 UI Improvements

- **Clickable Data Folder Path**: In the Setup Wizard, the data folder path is now a clickable link that opens the folder in your system's file manager (Windows Explorer, Finder, etc.).

- **Help Menu Enhancement**: Added "🚀 Setup Wizard..." menu item to the Help menu, allowing users to re-run the setup wizard at any time.

---

## v1.9.175-beta - January 28, 2026

### ✨ New Features

- **Glossary AI Injection**: New "AI" column in Glossary Settings allows glossary terms to be automatically injected into LLM translation prompts. When enabled (orange checkmark), all terms from that glossary are sent to the AI with every translation request, helping the model use your preferred terminology. Recommended for small, curated glossaries (< 500 terms).

### 🎨 UI Improvements

- **Terminology Consistency**: Renamed "Primary Prompt" to "Custom Prompt" throughout the application for better alignment with the 2-Layer Prompt Architecture documentation.

- **Preview Prompt Enhancements**: The Preview Prompts dialog now highlights the "# GLOSSARY" section in orange when glossary terms are injected.

- **Glossary Legend Updated**: The glossary settings help text now explains all four columns: Read (green), Write (blue), Priority, and AI (orange).

---

## v1.9.174 - January 28, 2026

### ✨ New Features

- **Batch Status Change**: Change the status of multiple selected segments at once via right-click context menu or Edit → Bulk Operations → Change Status. Supports all status types (Not started, Pre-translated, Translated, Confirmed, etc.)

- **Ctrl+, Tag Wrapping for HTML**: When text is selected, Ctrl+, now wraps it with HTML tag pairs (e.g., `<b>selection</b>`) in addition to memoQ tags and CafeTran pipes.

- **Ctrl+, Tip in Grid**: Added a subtle tip label "💡 Tip: Ctrl+, inserts the next tag from source" in the pagination bar to help new users discover this powerful shortcut.

### 🐛 Bug Fixes

- **TM Overwrite Mode Fixed**: The "Save only latest translation (overwrite)" TM setting now actually overwrites existing entries with the same source text, instead of creating duplicate entries.

- **Shift+Click Multi-Select Fixed**: Fixed grid selection where Shift+click to select a range was selecting extra rows. Now properly respects Qt's native range selection.

- **Status Dropdown Width**: Widened the status dropdown to prevent text truncation (e.g., "Pre-translated", "TR confirmed" now fully visible).

### 🎨 UI Improvements

- **AI Settings Reorganized**: Moved "Model Version Checker" and "API Keys" sections higher in AI Settings, right after Model Selection for easier access.

---

## v1.9.173 - January 28, 2026

### ✨ Improvements

- **Smarter TM Exact Matching**: Exact matches now use text normalization before hashing, so matches are found even when source text differs only in whitespace or Unicode normalization (e.g., non-breaking spaces vs regular spaces).

- **Improved TM Pre-Translation Dialog**: The progress dialog now shows the current segment being processed, count of matches found so far, and elapsed time. A patience message appears after 10 seconds for large jobs.

- **Higher Fuzzy Match Threshold**: Raised the minimum fuzzy match threshold from 70% to 75% for better quality matches across all TM operations.

---

## v1.9.172 - January 28, 2026

### 🐛 Bug Fixes

- **Fresh Projects Start Clean**: Fixed bug where TMs and glossaries remained activated from previous sessions when loading/creating new projects. Now all TMs and glossaries are properly deactivated when loading a project, giving you a clean slate. Saved resource activations are then restored from the project file if available.

---

## v1.9.171 - January 28, 2026

### 🐛 Bug Fixes

- **TM Target & Alt+0 Badge Restored**: Fixed regression where the TM Target and its blue "0" badge (Alt+0 shortcut) were missing from the Match Panel, even when TM matches were found. TM matches are now displayed correctly, and the Alt+0 shortcut works as documented.

---

## v1.9.170 - January 27, 2026

### ✨ New Features

- **Scratchpad Tab in Right Panel**: The Scratchpad is now available as a permanent tab in the right panel for easier access.
  - **Location**: Right panel tabs → last tab after "Session Log"
  - **Auto-Update**: Content automatically syncs with project's scratchpad notes
  - **Dual Access**: Available both as popup dialog (`Ctrl+Shift+P`) and as permanent tab
  - **Project-Aware**: Tab clears when creating new project, populates when loading project

- **Settings Improvement**: "Disable ALL caches" was temporarily enabled by default *(reverted in v1.9.183 - caches are now enabled by default for performance)*.

- **TM Target Shortcut Badge**: Added a blue "0" badge next to the TM Target in the Match Panel, indicating the Alt+0 shortcut for instant TM match insertion. Shortcut is documented and works out of the box.

---

## v1.9.169 - January 27, 2026

### ✨ New Features

- **Scratchpad for Private Notes**: New pop-up scratchpad for translator's private notes during a job.
  - **Access**: `Tools → 📝 Scratchpad...` or keyboard shortcut `Ctrl+Shift+P`
  - **Private**: Notes are stored only in the `.svproj` file and are **never** exported to CAT tools or shared with clients
  - **Persistent**: Notes are saved with the project and restored when you reopen it
  - **Use Cases**:
    - Terminology decisions and rationale
    - Client preferences and style notes
    - Research findings and reference links
    - Questions to ask the project manager
    - Personal reminders and to-do items
  - **Design**: Clean dialog with monospace font, placeholder text with usage suggestions

---

## v1.9.168 - January 27, 2026

### ✨ New Features

- **Markdown File Import Support**: Added `.md` file support to the text file importer. ([#127](https://github.com/michaelbeijer/Supervertaler/issues/127))
  - **File Menu**: `Import → Text / Markdown File (TXT, MD)...`
  - **File Filter**: Now shows "Text Files (*.txt *.md)", "Markdown (*.md)", "Plain Text (*.txt)"
  - **Smart Dialog**: Detects Markdown files and shows Markdown-specific import instructions
  - **Syntax Highlighting**: Markdown elements are highlighted with distinctive colors:
    - **Headings** (`#`, `##`, etc.): Blue, bold
    - **Bold/Italic markers** (`**`, `*`, `__`, `_`): Violet, bold
    - **Code** (`` ` ``, `` ``` ``): Orange, bold
    - **Links/Images** (`[]()`, `![]()`): Purple
    - **Blockquotes** (`>`): Green, bold
    - **Lists** (`-`, `*`, `+`, `1.`): Orange, bold
  - **Round-trip Safe**: Markdown syntax is preserved as-is for clean export back to `.md`
  - **Use Case**: Translate documentation, README files, AI prompts, and other Markdown content

---

## v1.9.167 - January 27, 2026

### 🐛 Bug Fixes

- **Keyboard Shortcuts Panel Text Vanishing**: Fixed bug where UI text (Action, Shortcut, Status columns) would disappear after changing a shortcut. ([#125](https://github.com/michaelbeijer/Supervertaler/issues/125))
  - **Root Cause**: Qt's sorting feature was interfering with table row modifications during `load_shortcuts()`, causing items to become disassociated from their rows.
  - **Fix**: Disabled sorting during table modifications and re-enabled it after completion.

---

## v1.9.166 - January 27, 2026

### 🐛 Bug Fixes

- **CRITICAL: TM Write Checkbox Now Works**: Fixed confirmed translations going to "project" TM instead of user-designated TM. The Write checkbox (not Read) now determines where segments are saved. ([#126](https://github.com/michaelbeijer/Supervertaler/issues/126))
  - **Root Cause**: `save_segment_to_activated_tms()` was using `get_active_tm_ids()` which only returns TMs with the Read checkbox enabled, ignoring the Write setting entirely.
  - **Fix**: Added new `get_writable_tm_ids()` method that checks for TMs with Write enabled (`read_only = 0`), and updated save logic to use it.
  - **Now**: Enabling Write checkbox alone (without Read) will save confirmed segments to that TM.

---

## v1.9.165 - January 27, 2026

### ✨ New Features

- **Settings Panel Font Scaling**: Added UI Font Scale setting (80%-200%) in View Settings for better readability on high-DPI/4K displays. Especially useful for macOS users with Retina displays. Click "Apply" for instant preview or save to persist. ([#128](https://github.com/michaelbeijer/Supervertaler/issues/128))

---

## v1.9.164 - January 26, 2026

### 🐛 Bug Fixes

- **macOS/Linux Compatibility**: Fixed `ModuleNotFoundError: No module named 'keyboard'` on non-Windows platforms. The `keyboard` module is Windows-only and is now imported conditionally with graceful fallback. ([#124](https://github.com/michaelbeijer/Supervertaler/issues/124))

---

## v1.9.163 - January 26, 2026

### 🐛 Bug Fixes

- **DOCX Import**: Fixed paragraph style bold/italic not being captured. Headings using styles like "Title" or "Subtitle" now correctly export as bold. Previously only direct run-level formatting was detected.
- **Termview Font Settings**: Fixed spinbox up/down buttons not visible. Fixed font size changes only applying to bottom Termview, not Match Panel Termview.
- **View Settings Dialog**: Fixed "Settings Saved" dialog not closing when clicking OK.

---

## 🧪 Cache Kill Switch & Performance Improvements (v1.9.155-162) - January 26, 2026

**Experimental Cache Bypass for Faster Grid Navigation**

This release includes a series of performance optimizations and a new experimental feature for testing grid responsiveness.

### 🧪 NEW: Cache Kill Switch (v1.9.162)

Added experimental setting to bypass all caching systems:

- **Location**: Settings → General → 🧪 Experimental Performance
- **Option**: "Disable all caches (for testing responsiveness)"
- **What it does**: Bypasses translation_matches_cache, termbase_cache, prefetch workers
- **Result**: Direct SQLite lookups - may actually be faster due to no lock contention!

Use this setting to test whether caching is helping or hurting your workflow.

### ⚡ Performance Optimizations (v1.9.155-161)

**Ctrl+Enter Speed Improvements:**
- Reduced verbose logging overhead (15-20 fewer log calls per navigation)
- Preview tab now skips TM/glossary lookups entirely (faster reviewing)
- Idle prefetch system loads matches while you type (Ctrl+Enter feels instant)
- Direct termbase lookups in prefetch worker (no more race conditions)

**TM Matching Fixes:**
- Fixed missing fuzzy matches when using multiple TMs (increased FTS5 candidate pool)
- Fixed crashes when no TMs activated or only "Write" enabled
- Fixed wrong dictionary key in TM exact match lookups

**Proactive Grid Highlighting (v1.9.161):**
- Glossary terms now highlight in UPCOMING segments while you work on current one
- See terms in segments 255, 256, 257 while editing segment 254
- No more waiting for highlighting after navigation

### 🔧 Bug Fixes

- Fixed batch translation crashes with empty TM lists
- Fixed TM pre-translation SQLite threading errors (now runs on main thread)
- Fixed retry pass variable scope issues in batch translate
- Fixed various logging and debug output issues

---

## 🎯 Match Panel Consolidation (v1.9.154) - January 25, 2026

**Streamlined Right Panel UI**

Replaced the Compare Panel with a more focused Match Panel that combines glossary terms and TM matches in one convenient view.

**What Changed:**
- **Compare Panel removed**: Was redundant with Translation Results panel
- **Match Panel introduced**: Combines Termview (glossary) + TM Source/Target boxes
- **Green TM boxes**: TM matches display with green background (#d4edda) for easy identification
- **Zoom shortcuts work**: Ctrl+Alt+= and Ctrl+Alt+- now zoom the Match Panel TM boxes
- **Cleaner tab structure**: Fewer tabs = less switching during translation

**New Right Panel Tabs:**
1. Translation Results (TM/MT/Glossary matches in list view)
2. Match Panel (Termview + TM Source/Target side-by-side)
3. Preview (document preview)
4. Segment Note
5. Session Log

**Benefits:**
- ✅ Less tab clutter - removed redundant Compare Panel
- ✅ Glossary terms and TM matches visible together in Match Panel
- ✅ Green highlighting makes TM boxes easy to spot
- ✅ Keyboard zoom shortcuts work on TM boxes

---

## 📐 Tab Layout Reorganization (v1.9.153) - January 23, 2026

**Better Workflow Organization**

Redesigned the tab layout to consolidate resources and improve translation workflow:

**What Changed:**
- **Termview stays under grid**: Original position preserved for quick glossary access
- **Second Termview in right panel**: Duplicate instance so you can see terms while viewing other tabs
- **Segment Note moved**: Now in right panel alongside Compare Panel and Preview
- **Session Log moved**: Also in right panel for better organization
- **Simultaneous updates**: Both Termview instances update together automatically

**New Tab Structure:**
- **Left panel**: Grid + Termview (collapsible)
- **Right panel**: Translation Results, Compare Panel, Preview, Segment Note, Session Log, Termview (6 tabs)

**Benefits:**
- ✅ All reference materials (TM, glossary, notes) in one location
- ✅ Grid area cleaner - just grid + glossary
- ✅ Two Termview instances let you see terms while using other tabs
- ✅ Ctrl+N shortcut still works to jump to Segment Note tab

**Implementation:**
- New helper method: `_update_both_termviews()` - updates both instances simultaneously
- Updated 5 locations where Termview gets updated
- Modified tab creation code to reorganize layout
- Updated Ctrl+N shortcut to find tab by name (works regardless of tab visibility)

**Future Enhancement (Phase 2):**
User requested advanced docking: ability to drag tabs to dock them vertically in the right panel (like VS Code). This would require QDockWidget architecture - a significant refactor planned for future version.

---

## ⚡ Instant Glossary Updates (v1.9.152) - January 23, 2026

**Lightning-Fast Term Addition Performance**

Adding terms to glossaries is now instant! The 5-6 second delay when using Alt+Shift+Up/Down shortcuts has been eliminated.

**The Problem:**
- Users experienced 5-6 second delays after adding glossary terms during translation
- Long patent sentences with 50+ words required 50+ individual database searches
- The app was searching for ALL words again just to find the ONE term we just added

**The Solution:**
- **Direct cache update**: New term added directly to cache instead of full segment re-search
- **Immediate TermView update**: Display updates instantly using cached matches
- **Smart highlighting**: Source cell highlighting updated via direct function call
- **Zero database searches**: We already know what we added - no need to search for it!

**Result:**
- ✅ TermView shows new term instantly (< 0.1 seconds)
- ✅ Source highlighting updates instantly
- ✅ Smooth, responsive workflow for building glossaries
- ✅ Perfect for intensive patent translation workflows

**Technical Implementation:**
- Modified `_quick_add_term_with_priority()` to create match entry directly from added term
- Bypasses expensive `find_termbase_matches_in_source()` database search
- Calls `highlight_source_with_termbase()` directly with updated cache
- Maintains all existing functionality while eliminating performance bottleneck

**Files Modified:**
- `Supervertaler.py` - Optimized glossary quick-add workflow (~60 lines modified)

**🛡️ Exit Crash Fix:**
- Enhanced `_cleanup_web_views()` method to prevent Python crash on program exit
- Now properly stops all WebEngine page loading/rendering before cleanup
- Processes events multiple times and adds delay to ensure Qt finishes cleanup
- No more "Python has stopped working" dialog when using File → Exit

---

## 🔧 TM Pre-Translation Fixed (v1.9.151) - January 23, 2026

**Critical Fix:** "Pre-translate from TM" batch operation now correctly finds TM matches!

**What Was Broken:**
- Running Edit → Batch Operations → Pre-translate from TM found 0 matches
- Even when a 100% TM match was clearly visible in the Compare Panel
- Issue: SQLite databases cannot be shared across threads

**Root Cause:**
The `PreTranslationWorker` ran in a background thread, but SQLite connections created in the main thread cannot be used in other threads. This caused a `sqlite3.ProgrammingError`.

**The Fix:**
- TM pre-translation now runs **on the main thread** (same as the Compare Panel)
- Uses `QProgressDialog` to show progress and keep UI responsive
- Uses the **exact same database methods** that work for segment navigation
- No more SQLite threading errors!

**Before:** "Pre-translate from TM" → 0 matches found
**After:** "Pre-translate from TM" → Correctly finds all TM matches

## 🔍 Superlookup Language-Aware Search Enhancement (v1.9.149-beta) - January 22, 2026

**Major Enhancement:** Superlookup TM search now intelligently handles language filters!

**What Changed:**
- **Intuitive Language Filters**: "From: Dutch → To: English" now means "Search FOR Dutch text and show me English translations"
- **Smart Column Detection**: Searches ALL TMs regardless of their stored direction (NL→EN or EN→NL)
- **Automatic Swapping**: Finds text in either column and automatically presents results in the correct order
- **More Powerful Than Traditional CAT Tools**: Unlike memoQ/Trados that only search one TM direction, Supervertaler finds matches everywhere

**How It Works:**
1. Set "From: Dutch → To: English"
2. Search for Dutch text
3. System searches both NL→EN TMs (source column) AND EN→NL TMs (target column)
4. Results automatically shown with Dutch in source, English in target

**UI Simplification:**
- **Removed Direction radio buttons** (Both/Source/Target) - always searches both columns now
- Cleaner, faster interface for translators
- Language dropdowns are all you need

**UI Polish:**
- **Renamed "QuickMenu" tab to "Prompt Manager"** - Better clarity for new users looking for translation prompts
- **Sub-tab renamed**: "Library" → "Prompt Library"
- Resolves confusion about where to find prompts for single-segment and batch translation

**Technical Details:**
- Language filters no longer restrict which TMs are searched
- Post-processing validates search text is in the correct language column
- Smart swapping ensures results always match user's requested language direction

**Files Modified:**
- `modules/database_manager.py` - Smart language-aware concordance search
- `modules/superlookup.py` - Column name handling
- `Supervertaler.py` - Removed Direction controls, simplified UI, renamed QuickMenu to Prompt Manager
- `modules/unified_prompt_manager_qt.py` - Updated tab and header labels

---

## 📁 User-Choosable Data Folder Location (v1.9.148-beta) - January 21, 2026

**Major Enhancement:** Users now choose where to store their data on first run!

**What Changed (from v1.9.147):**
- v1.9.147 stored data in hidden AppData folders - users couldn't easily find/backup their data
- v1.9.148 uses visible, accessible locations that users control

**The New System:**

| Platform | Default Location |
|----------|------------------|
| **Windows** | `C:\Users\Username\Supervertaler\` |
| **macOS** | `~/Supervertaler/` |
| **Linux** | `~/Supervertaler/` |

**Key Features:**
- **First-run dialog** lets users choose their data folder location
- **Default is visible** in home folder - easy to find and backup
- **Settings → General** includes "Change..." button to relocate data anytime
- **Auto-recovery** if config pointer is deleted but data exists at default location
- **Unified system** - ALL users (pip, EXE, dev) use the same approach

**How It Works:**
1. On first run, a dialog asks where to store data
2. User choice is saved to a small config pointer file:
   - Windows: `%APPDATA%\Supervertaler\config.json`
   - macOS: `~/Library/Application Support/Supervertaler/config.json`
   - Linux: `~/.config/Supervertaler/config.json`
3. This pointer just contains: `{"user_data_path": "C:\\Users\\John\\Supervertaler"}`
4. If pointer is deleted, app auto-recovers by checking the default location

**Benefits:**
- ✅ Data is visible and easy to find
- ✅ Easy to backup (just copy the folder)
- ✅ User has full control over location
- ✅ Can use cloud folders (OneDrive, Dropbox, etc.)
- ✅ Survives pip upgrades
- ✅ Works identically for all installation types

---

## 📁 Persistent User Data Location (v1.9.147) - January 21, 2026

*Note: v1.9.148 improves on this by using visible locations and adding user choice.*

**Major Enhancement:** User data (API keys, TMs, glossaries, prompts, settings) now persists across pip upgrades!

**The Problem:**
- When users ran `pip install --upgrade supervertaler`, their data was wiped
- This happened because user_data/ was stored inside the pip package directory
- pip replaces the entire package directory on upgrade, deleting all user files

---

## 🔑 Gemini/Google API Key Alias Fix (v1.9.146) - January 21, 2026

**Bug Fix:** Fixed "Gemini API Key Missing" error when users had `google=...` instead of `gemini=...` in their api_keys.txt file.

**The Problem:**
- Users could use either `google=YOUR_KEY` or `gemini=YOUR_KEY` in api_keys.txt
- Some code paths only checked for `gemini`, others only for `google`
- This caused confusing "API Key Missing" dialogs even when keys were properly configured

**The Solution:**
- Added **automatic normalization** in `load_api_keys()` - if either `google` or `gemini` is set, both keys are now populated
- Users can use either name interchangeably
- Fixed 6+ locations where the alias wasn't being handled correctly:
  - Single segment translation (Ctrl+T)
  - Batch translation
  - QuickMenu prompts
  - Async LLM fetch for Translation Results panel
  - Proofreading (also fixed `anthropic` → `claude` provider name bug)

**For Users:**
- Both `google=YOUR_KEY` and `gemini=YOUR_KEY` now work identically
- No action needed - existing api_keys.txt files will continue to work

---

## ✨ memoQ-Style Track Changes in Compare Panel (v1.9.145) - January 20, 2026

**Improvement:** Changed Compare Panel diff highlighting to match memoQ's "Track changes view" style.

**New Styling (memoQ-style):**
- **Deletions**: Red text + strikethrough (~~deleted~~)
- **Insertions**: Red text + underline (<u>inserted</u>)
- **Unchanged**: Normal text

This is cleaner and more familiar to translators who use Word's track changes or memoQ.

**Example:**
```
Current: In een derde aspect betreft de huidige uitvinding het gebruik van een dichtingskit...
TM:      In een tweede aspect betreft de huidige uitvinding een dichtingskit...

Compare Panel shows:
In een ~~tweede~~ derde aspect betreft de huidige uitvinding het gebruik van een dichtingskit...
        ^^^^^^^^  ^^^^^^                                      ^^^^^^^^^^^^^^^^
        red+strike red+under                                  red+underline
```

---

## ✨ Enhanced Compare Panel Diff Highlighting (v1.9.144) - January 20, 2026

**Improvement:** The Compare Panel now shows a complete diff with additions, deletions, and changes - matching what professional CAT tools display.

**Before:**
- Only showed text that was **different** (in red)
- Didn't show what was **added** in the current segment
- Hard to understand what changed between TM source and current source

**After:**
- **Red with strikethrough**: Text in TM that was changed/deleted in current segment
- **Green background**: Text in current segment that was added (not in TM)
- **Normal text**: Identical text in both

**Example:**
```
Current: In een derde aspect betreft de huidige uitvinding het gebruik van een dichtingskit...
TM:      In een tweede aspect betreft de huidige uitvinding een dichtingskit...

Compare Panel now shows:
In een [tweede→derde] aspect betreft de huidige uitvinding [+het gebruik van] een dichtingskit...
```

**Technical:**
- Changed from character-level to word-level diffing for better readability
- Additions shown inline in green where they would appear
- Deletions shown with strikethrough so you can see what was removed

---

## 🐛 Fix: Deleting Glossary Entry from Termview (v1.9.143) - January 20, 2026

**Bug Fix:** Deleting glossary entries via right-click in Termview failed with an error.

**Error in Logs:**
```
✗ Error deleting glossary entry from database: 'DatabaseManager' object has no attribute 'get_connection'
```

**Root Cause:**
- The delete handler was trying to call `db_manager.get_connection()` which doesn't exist
- Was using raw SQL instead of the existing `termbase_mgr.delete_term()` method

**The Fix:**
- Changed to use `self.termbase_mgr.delete_term(term_id)` which already exists and works correctly

---

## 🐛 Fix: Error When Editing Glossary Entry from Termview (v1.9.142) - January 20, 2026

**Bug Fix:** After editing a glossary entry via right-click → Edit in Termview, an error would occur preventing proper refresh.

**Error in Logs:**
```
Error refreshing segment matches: SupervertalerQt.on_cell_selected() missing 2 required positional arguments: 'previous_row' and 'previous_col'
```

**Root Cause:**
- The `_refresh_current_segment_matches()` method was calling `on_cell_selected(current_row, 2)` with only 2 arguments
- But `on_cell_selected()` requires 4 arguments: `current_row`, `current_col`, `previous_row`, `previous_col`

**The Fix:**
- Simplified `_refresh_current_segment_matches()` to use the targeted `_refresh_termbase_display_for_current_segment()` method
- This method already handles termbase cache clearing and display refresh correctly

---

## 🐛 Fix: Termview Blank After Adding Glossary Term (v1.9.141) - January 20, 2026

**Bug Fix:** After adding a term to a glossary via Alt+Down (or similar shortcuts), the Termview pane would go blank and stay blank until navigating to another segment.

**Error in Logs:**
```
Error updating termview: list index out of range
```

**Root Cause:**
- The `_refresh_termbase_display_for_current_segment()` method (added in v1.9.140) was using incorrect dictionary keys
- `find_termbase_matches_in_source()` returns dict with keys `source` and `translation`
- But the refresh method was looking for keys `source_term` and `target_term`
- This resulted in empty strings being passed to the termview, causing the index error

**The Fix:**
- Changed `match.get('source_term', '')` → `match.get('source', '')`
- Changed `match.get('target_term', '')` → `match.get('translation', '')`
- Applied same fix to Translation Results panel update section

---

## 🐛 Fix: Adding Glossary Term No Longer Triggers TM Search (v1.9.140) - January 20, 2026

**Performance Fix ([#118](https://github.com/michaelbeijer/Supervertaler/issues/118)):** Adding a term to a glossary was unnecessarily triggering a full Translation Memory search, causing delays.

**The Problem:**
- When adding a term via Ctrl+E, Ctrl+Q, or Ctrl+Shift+1/2, the app cleared BOTH caches and called `on_cell_selected()`
- This triggered a complete TM search even though adding a glossary term doesn't change the source/target text

**The Fix:**
- Created new targeted method `_refresh_termbase_display_for_current_segment()`
- Only clears the termbase cache (not TM cache)
- Updates TermView widget with fresh glossary matches
- Updates Translation Results panel's Termbases section only
- Re-highlights termbase matches in source cell
- TM results remain untouched

**Also in this release:**
- 🎤 Renamed "Voice OFF/ON" button to "Voice Commands OFF/ON" for clarity
- 🎤 Renamed "Dictate" button to "Dictation"

---

## 📐 Auto-Sizing Segment Number Column (v1.9.138) - January 20, 2026

**Grid UX Improvement:** The segment number column (#) now automatically sizes itself based on:
- The current font size (scales when you zoom in/out)
- The number of segments in the project (fits 3-digit, 4-digit numbers, etc.)

Uses Qt font metrics to calculate the exact pixel width needed. No more truncated numbers or wasted space!

**Also in this release:**
- Status column made slightly narrower (70px → 60px)

---

## 🏷️ Termview Punctuated Terms Fix (v1.9.138) - January 20, 2026

**Fixed: Glossary terms with punctuation now appear in Termview**

Glossary entries like "ca." (with period) or "typisch" (when appearing in parentheses as "(typisch)") were found by the termbase search (grid showed green highlighting) but did NOT appear in the Termview pane.

**Root Cause - Key Mismatch:**
- `matches_dict` keys were built using the raw source term: `"ca."` (with period)
- But lookup stripped punctuation from tokens: `"ca."` → `"ca"`
- Result: `matches_dict.get("ca")` returned nothing because the key was `"ca."`

**The Fix:**
- When building `matches_dict`, keys are now normalized by stripping punctuation
- Both the dictionary keys and lookup keys now use the same normalization
- Added brackets `()[]` to punctuation chars so "(typisch)" matches "typisch"

**Files Modified:**
- `modules/termview_widget.py` - Normalize punctuation in `matches_dict` keys and lookup

---

## 🔧 Termview Race Condition Fix (v1.9.137) - January 20, 2026

**Fixed: Glossary terms now appear in Termview pane**

When navigating to a segment, the Termview pane showed "No glossary matches" even though:
- The grid highlighted terms in green (glossary highlighting worked)
- Force Refresh (F5) would then show the terms correctly

**Root Cause - Timing/Race Condition:**
- `update_tab_segment_editor()` was called EARLY in the cell selection process
- It tried to read from `termbase_cache[segment_id]` to update Termview
- But the termbase search (`find_termbase_matches_in_source()`) hadn't run yet!
- Result: Termview called with empty list before matches were found

**The Fix:**
1. Removed premature Termview update from `update_tab_segment_editor()` 
2. Termview is now updated ONLY after the termbase search completes in `_on_cell_selected_full()`
3. Also fixed: Termview now updates even when no matches found (shows "No matches" state)

**Files Modified:**
- `Supervertaler.py` - Removed premature Termview update, fixed condition to always update Termview
- `modules/termview_widget.py` - Removed debug logging

---

## 📚 Glossary Matching Fix for Punctuation (v1.9.136) - January 20, 2026

**Fixed: Glossary terms with trailing punctuation now match correctly**

Glossary entries like "ca." (with period), "psi", and "typisch" were not being found in the Termview window when they appeared in source text like "ca. 2,2 (270 psi)".

**Root Cause:**
- When tokenizing source text, punctuation was stripped from words ("ca." → "ca")
- The database search then looked for "ca" but the glossary had "ca." – no match
- Short terms in parentheses like "(psi)" were also affected

**The Fix:**
1. Now searches for BOTH the stripped word AND the original word with punctuation
2. Database query enhanced with reverse matching: finds glossary terms where the search word matches the term with trailing punctuation stripped
3. Handles entries like "ca.", "gew.%", "psi", etc.

**Files Modified:**
- `Supervertaler.py` - Enhanced `find_termbase_matches_in_source()` to search with original punctuation
- `modules/database_manager.py` - Enhanced `search_termbases()` with punctuation-tolerant matching

---

## 🔍 Filter Now Searches Entire Document (v1.9.135) - January 20, 2026

**Fixed: Filter Source/Target boxes now search across ALL pages**

Previously, the Filter Source and Filter Target boxes above the grid would only search within the currently visible page. If the text you were looking for was on a different page, it wouldn't be found.

Now, filtering searches through the **entire document** regardless of pagination. When a filter is active, ALL matching rows are displayed (pagination is temporarily ignored). When you clear the filter, normal pagination resumes.

**Files Modified:**
- `Supervertaler.py` - Fixed `_apply_pagination_to_grid()` to show all filter matches

---

## 🔊 Fuzzy TM Match Sound Effect (v1.9.134) - January 20, 2026

**New sound effect option: "Fuzzy TM match found"**

Plays when navigating to a segment and a fuzzy TM match (50-99%) is found, but NO 100% match exists.

**Access:** Settings → General → Sound Effects → "Fuzzy TM match found"

Disabled by default (set to "None"). Works with the same sound options as other effects.

---

## 🔊 New Sound Effects (v1.9.133) - January 20, 2026

**Two new sound effect options added:**

1. **Segment confirmed** - Plays when you confirm a segment with Ctrl+Enter
2. **100% TM match alert** - Plays when navigating to a segment and a 100% TM match is found and auto-inserted

**Access:** Settings → General → Sound Effects

Both sounds are disabled by default (set to "None"). Users can configure them to any of the available Windows system sounds:
- OK, Asterisk, Exclamation, Hand, Question
- Windows .wav files: Restore, Navigation Start, Speech Disambiguation, etc.

**Files Modified:**
- `Supervertaler.py` - Added sound effect options and trigger points

---

## 🐛 Ctrl+K Superlookup Shortcut Fix (v1.9.132) - January 20, 2026

**Fixed: Ctrl+K was not working**

The Ctrl+K shortcut to open Superlookup with selected text was broken due to a duplicate shortcut conflict:

**Root Cause:**
- A QShortcut (global) was registered for Ctrl+K → `show_concordance_search()` (correct)
- A QAction menu item ALSO had `setShortcut("Ctrl+K")` → `_go_to_superlookup()` (wrong handler)
- When both claimed the same key, Qt's behavior was unpredictable

**The Fix:**
- Removed duplicate shortcut from menu action
- Menu item still shows "(Ctrl+K)" in label for discoverability
- Now connects to `show_concordance_search()` for consistent behavior

**Files Modified:**
- `Supervertaler.py` - Removed duplicate Ctrl+K binding from Tools menu

---

## ⌨️ Alt+K QuickMenu Shortcut (v1.9.131) - January 20, 2026

**Direct QuickMenu Access via Keyboard**

Added Alt+K keyboard shortcut to open the QuickMenu popup directly without right-clicking:

**How It Works:**
1. Press Alt+K while in the grid (source or target cell)
2. QuickMenu popup appears at cursor position
3. Use Up/Down arrows to navigate prompts
4. Press Enter to expand a prompt's actions
5. Select "Run (show response)" or "Run and replace target"

**Benefits:**
- Faster workflow - no mouse needed
- Quick access to AI prompt actions
- Works from both source and target cells
- Menu appears at cursor position for easy navigation

**Files Modified:**
- `modules/shortcut_manager.py` - Added `editor_open_quickmenu` shortcut definition
- `Supervertaler.py` - Added `open_quickmenu()` method, registered Alt+K shortcut

---

## ✨ Context Placeholders & Auto-Center Fix (v1.9.130) - January 20, 2026

**Three Context Placeholders for QuickMenu Prompts**

Split the `{{DOCUMENT_CONTEXT}}` placeholder into three specialized variants for better AI prompt control:

| Placeholder | Purpose | Output |
|-------------|---------|--------|
| `{{SOURCE+TARGET_CONTEXT}}` | Proofreading prompts | Both source and target text |
| `{{SOURCE_CONTEXT}}` | Translation/terminology questions | Source text only |
| `{{TARGET_CONTEXT}}` | Consistency/style analysis | Target text only |

**Why Three Placeholders?**
- **Proofreading**: Needs both source and target to verify translations
- **Translation questions**: Only needs source (showing MT translations would mislead the AI)
- **Style analysis**: Only needs target text to analyze consistency

**🎯 Auto-Center Active Segment Fix**

Fixed "Keep Active Segment Centered" feature using Qt's built-in centering:
- **Previous issue**: Manual viewport calculations were unreliable across different screen sizes
- **Solution**: Use `table.scrollTo(index, QAbstractItemView.ScrollHint.PositionAtCenter)`
- **Result**: Active segment now reliably centers in the viewport during navigation

**⌨️ Double-Tap Shift Context Menu**

New keyboard shortcut for faster right-click menu access (via AutoHotkey):
- **Double-tap Shift**: Opens context menu at cursor position
- **Works in Supervertaler only**: Won't interfere with other applications
- **Requires AutoHotkey**: Part of `supervertaler_hotkeys.ahk` script

**🔧 Script Rename**

Renamed AHK script for clarity:
- `superlookup_hotkey.ahk` → `supervertaler_hotkeys.ahk`
- Now handles multiple hotkeys: Ctrl+Alt+L (Superlookup) + Shift+Shift (context menu)

**Files Modified:**
- `Supervertaler.py` - Context placeholder handling, auto-center fix, AHK script references
- `modules/unified_prompt_manager_qt.py` - Updated Placeholders reference tab
- `modules/shortcut_manager.py` - Added double-shift shortcut documentation
- `supervertaler_hotkeys.ahk` - New combined hotkey script

---

## 🐛 QuickMenu Document Context Bug Fix (v1.9.129) - January 19, 2026

**Fixed Critical Bug: {{DOCUMENT_CONTEXT}} Placeholder Now Works**

Fixed a critical bug where the `{{DOCUMENT_CONTEXT}}` placeholder in QuickMenu prompts was completely broken due to a method name typo:

**The Problem:**
- QuickMenu prompts using `{{DOCUMENT_CONTEXT}}` would fail to load project segments
- Instead of receiving actual document context, the AI received an error message
- This made context-aware prompts ineffective (AI answered generic questions without project knowledge)

**Root Cause:**
- `_build_quickmenu_document_context()` called `self.load_general_settings_from_file()` which doesn't exist
- Should have been `self.load_general_settings()` (without "_from_file" suffix)
- Exception was caught but resulted in error text being sent to AI instead of segments

**The Fix:**
- Fixed method name: `load_general_settings_from_file()` → `load_general_settings()`
- Document context now builds correctly with configurable percentage (default 50%)
- Maximum 100 segments as safety limit to prevent token overflow

**User Impact:**
- ✅ QuickMenu prompts can now access full project context
- ✅ AI receives actual segments instead of error messages
- ✅ Context-aware translation suggestions now work as intended
- ✅ Better handling of domain-specific terminology with project knowledge

**Example Working Prompt:**
```
Suggest the best possible translation of "{{SELECTION}}" from {{SOURCE_LANGUAGE}} to {{TARGET_LANGUAGE}} within the context of the current patent application: {{DOCUMENT_CONTEXT}}
```

**Files Modified:**
- `Supervertaler.py` - Fixed method name in `_build_quickmenu_document_context()`

---

## 📐 Placeholders Tab Layout Optimization (v1.9.128) - January 19, 2026

**Vertical Space Optimization**

Redesigned the Placeholders reference tab to eliminate wasted vertical space and match the standard tool layout pattern used in AutoFingers, TMX Editor, and Supercleaner:

**Layout Changes:**
- **Tips Sidebar**: Moved "Usage Tips" section to a right sidebar panel (280-400px width)
- **Standard Header**: Changed header from 10pt to 16pt bold with #1976D2 color to match other tools
- **Description Box**: Added light blue (#E3F2FD) description box below header for consistency
- **Stretch Factor**: Added stretch factor to splitter layout (`layout.addWidget(splitter, 1)`) to fill all vertical space
- **QSplitter**: Implemented horizontal splitter with table (75%) and tips panel (25%)

**Result:**
- Table now fills entire vertical space without any wasted area
- Tips remain easily accessible in right sidebar
- Visual consistency with other tool tabs throughout the application

**Files Modified:**
- `modules/unified_prompt_manager_qt.py` - Complete `_create_placeholders_tab()` redesign (~85 lines modified)

---

## 🔧 Prompt Manager UI Fixes (v1.9.127) - January 19, 2026

**Save Button Fix**

Fixed issue where Save button remained greyed out (disabled) after creating a new prompt:

**Problem:**
- User clicks "+ New" → enters prompt name → prompt loads in editor
- Save button remains disabled despite content being loaded
- Could not save edits to new prompts

**Fix:**
- Added explicit `btn_save_prompt.setEnabled(True)` call after `_load_prompt_in_editor()` in `_new_prompt_in_folder()` method
- Ensures Save button is always enabled after new prompt creation
- Prevents workflow interruption when creating and editing prompts

**Label Rename**

Renamed QuickMenu checkbox label for clarity:

**Before:** "Show in QuickMenu"  
**After:** "Show in Supervertaler QuickMenu"

**Reason:** Distinguishes the app-level QuickMenu from the Grid right-click QuickMenu for better user understanding.

**Files Modified:**
- `modules/unified_prompt_manager_qt.py` - Save button enable call, checkbox label text

---

## 🔄 Prompt System Improvements (v1.9.126) - January 19, 2026

**Field Rename: `quickmenu_quickmenu` → `sv_quickmenu`**

Renamed the redundant `quickmenu_quickmenu` field to cleaner `sv_quickmenu` (Supervertaler QuickMenu) throughout the codebase:

**What Changed:**
- All internal code now uses `sv_quickmenu` instead of `quickmenu_quickmenu`
- Backward compatibility maintained: Old .svprompt files with `quickmenu_quickmenu` still load correctly
- Legacy `quick_run` field kept in sync for compatibility with older code
- Pattern: Read old field names if present, always write new field name

**Files Modified:**
- `modules/unified_prompt_library.py` - Updated parse, save, toggle methods (6 occurrences)
- `modules/unified_prompt_manager_qt.py` - Updated editor, creation, display code (12 occurrences)

**📝 Placeholders Reference Tab**

Added new "Placeholders" tab to Prompt Manager for easy reference when writing prompts:

**Features:**
- Complete list of all 5 available placeholders with descriptions and examples
- Table format: Placeholder | Description | Example
- Usage tips section with best practices
- Located in Prompt Manager after AI Assistant tab

**Available Placeholders:**
- `{{SELECTION}}` - Currently selected text in grid
- `{{SOURCE_TEXT}}` - Full source segment text
- `{{SOURCE_LANGUAGE}}` - Project source language (e.g., "Dutch")
- `{{TARGET_LANGUAGE}}` - Project target language (e.g., "English")
- `{{DOCUMENT_CONTEXT}}` - Formatted list of project segments (configurable %)

**Access:**
- Open Prompt Manager
- Click "📝 Placeholders" tab
- View table with all placeholders, descriptions, and examples

**Files Modified:**
- `modules/unified_prompt_manager_qt.py` - Added `_create_placeholders_tab()` method, added tab to sub-tabs

---

## 🐛 Prompt Save Crash Fix (v1.9.125) - January 19, 2026

**Fixed Critical Crash When Saving Prompts**

Fixed unhandled exception that caused the application to crash silently when saving prompts:

**The Problem:**
- Changing text in a prompt and clicking save would crash the app
- No error message displayed - just "Unhandled Python exception"
- Users couldn't save their prompt changes

**The Fix:**
- Wrapped entire `_save_current_prompt()` method in comprehensive try/except block
- Added detailed error logging with full stack trace
- Now shows user-friendly error dialog with actual error message
- Logs error to console and session log for debugging

**Error Handling:**
```python
try:
    # All save logic...
except Exception as e:
    # Log full traceback
    # Show error dialog to user
    # Prevent silent crash
```

**Files Modified:**
- `modules/unified_prompt_manager_qt.py` - Added comprehensive error handling to prompt save

---

## 📄 QuickMenu Document Context Support (v1.9.124) - January 19, 2026

**QuickMenu Prompts Can Now Access Full Document Context**

Major enhancement allowing QuickMenu prompts to access the entire project's source segments for context-aware AI suggestions:

**New Placeholder:**
- `{{DOCUMENT_CONTEXT}}` - Inserts formatted list of project segments (source + target)

**Configurable Context:**
- Slider in Settings → AI Settings → QuickMenu Document Context (0-100%)
- Default: 50% of project segments
- Safety limit: Maximum 100 segments to prevent token overload
- 0% disables document context

**Format:**
```
=== DOCUMENT CONTEXT ===
(Showing 250 of 500 segments - 50%)

[1] Technical defect
    → Technisch mankement

[2] Manufacturing process
    → Fabricageproces

...
```

**Example Use Case:**
```
{{DOCUMENT_CONTEXT}}

Suggest the best possible translation of "{{SELECTION}}" from {{SOURCE_LANGUAGE}} to {{TARGET_LANGUAGE}} within the context of the current project shown above.
```

**Benefits:**
- ✅ AI understands project domain and terminology
- ✅ Consistent translations across the document
- ✅ Better handling of ambiguous terms
- ✅ Context-aware suggestions for specialized fields

**Files Modified:**
- `Supervertaler.py` - Added `_build_quickmenu_document_context()`, enhanced `_quickmenu_build_custom_prompt()`, added UI settings

---

## 🤖 QuickMenu Generic AI Support (v1.9.123) - January 19, 2026

**QuickMenu Now Supports Any AI Task**

Fixed critical bug where QuickMenu prompts were being forced into translation mode, preventing generic AI tasks from working correctly:

**The Problem:**
- QuickMenu was calling `client.translate()` with the selected text as input
- This forced the LLM to interpret every prompt as a translation task
- Generic prompts like "Explain this", "Define the selection", "Suggest four translations" would fail
- The AI would try to translate the prompt itself instead of executing it

**The Fix:**
- Changed to use generic AI completion pattern (empty text + custom_prompt)
- Simplified prompt builder to not add translation-specific instructions
- QuickMenu prompts now work as intended for ANY task

**What You Can Do Now:**
- ✅ **Explain this** - Get explanations of technical terms
- ✅ **Define the selection** - Quick dictionary lookups
- ✅ **Suggest four translations** - Multiple translation options with context
- ✅ **Analyze tone** - Check if translation matches source tone
- ✅ **Search for examples** - Find usage examples
- ✅ **Any custom prompt** - QuickMenu works for any AI task

**Example QuickMenu Prompts:**
```
Explain {{SELECTION}} in simple terms.

Suggest four possible translations of "{{SELECTION}}" from {{SOURCE_LANGUAGE}} to {{TARGET_LANGUAGE}} within the context of patent law.

Define {{SELECTION}} and provide usage examples.
```

**Technical Changes:**
- `run_grid_quickmenu_prompt()`: Now uses empty text with custom_prompt for generic completion
- `_quickmenu_build_custom_prompt()`: Simplified to generic prompt builder (removed translation-specific wrappers)
- Supports placeholders: `{{SELECTION}}`, `{{SOURCE_TEXT}}`, `{{SOURCE_LANGUAGE}}`, `{{TARGET_LANGUAGE}}`

**Files Modified:**
- `Supervertaler.py` - Fixed QuickMenu AI execution to support generic tasks

---

## ⌨️ Ctrl+N Repurposed for Quick Notes (v1.9.122) - January 19, 2026

**Faster Note-Taking Workflow**

Repurposed Ctrl+N keyboard shortcut from "New Project" to "Focus Segment Note Tab" for a more useful translation workflow:

**What Changed:**
- **Ctrl+N**: Now switches to the Segment Note tab and focuses the editor
- **New Project**: No longer has a keyboard shortcut (create via File menu)

**Why This Change:**
- Users rarely create new projects via keyboard
- Quick note-taking is much more common during translation
- Faster workflow: Press Ctrl+N, start typing your note immediately

**How It Works:**
1. Press Ctrl+N while translating in the grid
2. The "📝 Segment note" tab (below the grid) is selected
3. Cursor automatically placed in the notes editor
4. Start typing your note right away

**Use Cases:**
- Add context about difficult terms
- Note translation concerns for later review
- Add research notes or URLs
- Document translator decisions

**Files Modified:**
- `modules/shortcut_manager.py` - Changed `file_new` default to empty, added `editor_focus_notes` with Ctrl+N
- `Supervertaler.py` - Added `focus_segment_notes()` handler method

---

## 🐛 Find & Replace Performance Fix (v1.9.121) - January 19, 2026

**Critical Fix: Actually Fast Now!**

Fixed v1.9.120 optimization that accidentally made Find & Replace slower by calling `load_segments_to_grid()` which recreates all widgets.

**The Problem in v1.9.120:**
- Replace All took 37-39 seconds for 12 replacements (worse than before!)
- Root cause: `load_segments_to_grid()` recreates all 755 QTextEdit widgets (23 seconds)
- My optimization batched UI updates but then destroyed the performance by rebuilding everything

**The Real Fix in v1.9.121:**
- Update only the affected cells in-place using `cellWidget().setPlainText()`
- No widget recreation - just update the text content
- Track which rows were modified and update only those
- Batch operations update all target cells efficiently

**Performance Results:**
- Replace operations should now be near-instant (<1 second)
- No more 20+ second widget recreation delays
- Same results, but actually fast this time

**Files Modified:**
- `Supervertaler.py` - Fixed `replace_all_matches()` and `_fr_run_set_batch()` to update cells in-place

---

## ⚡ Find & Replace Speed Optimization (v1.9.120) - January 19, 2026

**Note:** This version had a bug that made performance worse. Use v1.9.121 instead.

**Massive Performance Improvement for Find & Replace Operations**
- Track which rows were modified and update only those
- Batch operations update all target cells efficiently

**Performance Results:**
- Replace operations should now be near-instant (<1 second)
- No more 20+ second widget recreation delays
- Same results, but actually fast this time

**Files Modified:**
- `Supervertaler.py` - Fixed `replace_all_matches()` and `_fr_run_set_batch()` to update cells in-place

---

## ⚡ Find & Replace Speed Optimization (v1.9.120) - January 19, 2026

**Note:** This version had a bug that made performance worse. Use v1.9.121 instead.

**Massive Performance Improvement for Find & Replace Operations**

Optimized Find & Replace to be dramatically faster, especially when making many replacements:

**The Problem:**
- Replace All operations could take 5-10 seconds when making many replacements
- UI was updated for **every single replacement**, causing hundreds of redraws
- After all replacements, the entire grid was reloaded, recreating ALL widgets
- No pre-filtering - all segments were processed even if they didn't contain the search text

**Optimizations Implemented:**

1. **Batch UI Updates**: Wrap replacements in `setUpdatesEnabled(False)` to prevent redraws during processing
   - Before: Update grid after each replacement (hundreds of redraws)
   - After: Single grid reload at the end

2. **Pre-Filter Segments**: Quick text search to skip segments that don't contain the search text
   - Before: Process all 500+ segments with regex operations
   - After: Only process segments that might match (case-insensitive substring check)

3. **Removed Individual Item Updates**: No longer calls `item.setText()` for each replacement
   - Before: Update each cell individually, then reload entire grid
   - After: Just reload grid once at the end

**Performance Results:**
- Operations that took 5-10 seconds now complete in under 1 second
- Batch F&R operations also optimized with same improvements
- No functional changes - same results, just much faster

**Files Modified:**
- `Supervertaler.py` - Optimized `replace_all_matches()`, `_fr_run_set_batch()`, `_execute_single_fr_operation()`

---

## ⌨️ Alt+D Dictionary Shortcut (v1.9.119) - January 19, 2026

**Quick Dictionary Addition from Grid**

Added Alt+D keyboard shortcut for quickly adding words to the custom dictionary without using the right-click menu:

**How It Works:**
- Place cursor on any misspelled word in the target cell
- Press Alt+D
- Word is instantly added to custom dictionary
- Red underline removed and highlighting refreshed across all cells

**Benefits:**
- Faster workflow - no need to right-click and select from menu
- Particularly useful when translating technical documents with many specialized terms
- Works exactly like the context menu "Add to Dictionary" but with a single keystroke

**Files Modified:**
- `modules/shortcut_manager.py` - Added `editor_add_to_dictionary` shortcut definition
- `Supervertaler.py` - Added `add_word_to_dictionary_shortcut()` handler method
- `Supervertaler.py` - Registered Alt+D shortcut in `setup_global_shortcuts()`

**Usage:**
1. See a red underline on a word in the target cell
2. Click to place cursor on that word
3. Press Alt+D
4. Word added to dictionary, underline removed

---

## 🐛 Termview Glossary Punctuation Fix (v1.9.118) - January 19, 2026

**Fixed Glossary Punctuation Matching in Termview Widget**

Completed the glossary punctuation fix by applying it to the Termview widget:

**The Problem:**
- v1.9.117 fixed punctuation matching in the Translation Results panel
- But the Termview widget has its own separate matching logic that wasn't fixed
- Glossary entry: `"...problemen."` (with period) wouldn't show in Termview
- Same entry without period: `"...problemen"` worked correctly

**The Fix:**
- Applied the same punctuation normalization fix to `get_all_termbase_matches()` in `termview_widget.py`
- Now strips trailing/leading punctuation from glossary terms before pattern matching
- Both Translation Results panel AND Termview now handle punctuation correctly

**User Impact:**
- Users can add full sentences/phrases to glossaries with punctuation
- Termview now shows matches for entries with periods, quotes, etc.
- Consistent behavior between Translation Results and Termview

**Files Modified:**
- `modules/termview_widget.py` - Added punctuation stripping to `get_all_termbase_matches()` (line ~933-947)

---

## 🐛 Glossary Matching with Punctuation (v1.9.117) - January 19, 2026

**Fixed Critical Glossary Matching Bug**

Glossary entries with trailing punctuation (periods, quotes, etc.) now match correctly in source text:

**The Problem:**
- Glossary entry: "De huidige uitvinding beoogt een oplossing te vinden voor tenminste enkele van bovenvermelde problemen." (with period)
- Source text: "De huidige uitvinding beoogt een oplossing te vinden voor tenminste enkele van bovenvermelde problemen."
- **Result**: No match! ❌

**Root Cause:**
- Tokenization stripped punctuation from source text words: "problemen." → "problemen"
- But glossary matching used the original entry WITH punctuation: "...problemen."
- Match failed because "problemen" ≠ "...problemen."

**The Fix:**
- Now strips trailing/leading punctuation from **both** source text AND glossary entries before matching
- Normalized term: "...problemen." → "...problemen"
- Source text: "...problemen." → "...problemen"
- **Result**: Match succeeds! ✅

**User Impact:**
- Users can now add full sentences to glossaries without worrying about punctuation
- Entries work correctly whether they have periods, quotes, or other punctuation at the end
- More natural workflow - copy/paste sentences directly into glossaries

**Files Modified:**
- `Supervertaler.py` - Added punctuation stripping to `find_termbase_matches_in_source()` (line ~31029-31031)

---

## 🐛 Fixed ALL Tab Navigation + Startup Tab (v1.9.116) - January 19, 2026

**What Was Wrong:**
The v1.9.115 fix for the "API Keys Missing" dialog navigation was incomplete - it still went to AutoFingers! The root cause was that `_go_to_settings_tab()` was using the **wrong tab index**.

**The Real Problem:**
When the Prompt Manager tab was added to the main tab bar, it shifted ALL subsequent tab indices:
- **Before**: Grid=0, Resources=1, Tools=2, Settings=3
- **After**: Grid=0, Resources=1, **Prompt Manager=2**, Tools=3, Settings=4

But `_go_to_settings_tab()` was still using index 3 (which is now Tools, not Settings)!

**What Was Fixed:**
- ✅ `_go_to_settings_tab()` now uses correct index 4 (Settings)
- ✅ "API Keys Missing" dialog now correctly navigates to Settings → AI Settings
- ✅ Navigate To menu items updated (Prompt Manager, Tools, Settings)
- ✅ First-run welcome dialog navigation fixed
- ✅ AutoFingers navigation fixed
- ✅ Superlookup hotkey handler fixed
- ✅ **Startup tab now Grid** (index 0) instead of Tools/AutoFingers

**User-Facing Changes:**
1. When you start Supervertaler, it now opens to the **Grid tab** (empty or with your last project)
2. "API Keys Missing" dialog **actually works** now - takes you to Settings → AI Settings
3. All menu navigation items work correctly

**Files Modified:**
- `Supervertaler.py` - Fixed 8+ locations with incorrect tab indices, added startup tab initialization

---

## 🐛 API Keys Dialog Navigation Fix (v1.9.115) - January 19, 2026

**Fixed "API Keys Missing" Dialog Navigation**

When users start Supervertaler without configured API keys, they see a dialog asking "Would you like to configure them now?" Clicking "Yes" should take them to Settings → AI Settings where they can configure their keys.

**The Bug:**
- Dialog navigation went to **Tools → AutoFingers** instead of **Settings → AI Settings**
- Users couldn't find where to configure their API keys

**The Fix:**
- Updated `_go_to_settings_tab()` to accept optional `subtab_name` parameter
- Dialog now calls `_go_to_settings_tab("AI Settings")` to navigate directly to the AI Settings sub-tab
- Users are now taken to the correct location where API keys can be configured

**Files Modified:**
- `Supervertaler.py` - Updated `_go_to_settings_tab()` method and API Keys Missing dialog handler

---

## 🔍 AI Assistant Diagnostic Logging (v1.9.114) - January 19, 2026

**Improved AI Assistant Troubleshooting**

Added diagnostic logging to help users troubleshoot API key issues:

- **Key Discovery Logging**: When AI Assistant initializes, it now logs which API keys were found
  - Example: "🔑 Found API keys for: openai, google, deepl"
  - If no keys found: "⚠ No API keys found in api_keys.txt"
  
- **Helps Diagnose Issues**:
  - Users can verify their API keys are being loaded correctly
  - Makes it clear which providers are available
  - Doesn't expose actual key values (only key names)

**Why This Helps**:
- User reported AI Assistant showing "not available" despite having keys configured
- New logging will show exactly which keys are detected
- Makes troubleshooting much easier for both users and developers

**Files Modified:**
- `modules/unified_prompt_manager_qt.py` - Added API key discovery logging to `_init_llm_client()`

---

## 🔑🔐 API Key Loading System Unified (v1.9.113) - January 19, 2026

**Unified API Key Loading with Dev-First Priority**

Consolidated the confusing multi-path API key loading system into a single, clear dual-path approach that fixes AI Assistant bug #107:

**The Problem:**
- Three different API key file locations existed (root, user_data, user_data_private)
- Two different loading mechanisms (`Supervertaler.load_api_keys()` vs `llm_clients.load_api_keys()`)
- Conflicting instructions in example files
- **AI Assistant bug (#107)**: Keys worked for translation but failed for AI Assistant with "Incorrect API key" error

**The Solution:**
- **Unified loading in main app**: `load_api_keys()` now checks TWO locations with clear priority
  1. `user_data_private/api_keys.txt` (Dev mode - gitignored, never uploaded to GitHub)
  2. `user_data/api_keys.txt` (User mode - ships with app)
- **AI Assistant fixed**: Now uses `parent_app.load_api_keys()` instead of module function
- **Example files updated**: Both example files now give consistent, clear instructions

**Developer Workflow:**
- Store keys in `user_data_private/api_keys.txt`
- Fully gitignored - safe from accidental commits
- All features find keys here (translation, AI Assistant, tests)

**User Workflow:**
- Keys go in `user_data/api_keys.txt`
- App auto-creates this location on first run
- Simple, single location

**Files Modified:**
- `Supervertaler.py` - `load_api_keys()` method now checks dev path first (line ~39407)
- `api_keys.example.txt` - Updated with dev/user instructions
- `user_data/api_keys.example.txt` - Updated with dev/user instructions
- `README.md` - Updated First Steps with API key setup
- `AGENTS.md` - Updated API Keys section with new dual-path documentation

**Result:**
- ✅ Developers: Keys safe in gitignored location
- ✅ Users: Simple single location
- ✅ AI Assistant: Now works with same keys as translation
- ✅ No more confusion about where to put keys

---

## 🐛 Bug Fixes (v1.9.112) - January 19, 2026

**Filter Pagination Bug Fixed**

Fixed critical bug where Filter Source/Target boxes only searched visible page instead of all segments:

- **The Problem**: When pagination was active, filtering only searched through currently visible rows
- **Root Cause**: Used `self.table.rowCount()` which could be limited by pagination state
- **The Fix**: Now uses `len(segments)` to always search ALL segments in project
- **Result**: Filtering finds matches across entire project regardless of pagination settings

**Bilingual Table Export - Notes Column**

Fixed segment notes not being exported to Supervertaler Bilingual Table DOCX files:

- **The Problem**: Notes column was hardcoded to empty string `cells[4].text = ''`
- **The Fix**: Now properly exports `seg.notes` from each segment
- **Formatting**: 8pt font to match Status column styling
- **Includes**: Proofreading notes (⚠️ PROOFREAD prefix), user notes, all segment annotations

**Grid Column Width Optimization**

Reduced segment ID column width for more compact display:

- **Before**: 55px (unnecessarily wide)
- **After**: 40px (fits up to 3 digits comfortably, readable for 4+ digits)
- **Result**: More horizontal space for Source/Target columns

**Files Modified:**
- `Supervertaler.py` - Fixed `apply_filters()` iteration, notes export, column width

---

## 🔒 Clean Slate Project Imports (v1.9.111) - January 18, 2026

**Automatic Resource Deactivation on New Project Import**

New projects now start with a clean slate - all TMs, glossaries, and Non-Translatable lists are automatically deactivated on import:

- **Auto-Deactivation**: When importing a new project (DOCX, TXT, memoQ, CafeTran, Trados), all existing resources are deactivated
- **Explicit Activation**: Users explicitly activate only the resources they need for each project
- **Prevents Pollution**: Stops unintended resource bleeding across unrelated projects
- **Applied to All Imports**: Works consistently across all 5 import handlers

**User Workflow:**
1. Import new project → All resources deactivated automatically
2. Go to Project Resources tab → Activate needed TMs/glossaries
3. Work on project with only relevant resources active
4. Import next project → Clean slate again

**Technical Implementation:**
- New `_deactivate_all_resources_for_new_project()` method
- Deactivates TMs via `tm_metadata_mgr.deactivate_tm()`
- Deactivates glossaries via `termbase_mgr.deactivate_termbase()`
- Deactivates NT lists via `nt_manager.set_list_active(False)`
- Logs: "📋 New project: All TMs, glossaries, and NT lists deactivated (start clean)"

**Files Modified:**
- `Supervertaler.py` - Added deactivation method, called in all import handlers

---

## 🔄 Superconverter - Format Conversion Hub (v1.9.110) - January 18, 2026

**New Superconverter Tool - Document & Format Conversion**

Introduced a comprehensive format conversion tool under **Tools → Superconverter** with three tabs:

**1. Bilingual Table (Markdown Export)**
- Export project segments as AI-optimized Markdown tables
- Perfect for ChatGPT, Claude, Gemini - renders as clean tables in chat interfaces
- Options: bilingual, source-only, or target-only
- Filters: all segments, untranslated only, or translated only
- Language-tagged rows (e.g., `Dutch:`, `English:`)

**2. Document → Markdown Converter**
- Convert DOCX and TXT documents to structured Markdown
- **Single file conversion**: Choose file, set output location, convert
- **Batch conversion**: Select multiple files, output folder, convert all at once
- **Auto-detects ALL CAPS headings** (e.g., "TECHNISCH DOMEIN" → "## Technisch Domein")
  - Especially useful for patent documents and technical reports
- Preserves Word document structure:
  - Headings (H1-H6) → Markdown headings
  - Bulleted lists → Unordered lists
  - Numbered lists → Ordered lists
  - Paragraphs with proper spacing
  - Bold/italic formatting

**3. TMX Tools (Placeholder)**
- Prepared for future TMX conversion workflows
- More conversion workflows coming soon

**UI/UX Improvements:**
- All 11 Tools tab items now accessible via **Tools menu** for quick navigation
- Removed focus rectangles from buttons for cleaner appearance
- File → Export menu updated: "AI-Readable Format (TXT)" → "AI-Readable Markdown (.md)"

**Technical Details:**
- `create_superconverter_tab()` - New 3-tab converter interface
- `export_bilingual_table_markdown()` - Markdown table export with filters
- `convert_document_to_markdown()` - Single DOCX/TXT conversion
- `batch_convert_documents_to_markdown()` - Batch processing
- `_convert_docx_to_markdown()` - DOCX structure preservation + ALL CAPS detection
- `_convert_txt_to_markdown()` - TXT conversion + ALL CAPS detection
- `_navigate_to_tool()` - Helper for Tools menu navigation

**Files Modified:**
- `Supervertaler.py` - Complete Superconverter implementation (911 line insertions)

---

## 🔧 TMX Language Pair Bug Fix (v1.9.109) - January 18, 2026

**Fixed Critical TMX Import Language Reversal Bug**

**Issue ([#105](https://github.com/michaelbeijer/Supervertaler/issues/105)):** When importing TMX files, the language pair was sometimes reversed (EN-GB → DE-DE imported as DE-DE → EN-GB). This made it impossible to find matches for translated segments.

**Root Cause:** The TMX import code was incorrectly assuming that the FIRST language in the TMX file was the source language and the SECOND was the target language. However, TMX files list languages in arbitrary order (often alphabetically), so this assumption was wrong.

**Fix Implemented:**
- Added language pair selection dialog when importing TMX files
- User now explicitly selects which detected language should be source and which should be target
- Prevents accidental language reversal
- Applies to both "Create new TM from TMX" and "Add to existing TM" workflows

**User Workflow:**
1. Import TMX file
2. Dialog shows all detected languages (e.g., "de-DE, en-GB")
3. User selects: Source = en-GB, Target = de-DE
4. Import proceeds with correct language pair
5. TM matches now work correctly

**Files Modified:**
- `Supervertaler.py` - Added language selection dialog in `_import_tmx_as_tm()` method (2 locations)

---

## 📥📤 memoQ XLIFF Import/Export Support (v1.9.108) - January 18, 2026

**Complete memoQ XLIFF (.mqxliff) Workflow**

Added full import/export support for memoQ XLIFF files - feature was implemented in module but never exposed in UI:

**Implementation:**

1. **Import Menu Item**: File → Import → memoQ XLIFF (.mqxliff)...
   - Opens file dialog for `.mqxliff` files
   - Automatically extracts source segments using `MQXLIFFHandler`
   - Converts ISO language codes to full names (`sk` → `Slovak`)
   - Stores handler and source path for round-trip export

2. **Export Menu Item**: File → Export → memoQ XLIFF - Translated (.mqxliff)...
   - Updates target segments in original XLIFF structure
   - Preserves formatting tags (bpt/ept pairs)
   - Saves translated file with proper namespace handling

3. **Language Code Normalization**:
   - New `_normalize_language_code()` method
   - Converts ISO 639-1/639-2 codes to full language names
   - Supports 30+ languages including Slovak (`sk`, `sk-SK`)

4. **memoQ Bilingual DOCX Language Detection**:
   - Expanded `lang_map` in `import_memoq_bilingual()` from 8 to 24 languages
   - Now includes Slovak, Czech, Hungarian, Romanian, Bulgarian, Greek, Russian, Ukrainian, Swedish, Danish, Finnish, Norwegian, Japanese, Chinese, Korean, Arabic, Turkish, Hebrew
   - Fixes bug where Slovak would default to EN→NL instead of being detected

5. **Project Persistence**:
   - Added `mqxliff_source_path` field to `Project` dataclass
   - Source path saved in `.svproj` files
   - Automatic handler restoration when loading projects

**Round-Trip Workflow:**
1. Export from memoQ as XLIFF
2. Import into Supervertaler
3. Translate segments
4. Export back to XLIFF
5. Import into memoQ

**Files Modified:**
- `Supervertaler.py` - Import/export menu items, methods, language normalization
- `Supervertaler.py` - Project dataclass: added `mqxliff_source_path` field
- `Supervertaler.py` - Project save/load: persist mqxliff_source_path

**GitHub Discussion:**
- https://github.com/michaelbeijer/Supervertaler/discussions/106

---

## ✅ Prompt Library & Superlookup Fixes (v1.9.107) - January 15, 2026

**Prompt Library Improvements**

- **Unified filename and Name field**: Tree and editor now show full `.svprompt` extension
- **File Operations**: Name field edits now rename files on disk (filename = what you see = what you edit)
- **New Prompt Dialog**: Now asks for "filename with extension" and auto-appends `.svprompt` if missing

**Superlookup Navigation Fixes**

- **Fixed Ctrl+K AttributeError**: Removed ~140 lines of orphaned Supermemory code that was causing errors
- **Fixed Ctrl+K Navigation**: Now correctly navigates to Tools tab (index 3) instead of Prompt Manager (index 2)
  - Tab indices shifted when Prompt Manager was added: Tools moved from 2→3

**Files Modified:**
- `modules/unified_prompt_manager_qt.py` - Tree display, editor field, save logic
- `Supervertaler.py` - Removed orphaned Supermemory methods, fixed tab index

---

## 🗑️ Supermemory Removed (v1.9.105) - January 15, 2026

**Major Architectural Change**

- **Removed Supermemory** (vector-indexed semantic search) from the project entirely
- Supermemory did not work reliably in frozen PyInstaller builds due to complex PyTorch native dependencies
- **Recommendation:** Focus development effort on improving the SQLite-based Translation Memory system
  - Better TMX import performance for large files
  - Advanced fuzzy matching algorithms
  - More reliable and faster than vector-based approaches for professional translation workflows

**Removed:**
- `modules/supermemory.py` (2100+ lines)
- Supermemory tab from UI
- Auto-init and cleanup code
- Dependencies: sentence-transformers, chromadb, tokenizers
- ~600 MB from default installation footprint

**Files Modified:**
- `Supervertaler.py` - Removed UI tab, auto-init, cleanup methods
- `modules/feature_manager.py` - Removed supermemory feature definition
- `pyproject.toml` - Removed supermemory pip extra and dependencies
- Build specs updated (CORE/FULL now only differ in Local Whisper inclusion)

## 📦 Packaging: Lighter Default Install (v1.9.104) - January 14, 2026

- Made **Supermemory** an optional install extra again, so the default `pip install supervertaler` no longer pulls the heavy ML stack (PyTorch / sentence-transformers / ChromaDB). Install with `pip install supervertaler[supermemory]` when needed.

## ✅ Filtered Ctrl+Enter + Website Screenshots (v1.9.103) - January 14, 2026

**Bug Fix**

- Fixed Ctrl+Enter confirmation under active **Filter Source/Filter Target**: it now confirms and advances through the *filtered* segments without the grid reverting to an unfiltered view.

**Website**

- Added new screenshots demonstrating the **Compare Panel** (MT + TM) and **Termview**.
- Updated the Prompt Manager screenshot reference.

## ⚡ QuickMenu in the Grid (v1.9.102) - January 14, 2026

- New **⚡ QuickMenu** in the Grid right-click menu (Source + Target cells)
  - Run a prompt and preview the response
  - Run a prompt and replace the selected text / target
- Prompt system update: renamed “Quick Run menu” terminology to **QuickMenu**
- New prompt metadata fields (backward compatible with `quick_run`):
  - `quickmenu_label` (menu label)
  - `quickmenu_grid` (show in Grid right-click QuickMenu)
  - `quickmenu_quickmenu` (show in future app-level QuickMenu)
- UI rename: main tab “📝 Project editor” → “📝 Grid”
**Framework:** PyQt6
**Status:** Active Development

**Note:** For historical information about legacy versions (Tkinter Edition, Classic Edition), see [legacy_versions/LEGACY_VERSIONS.md](legacy_versions/LEGACY_VERSIONS.md).

---

## 🧰 Prompt Library UX + Update Check Reliability (v1.9.101) - January 13, 2026

**Prompt Library**

- Prompt Library folders default to collapsed on first load and preserve expand/collapse + selection across refreshes.
- Added toolbar buttons: **Collapse all** / **Expand all**.
- Fixed **Duplicate** (creates a real copy with a unique “(copy …)” name) and adjusted **New Prompt** to create inside the currently selected folder.
- Added drag-and-drop moves for prompt files and folders (also supports dragging Favorites/Quick Run entries as shortcuts to move the underlying prompt).

**Check for Updates**

- Update check now uses a longer timeout and falls back to the GitHub releases page when `api.github.com` is slow/blocked.
- Fixed the update progress dialog briefly flashing and disappearing on some systems.
- Fixed a rare crash/auto-close issue where the first (API) network reply could finish after the fallback started and prematurely close/delete the active fallback request.

**Website**

- Navbar now includes a GitHub icon link + version pill, with cache-busting query strings for CSS/JS.

---

## ⌨️ Ctrl+Return Works Everywhere (v1.9.100) - January 13, 2026

**Bug Fix: Ctrl+Enter/Ctrl+Return confirmation now reliable**

- Fixed a Windows/Qt routing quirk where `Ctrl+Return` (main keyboard Return) could be swallowed before it reached the grid editor widgets and/or the global `QShortcut`.
- Implemented an application-level event filter that intercepts `Ctrl+Return` and `Ctrl+Enter` and triggers `confirm_selected_or_next()` when focus is in the editor grid context.
- Expanded the same behavior to also work when focus is in the **Filter Source** / **Filter Target** boxes.

---

## 🔊 Compare Panel Shortcuts + Sound Effects (v1.9.99) - January 12, 2026

**Feature: Compare Panel-first quick insert workflow**

- **Alt+0 / Alt+0,0 insertion (Compare Panel)**: Single-tap `Alt+0` inserts the current MT result; double-tap `Alt+0,0` inserts the current TM Target.
- **Full-segment replacement + single undo step**: Insertions replace the entire current target segment, wrapped in a single undo edit block.
- **Compare Panel navigation shortcuts**:
  - MT prev/next: `Ctrl+Alt+Left` / `Ctrl+Alt+Right`
  - TM prev/next: `Ctrl+Alt+Up` / `Ctrl+Alt+Down`
- **Context-aware match shortcuts**: Match navigation/insertion shortcuts now act only on the active panel (Compare Panel vs Translation Results), avoiding hidden-panel side effects.

**Feature: Minimalist sound effects + status-bar feedback**

- Added per-event **Windows sound mapping** (beeps or selected Windows `.wav` files) with a global enable toggle.
- Added **status bar “information bar” messages** for glossary entry add outcomes (added / duplicate / error).
- Sound effects are now **OFF by default** on fresh installs.

**UX: Reduced log spam**

- Collapsed repeated “Saved segment to TM(s)” messages into a single debounced log line with an `(xN)` count.

---

## 📝 Glossary Notes in Tooltips (v1.9.98) - January 11, 2026

**Bug Fix: Glossary Entry Notes Now Display in Tooltips**

Fixed an issue where glossary entry notes were not appearing in tooltips, even though they were correctly saved to the database.

**The Problem:**
- Notes were being saved correctly to the `termbase_terms.notes` database column
- However, when converting termbase matches from dictionary format to list format for display, the `notes` field was being dropped in multiple places
- This meant TermView tooltips and source cell tooltips never received the notes data

**The Fix:**
Fixed 5 locations where glossary notes were being lost:
1. **Cached termbase matches conversion** (lines ~26753-26768): Added `'notes'` field
2. **Fresh termbase matches conversion** (lines ~26835-26850): Added `'notes'` field  
3. **Refresh current segment conversion** (lines ~30420-30438): Added `'notes'` field
4. **TranslationMatch metadata** (lines ~30469-30485): Added `'notes'`, `'term_id'`, `'termbase_id'` to metadata dict

**Result:**
- Glossary entry notes now appear in TermView tooltips when hovering over terms
- Notes also appear in source cell tooltips for highlighted glossary terms
- Full data flow now preserved: Database → TranslationMatch → dict → list → TermBlock → Tooltip

**Also in this release:**
- **WebEngineView cleanup**: Fixed "Release of profile requested but WebEnginePage still not deleted" terminal warnings by properly cleaning up Superlookup web views on app close
- **FAQ update**: Added documentation about embedded browser password/cookie security in Superdocs FAQ
- **Community docs**: Added `CONTRIBUTING.md` and `CODE_OF_CONDUCT.md` (Contributor Covenant v2.1)

---

## 🌐 All MT Providers in Translation Results (v1.9.97) - January 11, 2026

**Feature: Multiple Machine Translation Providers Now Displayed**

The Translation Results panel now shows translations from **all configured MT providers**, not just Google Translate.

**Previously:**
- Only Google Translate was called when navigating to a segment
- DeepL, Amazon Translate, and MyMemory were only available in Batch Translate

**Now:**
- All enabled MT providers are called and displayed progressively
- Each provider's translation appears as it completes
- Provider codes shown: GT (Google), DL (DeepL), AT (Amazon), MM (MyMemory)

**Supported MT Providers:**
| Provider | API Key Required | Notes |
|----------|-----------------|-------|
| Google Translate | Yes | `google_translate` key |
| DeepL | Yes | `deepl` key |
| Amazon Translate | Yes | `amazon_translate` + `amazon_translate_secret` + region |
| MyMemory | Optional | Free tier works without key; email gets higher limits |

**Configuration Tip:**
For MyMemory, you can use your email address as the key to get 10,000 words/day instead of 1,000:
```
mymemory = your.email@example.com
```

**Technical Changes:**
- Expanded `_add_mt_and_llm_matches_progressive()` to call all configured MT providers
- Each provider respects its enabled/disabled state from Settings → MT Settings
- Results displayed immediately as each provider responds

---

## 🛡️ Thread-Safe Logging (v1.9.96) - January 11, 2026

**Bug Fix: Crash When Adding Terms via Alt+Down**

Fixed a critical crash that occurred when adding glossary terms using the Alt+Down quick-add shortcut.

**The Problem:**
- The `log()` method was being called from background worker threads (e.g., termbase batch processor)
- Qt widgets like `status_bar` and `session_log_text` were being accessed from non-main threads
- This violates Qt's threading model and caused: `QObject::killTimer: Timers cannot be stopped from another thread`

**The Fix:**
- Made `log()` method thread-safe using PyQt signals
- Added `_log_signal = pyqtSignal(str)` to `SupervertalerQt` class
- Background threads now emit the signal instead of directly updating UI
- Signal automatically queues to main thread's event loop
- Console logging (`print()`) still works from any thread

**Technical Details:**
- New `_log_to_ui()` internal method handles actual widget updates
- Thread detection via `threading.current_thread() == threading.main_thread()`
- Signal connected with default `AutoConnection` which queues cross-thread calls

**Result:**
- No more crashes when background workers log messages
- Alt+Down quick-add works reliably
- All UI updates properly marshalled to main thread

---

## 🔍 TM Fuzzy Matching Fix (v1.9.95) - January 11, 2026

**Bug Fix: Improved Translation Memory Fuzzy Matching for Long Segments**

Fixed a critical issue where highly similar TM entries were not being found for long segments (especially in patent/technical documents).

**The Problem:**
- FTS5 full-text search uses BM25 ranking which prioritizes entries matching more search terms
- For long segments with many technical compound words, BM25 pushed truly similar entries below the candidate limit
- Example: Two sentences that were 92% similar weren't matching because other entries matched more individual words

**The Fix:**
- Increased the FTS5 candidate pool from 100 to 500 entries
- This ensures similar entries make it into the candidate pool before SequenceMatcher calculates actual similarity
- More candidates = better chance of finding the truly similar matches

**Technical Details:**
- Changed `max(50, max_results * 10)` to `max(500, max_results * 50)` in `search_fuzzy_matches()`
- FTS5 BM25 is great for keyword relevance but needs a larger pool for similarity-based reranking
- The SequenceMatcher then correctly scores the candidates by actual text similarity

**Result:**
- TM fuzzy matches now reliably appear for long technical segments
- 90%+ similar entries are no longer missed due to BM25 ranking artifacts

---

## 🎯 TermView Quick-Insert Shortcuts (v1.9.94) - January 11, 2026

**New Feature: TermView Quick-Insert Shortcuts**

**Note (v1.9.99+):** `Alt+0` / `Alt+0,0` are now reserved for the Compare Panel insertion workflow. TermView shortcuts start at `Alt+1`.

Insert glossary terms directly from TermView using keyboard shortcuts – a novel feature not found in other CAT tools!

- **Alt+1 through Alt+9** – Insert terms 1-9 (displayed as badges 1-9)
- **Double-tap Alt+N,N** – Insert terms 10-18 (displayed as badges 11, 22, ..., 99)
- **18 terms accessible** via quick keyboard shortcuts
- **Visual badges** show shortcut numbers on each term in TermView
- **Smart double-tap detection** – first tap inserts immediately, double-tap within 300ms replaces with the 11-20 term

**Visual Improvements:**
- 🎨 **Unified term styling** – Background color now extends across both translation text and shortcut badge
- 🔵 **Blue number badges** – Clear visual indicators (14px for single digit, 20px for double)
- 💡 **Hover effects** – Entire term block highlights on hover
- 🏷️ **Tooltips** – Show exact shortcut (e.g., "Press Alt+3 to insert" or "Press Alt+3,3 to insert")

**How It Works:**
| Badge | Shortcut | Term # |
|-------|----------|--------|
| 1-9 | Alt+1-9 | 1st-9th terms |
| 11-99 | Alt+1,1 - Alt+9,9 | 10th-18th terms |

---

## ⌨️ Keyboard Shortcuts & Quick Glossary Add (v1.9.93) - January 11, 2026

**New Features:**
- ⚡ **Quick Add to Priority Glossary** – Add terms directly to glossaries by their priority ranking
  - Alt+Up: Add selected term pair to glossary with Priority #1
  - Alt+Down: Add selected term pair to glossary with Priority #2
  - Works with any glossary that has Read enabled and a priority set
  - No dialog required – instant term addition

- 🔧 **Shortcut Enable/Disable** – Disable shortcuts from Settings → Keyboard Shortcuts
  - New "Enabled" checkbox column in shortcuts table
  - Disabled shortcuts fully release their key combinations
  - Released keys can be reassigned to other shortcuts
  - Settings persist between sessions

**Improvements:**
- ✓ **Button Renamed** – "Save & Next" button renamed to "Confirm & Next" to accurately reflect its function
- ⌨️ **Ctrl+Enter Fix** – Ctrl+Enter now correctly handled in target editor cells
- ↵ **Enter Key Behavior** – Plain Enter no longer inserts newlines; use Shift+Enter for line breaks

**Bug Fixes:**
- 🔧 Fixed Ctrl+Enter not working when focus is in target cell
- 🔧 Fixed "Save & Next" button not confirming segments (was only moving to next)
- 🔧 Fixed custom shortcut key bindings not loading at startup

---

## 🔄 F5 Force Refresh & Curly Quote Matching (v1.9.92) - January 10, 2026

**New Feature:**
- ⌨️ **F5 Force Refresh** – Press F5 to force refresh all glossary and TM matches for the current segment
  - Clears all caches (termbase cache, translation matches cache)
  - Re-searches all connected glossaries and translation memories
  - Updates TermView, Translation Results panel, and grid highlighting with fresh results
  - Useful when glossary changes aren't immediately reflected

**Bug Fixes:**
- 🔤 **Curly Quote Matching Fix** – Single-word glossary terms now correctly match when surrounded by curly quotes
  - Terms like `"omvatten",` or `„word"` now match their glossary entries
  - Comprehensive Unicode quote handling: `"`, `"`, `„`, `«`, `»`, `'`, `'`, `‚`, `‹`, `›`
  - Text normalized by replacing all quote variants with spaces before regex matching

**Improvements:**
- 🏷️ **TermView Tag Stripping** – CAT tool tags now stripped from TermView display for cleaner appearance
  - Handles `<b>`, `<i>`, memoQ `{1}`, `[2}`, Trados `<1>`, Déjà Vu `{00001}`
- 📝 **Find/Replace Field Behavior** – Selected text now always goes to Find field, never to Replace field
- 🔄 **TermView Cache Updates** – TermView now updates from cache when navigating segments (not just TM/MT results)

---

## 🎯 Déjà Vu X3 Bilingual RTF Support (v1.9.91) - January 10, 2026

**New CAT Tool Integration:**
- 📄 **Déjà Vu X3 RTF Import** – Import bilingual RTF files exported from Déjà Vu X3
  - Parses 4-column table format (ID | Source | Target | Comments)
  - Automatic language detection from RTF language codes (60+ languages supported)
  - Segment IDs preserved for round-trip workflow
- 📤 **Déjà Vu X3 RTF Export** – Export translations back to RTF format
  - Translations inserted with proper RTF formatting
  - Unicode characters properly encoded (`\uNNNN?` format)
  - Target language codes applied automatically
  - Balanced RTF brace structure maintained
- 🏷️ **Déjà Vu Tag Support** – Inline tags `{00108}` highlighted in pink
  - Pattern: `{NNNNN}` (5-digit numbers)
  - Tags preserved through translation workflow
- 🔄 **Full Round-Trip Workflow**:
  1. Export bilingual RTF from Déjà Vu X3
  2. Import into Supervertaler (File → Import → Déjà Vu Bilingual RTF)
  3. Translate using AI, TM, or manual editing
  4. Export back to RTF (File → Export → Déjà Vu Bilingual RTF)
  5. Reimport into Déjà Vu X3

**New Module:**
- `modules/dejavurtf_handler.py` – Complete Déjà Vu X3 RTF parser (~800 lines)
  - `DejaVuSegment` dataclass for segment data
  - `DejaVuRTFHandler` class with load/save methods
  - RTF text encoding/decoding utilities
  - Language code mapping for 60+ languages

**Technical Implementation:**
- RTF parsing uses regex patterns for `\cell` markers and segment IDs
- Segment IDs extracted via pattern `insrsid\d+\s+(\d{7})\}`
- Language detection uses `Counter` to find most common `\lang` codes
- Export inserts formatted RTF groups with proper brace balancing
- Project persistence: `dejavu_source_path`, `dejavu_segment_id`, `dejavu_row_index`

**Files Modified:**
- `Supervertaler.py` – Menu items, import/export methods, TagHighlighter pattern
- `modules/dejavurtf_handler.py` – NEW handler module

---

## 🐛 Bug Fixes (v1.9.89) - January 9, 2026

**Critical Bug Fixes:**
- 🔧 **Translation Results Zoom Persistence** – Fixed font size settings not being restored when loading projects (typo: `set_compare_font_size` → `set_compare_box_font_size`)
- 🎨 **Border Thickness Spinbox** – Fixed arrows not appearing in Target Cell Focus Border thickness control
  - Removed problematic stylesheet that was hiding buttons
  - Increased maximum thickness from 5px to 10px
  - Made spinbox wider (90px) to accommodate larger values
- 🌍 **Language Pair Memory** – Fixed DOCX import defaulting to EN→NL instead of remembering last used language pair
  - Now remembers last imported language pair across sessions
  - Falls back to current project languages if available
  - User report: "Whatever I do... the language is detected always as EN-NL!" → FIXED

**Files Modified:**
- `Supervertaler.py` – All bug fixes implemented

---

## 🔍 Context Menu Enhancement (v1.9.88) - January 9, 2026

**Superlookup Integration in Context Menus (NEW):**
- 🔍 **Quick Concordance Search** – Right-click selected text in source or target cells to instantly search in Superlookup
- 📋 **Context Menu Item** – New "🔍 Search in Superlookup (Ctrl+K)" option appears when text is selected
- 🎯 **Smart Navigation** – Automatically opens Superlookup tab and triggers search
- 🌍 **Language-Aware** – Passes project language pair to Superlookup for filtered results
- 📊 **Vertical View** – Uses traditional concordance list layout for search results
- ⚡ **Unified Search** – Searches TM, glossaries, Supermemory, MT, and web resources simultaneously
- 🔄 **Works Everywhere** – Available in both source (read-only) and target (editable) cells

**Workflow Benefits:**
- Select any term → Right-click → Instant concordance search (no need to open Superlookup first)
- Perfect for terminology research while translating
- Complements existing Ctrl+K keyboard shortcut

---

## ⚡ Workflow Enhancements & UI Polish (v1.9.87) - January 9, 2026

**Auto-Confirm 100% TM Matches (NEW):**
- 🎯 **Intelligent Auto-Confirmation** – When pressing Ctrl+Enter, automatically inserts, confirms, and skips segments with perfect TM matches
- 🔄 **Recursive Processing** – Continues through multiple 100% matches until finding a segment requiring manual work
- 🛡️ **Safety Check** – Only auto-confirms segments with empty targets (won't overwrite existing translations)
- ⚡ **Hash-Based Lookup** – Uses instant MD5 hash matching for O(1) performance
- 📊 **TM Integration** – Auto-confirmed segments automatically saved to active Translation Memories
- ⚙️ **Optional Setting** – Enable/disable in General Settings → TM/Glossary section
- 📝 **Session Logging** – Clear logs show: Found match → Auto-confirmed → Skipped to next

**Tab Layout Customization (NEW):**
- 📐 **Flexible Tab Position** – Move Termview and Session Log tabs above or below the grid
- ⚙️ **View Settings Toggle** – New "📐 Tab Layout" section with checkbox: "Show Termview/Session Log tabs above grid"
- 💾 **Persistent Setting** – Preference saved and restored between sessions
- 🔄 **Easy Switching** – Close and reopen project tab to apply layout change

**Grid & UI Improvements:**
- 📏 **Wider Segment Column** – Segment # column increased from 35px to 55px (fits 4-digit segment numbers up to 9999)
- 🎯 **Auto-Center Fix** – "Keep Active Segment Centered" setting now persists between restarts
- 🎨 **Badge Text Color** – Changed from black to dark gray (#333333) for better appearance on bright match backgrounds
- 🎨 **Color Customization** – New badge text color picker with 8 preset colors and custom selection
- 🔍 **Settings Rename** – "View/Display" tab renamed to "View Settings" for clarity

**Technical Improvements:**
- Navigation logic now uses exact match lookup for 100% TM matches (not fuzzy search)
- Pagination handling in auto-confirm recursion (switches pages when needed)
- Status icon updates and project modification flag management
- Settings persistence through general_settings.json

---

## 🔧 Glossary Quality Improvements (v1.9.86) - January 9, 2026

**Enhanced Glossary Management:**
- 🚫 **Duplicate Prevention** – Cannot save duplicate source→target pairs to a glossary
- 🎯 **Priority-Based Filtering** – If identical match exists in multiple glossaries, only highest priority version shown
- ⚖️ **Font Normalization** – TermView source and target text now use same font size
- 🔍 **Smart Filtering** – Duplicate filtering applied throughout: grid highlighting, Translation Results, TermView, and Superlookup
- ⚠️ **User Feedback** – Clear warning dialog when attempting to add duplicate terms

**Technical Changes:**
- Added case-insensitive duplicate check in `termbase_manager.py` before inserting terms
- `add_term()` now returns `None` if duplicate detected (graceful handling)
- Duplicate filtering in `find_termbase_matches_in_source()` prevents multiple sources from showing duplicates
- TermView target font size matches source font size (was 2pt smaller)

---

## ✅ AI Proofreading System (v1.9.85) - January 7, 2026

**Intelligent Translation Quality Verification:**
- 🔍 **Batch Proofreading** – LLM analyzes translations for errors, inconsistencies, and quality issues
- 📝 **Issue Tracking** – Problems stored in Notes field with `⚠️ PROOFREAD:` prefix
- 📊 **Results Table** – View all segments with issues, double-click to navigate
- 🎯 **Advanced Filters** – New "Has proofreading issues" filter option
- 🟠 **Visual Indicators** – Orange highlight on status icons for segments with proofreading notes
- 🧹 **Clear Operations** – Bulk clear all proofreading notes or clear individual segments
- ⚡ **Batch Processing** – Efficient API calls (20 segments per request)
- 📈 **Progress Dialog** – Real-time stats during proofreading operation

**Access Points:**
- Edit → Batch Operations → ✅ Proofread Translation...
- View → ✅ Proofreading Results...
- Right-click → ✅ Clear Proofreading Notes

---

## 📐 Subscript & Superscript Support (v1.9.84) - January 7, 2026

**New Formatting Tags:**
- ⬇️ **Subscript** – `<sub>` tags for subscript text (e.g., P<sub>totaal</sub>)
- ⬆️ **Superscript** – `<sup>` tags for superscript text (e.g., m<sup>2</sup>)

**Full Pipeline Support:**
- 📥 **Import** – Subscript/superscript preserved from DOCX files as `<sub>`/`<sup>` tags
- 📤 **Export** – Tags converted back to real Word subscript/superscript formatting
- 🎨 **Preview** – Document Preview renders actual subscript/superscript positioning

**Technical Details:**
- Updated `modules/tag_manager.py` with sub/sup support in FormattingRun dataclass
- TAG_PATTERN regex extended to match `<sub>` and `<sup>` tags
- DOCX handler applies `run.font.subscript` and `run.font.superscript` on export

---

## 📝 Notes Tab & Status Indicator (v1.9.83) - January 6, 2026

**Notes Tab in Translation Results Panel:**
- 📝 **TM Info + Notes Tabs** – Translation Results panel now has tabbed interface
- 💾 **TM Info Tab** – Shows TM match details when a match is selected
- ✏️ **Notes Tab** – Add/edit notes for each segment
- 🔄 **Auto-Save** – Notes save automatically as you type
- 📂 **Persistence** – Notes saved to .svproj project file

**Notes Indicator on Status Icon:**
- 🟠 **Orange Highlight** – Status icon (✓/✗) gets orange background when segment has notes
- 🎯 **Compact Design** – No separate icon cluttering the status cell
- 💬 **Tooltip** – Hover over status cell to see notes preview
- 📏 **Narrower Status Column** – Reduced from 120px to 70px for cleaner look

**UI Cleanup:**
- 🗑️ **Removed Comments Tab** – Redundant tab under grid removed (Notes tab replaces it)
- 🧹 **Cleaner Layout** – Only Termview and Session Log tabs remain under grid

---

## 🤖 Export for AI (v1.9.82) - January 5, 2026

**New Export Format:**
- 🤖 **AI-Readable Format** – New export option in File → Export menu
- 📝 **[SEGMENT XXXX] Format** – Outputs clean numbered segments with language labels
- 🌐 **Language Codes** – Auto-detects project languages (NL, EN, DE, etc.)
- ⚙️ **Configurable** – Customizable language codes, start number, zero padding

**Export Options:**
- 🔄 **Content Modes** – Bilingual (source+target), Source only, Target only
- 📊 **Segment Filters** – All segments, Untranslated only, Translated only
- 👁️ **Live Preview** – See format preview before exporting

**Use Cases:**
- 🧠 **AI Translation** – Export source-only for ChatGPT/Claude translation
- 🔍 **AI Review** – Export bilingual for AI quality review
- 📎 **Easy Parsing** – Simple format for automated processing

---

## 🔍 Superlookup UX Improvements (v1.9.81) - January 4, 2026

**Search History Dropdown:**
- 📜 **History Dropdown** – Superlookup search box now shows last 20 searches in dropdown
- 💾 **Persistent History** – Saved to `user_data/superlookup_history.json`
- ⌨️ **Editable Combo** – Type to search, click dropdown for history

**Resizable Sidebar:**
- ↔️ **QSplitter** – Web Resources sidebar now resizable (120-250px range)
- 📏 **No Text Cutoff** – Resource buttons properly visible at all widths

**UI Polish:**
- 🎯 **Focus Rectangles Removed** – Global stylesheet removes ugly focus outlines from all buttons
- 🟢 **Styled Radio Buttons** – Replaced 5 plain QRadioButton instances with CheckmarkRadioButton
- 🐛 **External Mode Fix** – External browser mode now correctly triggers web search

---

## 💻 GitHub Code Search (Beijerterm) in Superlookup (v1.9.80) - January 4, 2026

**New Web Resource:**
- 💻 **GitHub Code (Beijerterm)** – Search Beijerterm terminology repo directly from Superlookup
- 🔗 **Search URL** – `https://github.com/search?q={query}+repo:michaelbeijer/beijerterm&type=code`
- 📚 **Source Files** – Search YAML glossary files, Markdown documentation
- ✨ **Renamed** – "GitHub Code" → "GitHub Code (all)" for clarity

---

## 📚 Beijerterm Integration in Superlookup (v1.9.79) - January 4, 2026

**New Web Resource:**
- 📚 **Beijerterm** – Added to Superlookup's Web Resources tab (replaces old michaelbeijer.co.uk wiki)
- 🔗 **Search URL** – `https://michaelbeijer.github.io/beijerterm/?q={query}`
- 📊 **500k+ Terms** – Dutch-English terminology database with 583,000+ term entries
- ⚡ **URL Search** – Beijerterm now supports `?q=searchterm` for programmatic search integration

---

## 🔍 Find & Replace History & Batch Sets (v1.9.78) - January 4, 2026

**F&R History Dropdowns:**
- 📜 **History Dropdowns** – Find and Replace fields now have dropdown arrows showing last 20 searches
- 💾 **Persistent History** – Search/replace terms saved to `user_data/find_replace_history.json`
- 🔽 **Quick Access** – Click dropdown arrow or type to filter previous search terms

**F&R Sets (Batch Operations):**
- 📁 **F&R Sets Panel** – Collapsible panel for creating and managing batch replace operations
- ▶️ **Batch Operations** – Run multiple find/replace operations with a single click
- ➕ **Add to Set** – Save current find/replace values to a named set
- 📥📤 **Import/Export** – Save F&R sets as `.svfr` files for sharing or backup
- 🖱️ **Double-click** – Double-click any operation in a set to load it into the dialog

**New Module (`modules/find_replace_qt.py`):**
- `FindReplaceHistory` – Manages and persists recent search/replace terms
- `FindReplaceOperation` – Dataclass for single F&R operation (find, replace, options)
- `FindReplaceSet` – Collection of operations that can be saved/loaded
- `FindReplaceSetsManager` – QWidget UI for managing F&R sets with tables
- `HistoryComboBox` – Editable combo box with history dropdown

---

## 💻 GitHub Code Search in Superlookup (v1.9.77) - January 4, 2026

**New Web Resource:**
- 💻 **GitHub Code Search** – Added to Superlookup's Web Resources tab
- 🔗 **Search URL** – `https://github.com/search?q={query}&type=code`
- 🎯 **Use Case** – Search for terms/code across all public GitHub repositories
- ✨ **Great for** – Finding how technical terms are used in real code, locating terminology in open-source projects

---

## 🎉 Onboarding, Spellcheck & Project Info (v1.9.76) - January 3, 2025

**First-Run Welcome for New Users:**
- 🎉 **Welcome Dialog** – Shows on first launch explaining modular pip extras
- 📦 **Auto-Navigate** – Opens Settings → Features tab automatically to show installed/missing features
- ✅ **Don't Show Again** – Checkbox uses standard green CheckmarkCheckBox style
- 🐛 **Bug Fix** – First-run flag now saves to correct file (ui_preferences.json)

**Free vs Paid LLM Pricing Info:**
- 💰 **Info Box in AI Settings** – Clear pricing information at top of Settings → AI Settings
- 🆓 **Google Gemini** – FREE tier (15 req/min, 1M tokens/day)
- 🖥️ **Ollama** – 100% FREE (runs locally on your computer)
- 💳 **OpenAI/Claude** – Paid API only (no free tier)
- ⚠️ **Important Note** – Clarifies that ChatGPT Plus and Claude Pro web subscriptions do NOT include API access

**Spellcheck System Overhaul:**
- 🔤 **Spylls Backend** – Replaced `cyhunspell` with `spylls` (pure Python Hunspell), fixing Windows/Python 3.12 compatibility
- 🌍 **Language Variants** – Dropdown now shows "English (US)", "English (GB)", "Portuguese (BR)" etc.
- 📁 **Subdirectory Search** – Finds dictionaries in subfolders like `dictionaries/en/en_GB.dic`
- ✅ **Regional Spelling Works** – "colour" correct in en_GB, incorrect in en_US (and vice versa)
- 📋 **Improved Spellcheck Info Dialog** – Three backends displayed separately, active one highlighted green, bundled dictionary info, project links section

**Project Info Dialog (NEW):**
- 📋 **File → Project Info...** – New menu item to view comprehensive project information
- 📊 **Statistics** – Segment counts, word counts, character counts, progress percentage
- 📁 **Source Files** – Shows original DOCX, memoQ, CafeTran, Trados paths
- 🔧 **Resources** – Active prompt, TMs, glossaries, spellcheck settings

---

## 📦 Modular Architecture (v1.9.75) - January 2, 2025

**Major new feature: Install only the features you need!**

- 📦 **Modular Installation System** – Users can now choose which features to install, reducing disk space from ~1.2 GB (full) to ~300 MB (core only). Heavy dependencies like sentence-transformers, chromadb, and PyQt6-WebEngine are now optional.
- ⚙️ **Settings → Features Tab** – New settings page showing which optional features are installed (✅) vs not installed (❌), with size estimates and pip install commands for each.
- 🔧 **Feature Manager Module** – New `modules/feature_manager.py` provides `FeatureManager` class, `FEATURE_MODULES` definitions, and lazy import helpers for conditional loading.
- 📋 **pip Extras Support** – Install specific features with `pip install supervertaler[supermemory,voice,web]` or everything with `pip install supervertaler[all]`.

**Installation Options:**
| Command | Size |
|---------|------|
| `pip install supervertaler` | ~300 MB (core) |
| `pip install supervertaler[supermemory]` | +600 MB |
| `pip install supervertaler[voice]` | +150 MB |
| `pip install supervertaler[web]` | +100 MB |
| `pip install supervertaler[all]` | ~1.2 GB |

---

## 🔁 Maintenance Update (v1.9.74) - December 31, 2025

- 🧹 **Removed in-app Superdocs viewer & generator** – The documentation generator and Qt viewer have been deprecated and removed from the application; official documentation is now hosted on GitBook: https://supervertaler.gitbook.io/help/. The app now directs users to the online Superdocs. Packaging metadata and site links updated accordingly.
- 📚 **Superdocs tooling docs refreshed** – Expanded the Tools section documentation (TMX Editor, AutoFingers, Supervoice voice commands, Image Extractor) to match the current UI and workflows.
- 🔍 **Superdocs Superlookup docs refreshed** – Expanded the Superlookup docs (TM search, glossary search, MT, web resources) to match current UI behavior and shortcuts.
- 🧩 **Superlookup copy/insert fix** – TM/Glossary results now store plain text in table items (while still rendering highlighted rich text), so copy/insert actions work reliably.

---

## 🌟 Recent Highlights - What's New in Supervertaler

**Latest Major Features:**

- 📝 **External Prompt Editor Display (v1.9.73)** - External prompts (not in the library) now display in the Prompt Editor panel when loaded or restored from a project. Editor shows name, description, and content fields. Edits can be saved back to the original file (.svprompt files save as JSON, .txt/.md as plain text). Visual indicator shows "📁 External: {name}" to distinguish from library prompts. Projects now correctly display their stored prompts (both external and library) in the editor when loaded.
 - 🔎 **Find & Replace: History, Reusable Sets & Batch Projects (v1.9.73)** - Added a dropdown history to the Find & Replace dialog that stores the last X entries for quick reuse. Users can save a sequence of find/replace operations as a reusable F&R project file, export/import these files, and run them as a batch on future projects. New UI includes a history dropdown, `Save/Load F&R Project` actions, and a `Run F&R Project` batch dialog with preview/dry-run and progress reporting.
- ⌨️ **Go to Segment Dialog (v1.9.71)** - Improved Ctrl+G shortcut with a minimal, streamlined dialog. Just type the segment number and press Enter - no need to click buttons. Global shortcut now works from anywhere in the application. Input field validates segment numbers and shows current position as placeholder. **Pagination-aware**: automatically switches to the correct page when jumping to segments on other pages. Cursor is placed in the target cell ready to edit.
- 📄 **Page Up/Down Pagination Navigation (v1.9.69)** - Page Up and Page Down keys now navigate through pagination pages! Press Page Up to go to the previous page, Page Down to go to the next page. Shortcuts appear in Settings → Keyboard Shortcuts under "Grid Navigation" category.
- 🎨 **memoQ Tag Color as Default (v1.9.68)** - Changed default tag highlight color to memoQ's actual dark red (`#7f0001`), color-picked directly from memoQ. Updated everywhere: grid cells, Translation Results panel, Settings defaults. Added 8 preset colors to the color picker (memoQ red, memoQ orange, Trados blue/purple, etc.). Each CAT tool export preserves its native tag colors (memoQ, Trados, Phrase, CafeTran). Supervertaler's own Bilingual Table export now uses memoQ red. Reset button updated to restore memoQ red.
- ⚡ **Performance Boost & Cache Fix (v1.9.66)** - Significantly faster segment navigation! Fixed termbase cache not working - empty results were being re-searched on every visit instead of being cached. Reduced verbose logging overhead that was slowing down navigation. Cache now properly stores and respects empty results using membership check (`segment_id in cache`). Removed per-word termbase search logging, per-match logging, prefetch progress logging, and MT/TM debug logging. Navigation should feel much snappier now.
- 📄 **Working Grid Pagination (v1.9.64)** - Grid pagination now actually works! Previously pagination controls existed but didn't filter the displayed segments. Now when you select "50 per page", only 50 segments are shown at a time. Use First/Prev/Next/Last buttons or type a page number to navigate. Efficient show/hide approach without grid reload for fast page changes.
- 🔄 **Batch Translate Retry Until Complete (v1.9.64)** - New "🔄 Retry until all segments are translated" option in batch translate dialog (enabled by default). If some segments fail or return empty after the first pass, automatically retries just those segments. Continues until all segments have translations or max 5 retries reached. No more running batch translate 2-3 times manually!
- 🤖 **Prompt Manager Tab Rename (v1.9.64)** - "Prompts" tab renamed to "Prompt manager" in Project resources for clarity.
- 📁 **External Prompt Restoration (v1.9.64)** - Fixed external prompts (from outside the library folder) not being restored when loading a project. External prompts are now correctly saved with `[EXTERNAL]` prefix and restored on project load.
- 🐧 **Linux Stability Fix (v1.9.63)** - Fixed memory access violations (segfaults) that could occur on Linux when clicking in the grid after importing a Trados package. Native code libraries (Hunspell, ChromaDB) can crash on Linux with improper dictionaries. Added safer Hunspell initialization with test spell check, crash detection flag to auto-disable spellcheck if it fails, and protected spellcheck highlighting with try/except. AutoHotkey registration now skipped entirely on Linux/Mac (no more "AutoHotkey not found" warnings). Linux users: if crashes persist, disable spellcheck in Settings or install proper Hunspell dictionaries (`sudo apt install hunspell-pl` for Polish).
- 🧹 **Dead Code Cleanup (v1.9.62)** - Removed ~230+ lines of deprecated and unused code. Cleaned up: `toggle_sidebar`, `handle_ribbon_action`, `create_toolbar`, `_render_paragraph`, deprecated termview methods, and verbose debug logging. Added missing `spellcheck_settings` field to Project dataclass with proper initialization. Removed unnecessary `hasattr()` checks. AutoFingers UI simplified by removing single-tab QTabWidget wrapper.
- 🔍 **Tag-Aware TM Matching (v1.9.60)** - Translation Memory fuzzy matching now works regardless of whether segments contain formatting tags! Searches both with and without tags, so `<b>Hello</b>` matches `Hello` in your TM. Similarity calculation also strips tags before comparing, giving accurate match percentages. Added `<li-b>` and `<li-o>` list item tags to TMX Tag Cleaner. Removed unused TMX Manager tab from AutoFingers - Import from TM button now in Control Panel.
- 🧹 **TMX Tag Cleaner (v1.9.59)** - New tag cleaning function in TMX Editor and main application! Access via Edit → Bulk Operations → Clean Tags, or the 🧹 Clean Tags toolbar button in TMX Editor. Select which tags to clean (formatting, TMX/XLIFF inline, memoQ, Trados, generic XML), choose replacement (remove or replace with space), and scope (source, target, or both). Cleans ALL languages in TMX regardless of display, not just visible pair. Handles both literal `<b>` and XML-escaped `&lt;b&gt;` tags. TMX Editor language dropdowns now correctly default to different languages (source→target, not source→source). AutoHotkey setup dialog now has "Do not show again" checkbox.
- 🏠 **Flattened Tab Structure (v1.9.57)** - Simplified main navigation from nested tabs to flat structure. The old "Workspace → Editor / Resources" hierarchy is now: **Project editor** | **Project resources** | **Tools** | **Settings**. All four tabs are now at the top level for easier navigation. Capitalization follows lowercase style for subtabs (e.g., "Project editor" not "Project Editor").
- ✏️ **Glossary Renaming (v1.9.56)** - Right-click on any glossary in Project resources → Glossaries tab to rename it. Previously, editing the name in the UI appeared to work but didn't actually save to the database. Now uses proper rename dialog with database persistence. Name column is no longer misleadingly editable inline.
- ⚡ **Lightning-Fast Filtering (v1.9.55)** - Filter operations (Ctrl+Shift+F) now run instantly instead of taking ~12 seconds! Optimized to avoid grid reload - only shows/hides rows and applies yellow highlights. **Ctrl+Shift+F toggle**: press once to filter on selected text, press again to clear the filter. Clear filter also listed separately in keyboard shortcuts for discoverability.
- 📋 **Superlookup Termbase Enhancements (v1.9.53)** - Improved Glossaries tab with additional metadata columns: Glossary name, Domain, Notes. Full metadata in results including priority, project, client, forbidden status. Tooltips show full content on hover.
- 📥 **Glossary Import Progress Dialog (v1.9.53)** - Real-time progress dialog when importing glossaries from TSV files. Visual progress bar, live statistics (✅ imported, ⏭️ skipped, ❌ errors), scrolling log window with color-coded entries.
- 🌐 **Superlookup Web Resources (v1.9.52)** - Expanded web resources tab with 14 reference sites! New resources: Juremy, michaelbeijer.co.uk, AcronymFinder, BabelNet, Wiktionary (Source & Target). Persistent login sessions with cookies stored in `user_data/web_cache/`. Auto-select language pair from project on load. Compact single-line search layout. Settings checkboxes control sidebar button visibility.
- 🔍 **Superlookup MT Integration (v1.9.51)** - Complete Machine Translation integration in Superlookup! Search now returns results from Google Translate, Amazon Translate, DeepL, Microsoft Translator, ModernMT, and MyMemory. MT provider status display shows active/disabled/missing API key providers with "⚙️ Configure in Settings" link. Error messages now shown in red with details (no more silent failures). Fixed language name mapping: "Dutch" → "nl", "English" → "en" for all MT providers. Added boto3 and deepl to requirements.txt. Removed debug print spam. Termbases tab now has search filter and split-view with editable terms grid.
- 🎤 **Voice Commands System (v1.9.50)** - Complete hands-free translation with Talon-style voice commands! Say "next segment", "confirm", "source to target", "translate", and more. **Always-On Listening Mode** with VAD (Voice Activity Detection) - no need to press F9. Dual recognition engines: **OpenAI Whisper API** (recommended, fast & accurate) or local Whisper model. New grid toolbar button (🎧 Voice ON/OFF) for easy toggle. Status bar indicator shows listening/recording/processing state. AutoHotkey integration for controlling external apps (memoQ, Trados, Word) by voice. Custom voice commands with fuzzy matching. Configure in Tools → Supervoice tab.
- 🎤 **Always-On Listening (v1.9.49)** - VAD-based continuous listening eliminates pressing F9 twice. Automatically detects speech, records, transcribes, and processes as command or dictation. Configurable mic sensitivity (Low/Medium/High). Visual feedback: 🟢 Listening → 🔴 Recording → ⏳ Processing. F9 stops always-on mode if active.
- 🎤 **Talon-Style Voice Commands (v1.9.48)** - 3-tier voice command architecture: Internal commands (control Supervertaler), System commands (AutoHotkey for other apps), Dictation fallback. Built-in commands: navigation, editing, translation, lookup. Custom command editor with phrase, aliases, and action configuration.
-  🧹 **Code Cleanup (v1.9.47)** - Removed ~811 lines of dead Document View code. The Document View feature was never used in production - the Grid View (Editor) is the primary and only workflow. Cleanup includes: removed `LayoutMode` class, removed `create_editor_widget()`, `create_document_view_widget()`, `refresh_document_view()` and all related helper methods. File reduced from 35,249 to 34,438 lines. No functional changes.
- 🏠 **Workspace UI Redesign (v1.9.46)** - Cleaner tab hierarchy with renamed tabs: **Workspace** (main tab) containing **Editor** (the grid) and **Resources** (TM, Termbases, Prompts, etc.). Removed Document View (unused). Simplified navigation menu. Fixed critical bug where termbase matches showed terms from non-activated termbases.
- 🏷️ **Termbase Highlight Styles (v1.9.45)** - Three configurable styles for termbase matches in the translation grid: **Background** (default pastel green shades), **Dotted Underline** (priority-based colors: red for P1, grays for P2-3, customizable for P4+), and **Semibold** (bold weight with tinted foreground). Configure via Settings → View Settings. Auto-spellcheck for target language: spellcheck now automatically initializes to project target language on import/load. Fixed short language codes (nl, de, fr) not mapping to dictionaries.
- 📚 **UI Reorganization (v1.9.44)** - Prompt Manager moved under Project Resources tab (prompts are project resources). Superlookup hotkey script now shows Supervertaler icon in system tray. Fixed termbase import "Could not find termbase ID" error. Removed dotted focus outline from Superlookup Search button.
- 🔑 **Superlookup Hotkey Improvements (v1.9.43)** - Fixed Ctrl+Alt+L global hotkey not bringing Superlookup to foreground. Added AutoHotkey setup helper (Help → Setup AutoHotkey for Superlookup). New AutoHotkey path configuration in Settings → General Settings. Better error handling when AutoHotkey is not installed.
- 📁 **Multi-File Project Support (v1.9.42)** - Import entire folders of files as a single multi-file project! File → Import → Folder (Multiple Files) supports DOCX and TXT files. Per-file progress tracking in View → File Progress dialog (or click status bar). New file filter dropdown to show segments from specific files. Status bar shows completion progress across all files. Source files automatically backed up to `_source_files/` folder. Relocate Source Folder feature to fix broken paths. Export to folder with TXT, DOCX, or Bilingual Table formats (export in progress - basic functionality available).
- 🔍 **Superlookup Fixes (v1.9.42)** - Renamed `UniversalLookupTab` to `SuperlookupTab` for consistency. Fixed `theme_manager` attribute error when using Ctrl+Alt+L hotkey. Theme-aware search term highlighting now works properly.
- 📋 **Spellcheck Info Dialog Redesign (v1.9.42)** - Two-column horizontal layout fits on screen without scrolling. Clear explanation of auto-switching between built-in pyspellchecker and Hunspell backends. Compact diagnostics section.
- 🌙 **Dark Mode (v1.9.41)** - Complete dark theme implementation with proper styling across the entire application. Dark compare boxes in Translation Results panel, dark Termview with visible text for non-matched words, and consistent theming throughout all UI components. Switch themes via View → Theme Editor.
- 🔍 **Superlookup Unified Concordance System (v1.9.40)** - Major consolidation: Ctrl+K now opens Superlookup instead of a separate concordance dialog. All lookup resources in one place: TM concordance, Termbase matches, Supermemory semantic search, Machine Translation, and Web Resources. New dual-view toggle: Horizontal (table) or Vertical (list) layout. Tab reorganization: "Project Resources" now comes before "Prompt Manager". Removed redundant tabs from Translation Memories (Concordance and Import/Export - functionality already available in Superlookup and TM List). FTS5 full-text search now properly used for blazingly fast concordance on millions of segments.
- 🔍 **Superlookup Multilingual Search (v1.9.39)** - Complete overhaul of Superlookup with multilingual language filtering. New From/To language dropdowns filter TM and termbase searches by source/target language pair. Search direction radio buttons (Both/Source only/Target only) for precise concordance searches. Yellow highlighting of search terms in results. Compact results display with tooltips for full text. Languages auto-populate from your TMs and termbases, grouped alphabetically by language family. UI cleanup: removed Manual Capture button and Operating Modes selector.
- 📁 **Improved Project File Format (v1.9.38)** - `.svproj` files now have all metadata at the top (name, languages, dates, settings, paths) with segments at the end for easier inspection in text editors. Added helpful tip in batch translate warning about using Select All + Clear Target instead of re-importing.
- 🔤 **User-Configurable Grid Fonts (v1.9.37)** - Choose your preferred font family for the translation grid from 10 popular options. Live preview shows font changes in real-time with sample source/target text and tags. Font family now persists between sessions.
- 🎨 **Universal Tag Coloring (v1.9.36)** - All CAT tool tags now highlighted in pink: memoQ `{1}`, `[2}`, Trados `<1>`, `</1>`, Phrase `{1}`, and HTML `<b>`, `<i>`. CafeTran pipe symbols only red in CafeTran projects (bug fix).
- 🎨 **memoQ Red Tags Support (v1.9.35)** - Fixed memoQ bilingual export not preserving red tag color. Tags in the target column now correctly inherit the red/magenta color from the source column, ensuring perfect formatting for memoQ re-import.
- 🎨 **UI Fixes (v1.9.34)** - Replaced all standard radio buttons with green-themed CheckmarkRadioButton.
- 🐛 **Spellcheck Update Fix (v1.9.33)** - Fixed issue where adding/ignoring words only removed underline in the current cell. Now triggers instant global refresh of all highlighters across the entire grid. No more false positive red underlines after you've whitelisted a word

- 📦 **Trados SDLRPX Status Fix (v1.9.32)** - Fixed critical bug where exported SDLRPX return packages kept segments in "Draft" status instead of updating to "Translated". Trados Studio now correctly recognizes translated segments. Client deliverables no longer show as MT draft content

- 🔤 **Spellcheck Language Fix (v1.9.31)** - Spellcheck now correctly uses the project's target language instead of defaulting to English. Added language dropdown in Spellcheck Info dialog to manually change spellcheck language. Language changes take effect immediately with highlighting refresh
- 🐛 **Critical LLM Fix (v1.9.30)** - Fixed OpenAI/LLM translation failing with "No such file or directory" error. Removed hardcoded debug file path that prevented translation when running from non-development directories
- 📝 **Spellcheck Integration (v1.9.29)** - Built-in spellcheck for target language. Works out of the box with pyspellchecker (8 languages bundled). Optional Hunspell support for more languages. Red wavy underlines for misspelled words. Right-click for suggestions, Add to Dictionary, Ignore. Custom dictionary with persistent word list. Spellcheck state saved per-project in .svproj files. Button state persists across restarts
- 📄 **Phrase (Memsource) Bilingual DOCX Support (v1.9.28)** - Full round-trip support for Phrase TMS bilingual DOCX files. Import preserves inline tags like `{1}`, `{1>text<1}`. Export writes translations back to Column 5 for seamless return to Phrase workflow. File → Import → Phrase (Memsource) Bilingual (DOCX) and File → Export → Phrase (Memsource) Bilingual
- 👁️ **Show Invisibles Feature (v1.9.28)** - Display invisible characters in the translation grid: spaces (·), tabs (→), non-breaking spaces (°), and line breaks (¶). Dropdown menu with granular control for each character type. Toggle All option. Smart handling preserves copy/paste (Ctrl+C copies original characters), double-click word selection, and Ctrl+Arrow word navigation. Configurable symbol color in Settings → View Settings
- 📄 **Simple Text File Import/Export (v1.9.27)** - Import simple text files where each line becomes a source segment. Translate with AI, then export a matching file with translations. Perfect for line-by-line translation of plain text content. Language pair selection, encoding options (UTF-8, Latin-1, etc.), and empty line handling. File → Import → Simple Text File (TXT) and File → Export → Simple Text File - Translated (TXT)
- 📦 **SDLPPX Project Persistence (v1.9.20)** - SDLPPX package path now saved in .svproj files. Full round-trip workflow persists across sessions - import SDLPPX, translate, save project, close, reopen, continue translating, export SDLRPX. Fixed export bug that showed "0 translations updated". Handler automatically restored on project load
- 📦 **Trados Studio Package Support (v1.9.19)** - Import SDLPPX packages directly from Trados Studio project managers. New File → Import → Trados Studio submenu with Package (SDLPPX) option. Translates SDLXLIFF files within the package, preserves SDL-specific markup and segment IDs. Export as SDLRPX return package (File → Export → Trados Studio → Return Package) for seamless delivery back to Trados users. Full round-trip workflow for freelance translators receiving packages
- 🔍 **Supermemory Concordance Integration (v1.9.18)** - Concordance Search (Ctrl+K) now includes Supermemory semantic search with two-tab interface. TM Matches tab for exact text search, Supermemory tab for meaning-based search. Active checkbox column in Supermemory to control which TMs are searched. Fixed Trados bilingual DOCX round-trip issues (xml:space, language settings). Supermemory moved to Resources tab
- 🧠 **Supermemory Enhancements (v1.9.17)** - Complete domain management system for translation memories with domain categorization (Legal, Medical, Patents, etc.), multi-language filtering in search, integration with Superlookup for unified lookup, and TMX/CSV export. Color-coded domain tags, dynamic column headers showing actual languages, and professional search/filter interface
- 🖥️ **Local LLM Support - Ollama (v1.9.16)** - Run AI translation entirely on your computer with no API costs, complete privacy, and offline capability. New "Local LLM (Ollama)" provider option in Settings with automatic hardware detection and model recommendations. Supports qwen2.5 (3B/7B/14B), llama3.2, mistral, and gemma2 models. Built-in setup wizard guides installation and model downloads. See FAQ for setup instructions
- 📋 **Bilingual Table Export/Import (v1.9.15)** - New Supervertaler Bilingual Table format for review workflows. Export menu options: **"Bilingual Table - With Tags (DOCX)"** preserves Supervertaler formatting tags for re-import after review. **"Bilingual Table - Formatted (DOCX)"** applies formatting (bold/italic/underline, bullet markers) for client-ready output. Tables include segment number, source, target, status, and notes columns. **"Import Bilingual Table"** compares edited DOCX with current project, shows diff preview, and applies changes. Document title links to supervertaler.com
- 📤 **Improved DOCX Export & Keyboard Navigation (v1.9.14)** - Fixed DOCX export to properly handle formatting tags (`<b>`, `<i>`, `<u>`) and convert them to actual Word formatting. Export now handles multi-segment paragraphs with partial replacement. Added cleanup for Unicode replacement characters (U+FFFC). Ctrl+Home/End now properly navigate to first/last segment even when editing in grid cells
- 📄 **Document Preview & List Tags (v1.9.13)** - New Preview tab shows formatted document view with headings, paragraphs, and list formatting. Click any text to navigate to that segment. Distinct list tags: `<li-o>` for ordered/numbered lists (1. 2. 3.) and `<li-b>` for bullet points (•). DOCX import now properly detects bullet vs numbered lists from Word's numbering XML. Type column shows `¶` for continuation paragraphs instead of `#`
- 📊 **Progress Indicator Status Bar (v1.9.12)** - New permanent status bar showing real-time translation progress: Words translated (X/Y with percentage), Confirmed segments (X/Y with percentage), and Remaining segments count. Color-coded: red (<50%), orange (50-80%), green (>80%). Updates automatically as you work
- ⚡ **Navigation & Find/Replace Improvements (v1.9.11)** - Ctrl+Home/End to jump to first/last segment. Find/Replace dialog now pre-fills selected text from source or target grid. Ctrl+Q shortcut for instant term pair saving (remembers last-used termbase from Ctrl+E dialog)
- 🔧 **Non-Translatables: Case-Sensitive & Full-Word Matching (v1.9.11)** - Non-translatables matching is now case-sensitive by default and only matches full words (not partial words). Added LLM refusal detection with helpful error messages for batch translation. Fixed crash when closing project (missing stop_termbase_batch_worker). Fixed .svprompt files not showing in Prompt Library tree
- 🔧 **TM Search Fixes & Language Matching (v1.9.10)** - Fixed TM matches not appearing in Translation Results panel. Added flexible language matching ("Dutch", "nl", "nl-NL" all match). TM metadata manager now initializes with project load. Removed legacy Project TM/Big Mama hardcoding. Cleaned public database for new users. Non-Translatables: sortable columns, right-click delete, Delete key support
- 🎨 **memoQ-style Alternating Row Colors (v1.9.9)** - Grid now displays alternating row colors across all columns (ID, Type, Source, Target) like memoQ. User-configurable colors in Settings → View Settings with even/odd row color pickers. Colors are consistent across the entire row including QTextEdit widgets
- 🔄 **CafeTran Integration & Editor Shortcuts (v1.9.8)** - Full CafeTran bilingual DOCX support with pipe symbol formatting. New Ctrl+Shift+S copies source to target. Ctrl+, inserts pipe symbols for CafeTran. Pipes highlighted in red/bold. Sortable keyboard shortcuts table. Batch size default changed to 20
- 🔄 **CafeTran Bilingual DOCX Support (v1.9.7)** - Full import/export support for CafeTran bilingual DOCX files. Import preserves pipe symbol formatting markers. Export writes translations back with formatting preserved. Round-trip workflow for CafeTran users
- 📁 **Custom File Extensions & Monolingual Export (v1.9.6)** - New branded file extensions: `.svproj` (projects), `.svprompt` (prompts), `.svntl` (non-translatables). All formats maintain backward compatibility. Monolingual DOCX import now prompts for language pair. New "Target Only (DOCX)" export preserves original document structure (tables, formatting). Original DOCX path saved in project files for reliable exports
- 📤 **Send Segments to TM & memoQ Tag Shortcuts (v1.9.5)** - Bulk send translated segments to TMs via Edit > Bulk Operations. Filter by status (Translated, Reviewed, etc.) and scope. New Ctrl+, shortcut inserts memoQ tags pairs or wraps selection. Tab renamed to "Resources"
- 🏷️ **Tag-Based Formatting System (v1.9.4)** - Complete inline formatting support for memoQ bilingual files. Import preserves bold/italic/underline as `<b>`, `<i>`, `<u>` tags. Toggle between WYSIWYG and Tag view with Ctrl+Alt+T. Ctrl+B/I/U shortcuts to apply formatting. AI translation preserves tags. Export converts tags back to Word formatting
- 📋 **Session Log Tab & TM Defaults Fix (v1.9.3)** - Added Session Log tab to bottom panel for easy access to log messages. Fixed TM Read/Write checkbox defaults to respect project.json settings
- ⚙️ **Superlookup Settings UI (v1.9.2)** - Redesigned Settings tab with sub-tabs for TM/Termbase/MT/Web resources. Proper 18x18px checkboxes with green background and white checkmarks matching standard Supervertaler style. Each resource type has dedicated full-height space for easy selection
- ↩️ **Undo/Redo for Grid Edits (v1.9.1)** - Full undo/redo support for grid editing operations with Ctrl+Z/Ctrl+Y. Tracks target text changes, status changes, and find/replace operations with 100-level history
- 🔍 **Termview - Inline Terminology (v1.9.0)** - Visual inline terminology display showing source words with translations underneath, inspired by RYS Trados plugin. Supports multi-word terms, click-to-insert, hover tooltips, and terms with punctuation like "gew.%"
- 🎨 **UI Refinements - Tab Styling (v1.8.0)** - Refined selected tab appearance with subtle 1px blue underline and light background highlighting for cleaner visual design
- ✅ **Simplified TM/Termbase System (v1.6.6)** - Redesigned with Read/Write checkboxes, auto-priority system, removed complex Active/Project concepts for clearer workflow
- 🔍 **Find/Replace & TM Enhancements (v1.7.9)** - Fixed highlighting, disabled TM saves during navigation, added bidirectional TM search with language variant matching
- 🔍 **Filter Highlighting Fix (v1.7.8)** - Fixed search term highlighting in source/target filter boxes using widget-internal highlighting
- 🎯 **Termbase Display Customization (v1.7.7)** - User-configurable termbase match sorting and filtering for cleaner translation results
- 💾 **Auto Backup System (v1.7.6)** - Automatic project.json and TMX backups at configurable intervals to prevent data loss
- 🐛 **Critical TM Save Bug Fix (v1.7.5)** - Fixed massive unnecessary database writes during grid operations that caused 10+ second freezes
- 💾 **Project Persistence (v1.7.4)** - Projects now remember your primary prompt and image context folder
- 🧪 **Prompt Preview & System Template Editor (v1.7.3)** - Preview combined prompts with figure context detection and improved system template editor with better layout
- 🔧 **Termbase Critical Fixes (v1.7.2)** - Fixed term deduplication and termbase selection issues
- 🎨 **Termbase UI Polish (v1.7.1)** - Improved visual consistency with pink highlighting for project termbases and real-time term count updates
- 📚 **Project Termbases (v1.7.0)** - Dedicated project-specific terminology with automatic extraction and pink highlighting
- 📁 **File Dialog Memory (v1.6.5)** - File dialogs remember your last used directory for improved workflow
- 🌐 **Superbrowser (v1.6.4)** - Multi-chat AI browser with ChatGPT, Claude, and Gemini side-by-side in one window
- ⚡ **UI Responsiveness & Precision Scroll (v1.6.3)** - Debug settings, disabled LLM auto-matching, memoQ-style precision scroll buttons, auto-center active segment
- 🖼️ **Superimage (v1.6.2)** - Extract images from DOCX files with preview and auto-folder management
- 📚 **Enhanced Termbase System (v1.6.1)** - Extended metadata with notes, project, client fields and refresh functionality
- 📚 **Complete Termbase System (v1.6.0)** - Professional terminology management with interactive features
- 🎤 **Supervoice (v1.4.0)** - AI voice dictation with OpenAI Whisper, 100+ languages, F9 hotkey
- 📊 **Superbench (v1.4.1)** - Benchmark LLM translation quality on YOUR actual projects with chrF++ scoring
- 🤖 **AI Assistant (v1.3.4)** - ChatGPT-quality conversational prompt refinement built into the editor
- 📚 **Unified Prompt Library (v1.3.0)** - Unlimited folders, favorites, multi-attach, quick run
- 📝 **TMX Editor (v1.1.3)** - Database-backed editor handles massive 1GB+ TMX files
- ✋ **AutoFingers (v1.2.4)** - Automated TMX-to-memoQ pasting with fuzzy matching and tag cleaning
- 📄 **PDF Rescue** - AI OCR with GPT-4 Vision transforms locked PDFs into clean DOCX
- 🖼️ **Image Context** - Multimodal AI automatically includes images when translating technical documents
- 💾 **Translation Memory** - Fuzzy matching with TMX import/export, auto-propagation
- 🔄 **CAT Tool Integration** - memoQ, Trados, CafeTran bilingual table support

**See full version history below** ↓

---

## [1.9.41] - December 16, 2025

### 🌙 Dark Mode - Complete Theme Implementation

**Full dark theme support across the entire application:**
- 🎨 **Compare Boxes**: Translation Results panel now properly displays dark backgrounds for Current Source, TM Source, and TM Target boxes in dark mode
- 📝 **Termview Visibility**: All words in Termview pane now visible in dark mode - not just terms with matches. Non-matched words use light text color on dark background
- 🔄 **Theme Consistency**: Fixed Qt styling issues where hidden widgets weren't receiving theme updates. Theme colors now applied when widgets become visible
- ⚡ **Reliable Styling**: Uses both stylesheet and QPalette approaches for maximum compatibility across different Qt rendering scenarios

**Technical improvements:**
- Added `_apply_compare_box_theme()` method for reliable theme application on visibility
- Theme-aware `TermBlock` and `NTBlock` classes in Termview widget
- Proper color inheritance for all UI components in dark mode

**Access Dark Mode:** View → Theme Editor → Select "Dark" theme

---

## [1.9.40] - December 12, 2025

### 🔍 Superlookup Unified Concordance System

**Major consolidation - Ctrl+K now opens Superlookup instead of separate dialog:**
- 🔗 **Unified Lookup Hub**: All concordance searches now go through Superlookup - one place for TM, Termbase, Supermemory, MT, and Web Resources
- ⌨️ **Ctrl+K Integration**: Pressing Ctrl+K in Project Editor navigates to Tools → Superlookup and auto-searches selected text
- 📝 **Selected Text Auto-Fill**: Any text selected in source/target automatically populates the search field

**Dual-view toggle for TM Matches tab:**
- 📊 **Horizontal (Table)**: Source | Target columns side-by-side - compact and scannable
- 📜 **Vertical (List)**: Dutch: ... / English: ... stacked format - traditional concordance layout with more detail
- 🔄 **Radio Button Toggle**: Switch between views instantly, results update in both views

**UI/Tab reorganization:**
- 📚 **"Resources" → "Project Resources"**: Clearer naming for the resources tab
- 🔀 **Tab Reorder**: Project Resources now comes BEFORE Prompt Manager (more logical flow)
- 🧹 **Removed Redundant Tabs**: Translation Memories no longer has Concordance or Import/Export tabs (functionality in Superlookup and TM List)
- 📦 **Compact Source Text**: Superlookup source text box shrunk from 100px to 50px
- 📚 **"Termbase Terms" → "Termbase Matches"**: Consistent naming

**FTS5 Full-Text Search optimization:**
- ⚡ **Concordance now uses FTS5**: `concordance_search()` now uses SQLite FTS5 MATCH instead of slow LIKE queries
- 🚀 **100-1000x faster** on large databases with millions of segments
- 🔄 **Auto-sync**: FTS5 index automatically rebuilt if out of sync with main table
- 🔧 **Manual rebuild**: New `rebuild_fts_index()` method available for maintenance

**ChromaDB stability fix:**
- 🐛 **Fixed Rust backend crashes**: Removed all `collection.count()` calls that caused native crashes in ChromaDB 1.3.x
- 📊 **Uses metadata count**: Stats now derived from SQLite metadata instead of ChromaDB collection queries
- ✅ **ChromaDB 0.6.3**: Stable version with Python backend, compatible with tokenizers 0.22.0

---

## [1.9.39] - December 11, 2025

### 🔍 Superlookup Multilingual Search

**Multilingual language filtering for TM and termbase searches:**
- 🌍 **From/To Language Dropdowns**: New filter dropdowns in Superlookup search bar to filter by source/target language pair
- 🔄 **Swap Button**: Quick ↔ button to swap From and To language selections
- 📚 **Auto-Population**: Languages auto-populate from your TMs and termbases when tab is first viewed
- 🔤 **Smart Sorting**: Languages alphabetically sorted with family grouping (all Dutch variants together, all English variants together, etc.)
- 🏷️ **Clear Display**: Format shows "English (en)", "Dutch (nl-BE)" for clarity and uniqueness

**Search direction controls:**
- ↔️ **Both**: Bidirectional search (searches source and target columns)
- → **Source only**: Search only in source text
- ← **Target only**: Search only in target text

**UI improvements:**
- 🟡 **Yellow Highlighting**: Search terms now highlighted in yellow in TM and termbase results
- 📏 **Compact Display**: Results use word wrap with 60px max row height, tooltips show full text on hover
- 🔢 **Hidden Row Numbers**: Cleaner display without row number column
- 🧹 **Removed Manual Capture**: Button was redundant (just paste text manually)
- 🧹 **Removed Operating Modes**: Dropdown was pointless (only Universal mode was used)

---

## [1.9.38] - December 11, 2025

### 📁 Project File & UX Improvements

**Reorganized .svproj file structure for human readability:**
- 📄 **Metadata First**: Project name, languages, dates, ID now at the top of the file
- ⚙️ **Settings Next**: Prompt, TM, termbase, spellcheck settings follow metadata
- 📂 **Paths Then**: Source file paths (DOCX, memoQ, Trados, etc.) before segments
- 📝 **Segments Last**: Translation content at the end for easy scrolling in text editors

**Improved batch translate warning for memoQ files:**
- 💡 Added tip: "You can clear all targets without re-importing" with instructions to use Select All + Clear Target from right-click menu
- Saves users from having to go back to memoQ to clean the file

---

## [1.9.37] - December 11, 2025

### 🔤 User-Configurable Grid Fonts

**New font customization options in Settings → View Settings:**
- 🔤 **Font Family Dropdown**: Choose from 10 popular fonts: Calibri, Segoe UI, Arial, Consolas, Verdana, Times New Roman, Georgia, Courier New, Tahoma, Trebuchet MS
- 👁️ **Live Preview**: Real-time preview showing sample source/target text with tags, updates instantly as you change font settings
- 💾 **Font Persistence**: Font family now saved to preferences and restored on startup (previously only font size was saved)
- 🎯 **Improved Spinbox**: Fixed font size spinner up/down arrows with better click targets
- 📝 **Contact Note**: Info text now includes "If your favourite font is missing, contact the developer!"

---

## [1.9.36] - December 10, 2025

### 🎨 Universal Tag Coloring

**All CAT tool tags now highlighted in pink in the translation grid:**
- 🏷️ **memoQ Tags**: `{1}`, `[2}`, `{3]`, `[4]` - all variations now colored pink
- 🏷️ **Trados Tags**: `<1>`, `</1>` - numeric tags now colored pink
- 🏷️ **Phrase Tags**: `{1}`, `{2}` - same as memoQ, now colored pink
- 🏷️ **HTML Tags**: `<b>`, `<i>`, `<u>`, `<li-o>` - already worked, still works

**CafeTran Pipe Symbol Fix:**
- 🐛 **Bug Fix**: Pipe symbols (`|`) were incorrectly highlighted red in ALL project types
- ✅ **Fixed**: Pipes now only red in CafeTran projects (as intended)
- 🔧 **Implementation**: Added `TagHighlighter._is_cafetran_project` class flag

---

## [1.9.35] - December 10, 2025

### 🎨 formatting
- **memoQ Red Tags**: Fixed issue where red formatting tags (e.g. `{1}`) in memoQ bilingual files were being exported as black text.
- **Smart Color Transfer**: Export now dynamically reads the source column color and applies it to the corresponding text in the target column.

## [1.9.34] - December 10, 2025

### 🎨 UI Fixes

**Checkmark Radio Buttons:**
- 🎨 **Global Update**: Replaced all standard `QRadioButton` instances across the application with the custom green `CheckmarkRadioButton`.
- ✅ **Updated Areas**: Find & Replace, Advanced Filters, Row Locking, Termbase Import, AutoFingers, and TM Import dialogs.
- 💅 **Visual Consistency**: Ensures a uniform look and feel across all green-themed UI elements.

---

## [1.9.32] - December 10, 2025

### 📦 Trados SDLRPX Status Fix

**Critical Bug Fix for Trados Return Packages:**
- 🔧 **Status Update Fix**: SDLRPX export now correctly updates segment confirmation status from "Draft" to "Translated"
- ✅ **Proper Trados Recognition**: Trados Studio now recognizes segments as translated, not machine translation drafts
- 📤 **Client Deliverables**: Return packages display correctly in Trados when client opens them
- 🏷️ **conf Attribute**: Fixed missing update of `conf` attribute in SDLXLIFF `<sdl:seg>` elements

**Technical Details:**
- Added `_update_segment_status()` method to `sdlppx_handler.py`
- Updates `conf` attribute in `sdl:seg-defs` section during export
- Maps internal status ('translated', 'approved') to SDL status ('Translated', 'ApprovedTranslation')
- Proper namespace handling for SDL elements in ElementTree

---

---

## [1.9.33] - December 10, 2025

### 🐛 Spellcheck Update Fix

**Fixed Spellcheck Highlighting Update:**
- 🔧 **Global Refresh**: Adding a word to custom dictionary or ignoring it now immediately updates all occurrences in the grid
- ✅ **No More False Positives**: Red wavy underlines vanish instantly across the entire document when you whitelist a word
- 🖱️ **Context Menu Fix**: Right-click "Add to Dictionary" and "Ignore Word" actions now trigger full grid refresh

---

## [1.9.31] - December 10, 2025

### 🔤 Spellcheck Language Fix

**Spellcheck Now Uses Project Target Language:**
- 🎯 **Automatic Language Detection**: Spellcheck initializes with project's target language instead of defaulting to English
- 🌐 **Language Dropdown**: Added language selector in Spellcheck Info dialog
- 🔄 **Immediate Effect**: Language changes take effect immediately with highlighting refresh
- 📝 **Fixed Initialization**: `_toggle_spellcheck()` now uses `self.current_project.target_lang`

---

## [1.9.30] - December 10, 2025

### 🐛 Critical LLM Fix

**Fixed OpenAI/LLM Translation Error:**
- 🔧 **File Path Error**: Fixed "No such file or directory: 'openai_debug.txt'" error that broke all LLM translations
- 📁 **Debug Path**: Removed hardcoded debug file path that only worked in development directory
- ✅ **Production Ready**: Translations now work when running from any directory

---

## [1.9.29] - December 10, 2025

### 📝 Spellcheck Integration

**Built-in Spellchecking for Target Language:**
- 📝 **Spellcheck Button**: Toggle in filter bar enables/disables spellchecking
- 〰️ **Red Wavy Underlines**: Misspelled words highlighted with red wavy underline
- 💬 **Right-Click Suggestions**: Click misspelled word for spelling suggestions
- ➕ **Add to Dictionary**: Add words to custom dictionary (persistent)
- 🔇 **Ignore Word**: Ignore word for current session only
- 📖 **Custom Dictionary**: Manage custom words from dropdown menu
- ℹ️ **Spellcheck Info**: View backend, language, and dictionary status

**Language Support:**
- 🇬🇧 English, 🇳🇱 Dutch, 🇩🇪 German, 🇫🇷 French, 🇪🇸 Spanish, 🇵🇹 Portuguese, 🇮🇹 Italian, 🇷🇺 Russian
- 🐍 **Built-in Backend**: Uses pyspellchecker with bundled dictionaries - works out of the box!
- 📚 **Hunspell Backend**: Optional .dic/.aff files for additional languages or improved accuracy
- Auto-matches project target language

**Settings & Persistence:**
- 💾 **Project-Level Settings**: Spellcheck state saved in .svproj files
- 🔄 **Session Persistence**: Button state remembered across restarts
- ℹ️ **Info Dialog**: Explains dual-backend system with dictionary download links

**Technical Details:**
- New module: `modules/spellcheck_manager.py` - Complete spellcheck handling
- Custom dictionary stored in `user_data/dictionaries/custom_words.txt`
- TagHighlighter extended for spell underline formatting
- Spellcheck only applied to target column (not source)
- Settings persisted in `ui_preferences.json` and `.svproj` files

---

## [1.9.28] - December 9, 2025

### 📄 Phrase (Memsource) Bilingual DOCX Support

**Full Round-Trip Workflow:**
- 📥 **Import Phrase Bilingual DOCX**: File → Import → Phrase (Memsource) Bilingual (DOCX)
- 📤 **Export Back to Phrase**: File → Export → Phrase (Memsource) Bilingual - Translated (DOCX)
- 🏷️ **Inline Tag Preservation**: Tags like `{1}`, `{1>text<1}` preserved for round-trip
- 🔍 **Auto-Detection**: Detects Phrase format (7-column tables, segment IDs with `:`)
- 💾 **Project Persistence**: Phrase source path saved in .svproj for future sessions

**Implementation:**
- New module: `modules/phrase_docx_handler.py` - Complete Phrase DOCX handling
- Language pair selection dialog for imported files
- Segment ID and status preserved in notes field
- Export updates only Column 5 (target text) as Phrase expects

### 👁️ Show Invisibles Feature

**Display Invisible Characters:**
- 🔘 **Dropdown Menu**: Show Invisibles button with granular control
- ·  **Spaces**: Displayed as middle dot (·)
- →  **Tabs**: Displayed as right arrow (→)
- °  **Non-Breaking Spaces**: Displayed as degree symbol (°)
- ¶  **Line Breaks**: Displayed as pilcrow (¶)
- 🎯 **Toggle All**: Quick on/off for all invisible types

**Smart Handling:**
- 📋 **Clipboard Safety**: Ctrl+C copies original characters, not symbols
- 🖱️ **Double-Click Selection**: Properly selects words when invisibles shown
- ⌨️ **Ctrl+Arrow Navigation**: Word-by-word navigation works correctly
- 🎨 **Configurable Color**: Symbol color in Settings → View Settings (default: light gray)
- ✅ **Zero-Width Space Technique**: Uses U+200B for line-break opportunities without breaking word boundaries

**Technical Details:**
- Replacements applied only at display time (segment data never modified)
- Automatic reversal when text is saved or edited
- TagHighlighter extended to color invisible symbols

### 🔧 TM Pre-Translation Fix

**Batch Translate with TM:**
- 🐛 **Fixed TM-Only Mode**: Batch Translate dialog now properly handles TM as a translation provider
- 📖 **TM Provider Support**: Select "Translation Memory" in provider dropdown for TM-only batch translation
- 🎯 **Respects Activated TMs**: Uses project's activated TMs for matching
- 📊 **Match Threshold**: Accepts matches 70% and above for pre-translation

---

## [1.9.26] - December 8, 2025

### 🔄 Automatic Model Version Checker

**Smart Model Updates:**
- 🆕 **Auto-detect New LLM Models**: Automatically checks for new models from OpenAI, Anthropic, and Google
- 📅 **Daily Checks**: Runs once per 24 hours on startup (configurable)
- 🔔 **Smart Notifications**: Popup dialog only when new models are detected
- ✅ **Easy Selection**: Click to select which models to add to Supervertaler
- 💾 **Intelligent Caching**: Remembers last check to avoid unnecessary API calls
- ⚙️ **Fully Configurable**: Enable/disable auto-check in Settings → AI Settings
- 🔍 **Manual Check**: "Check for New Models Now" button for on-demand checking

**Implementation:**
- New module: `modules/model_version_checker.py` - Core checking logic with 24-hour throttling
- New module: `modules/model_update_dialog.py` - User-friendly PyQt6 dialogs
- Settings integration: New "Model Version Checker" section in AI Settings
- Cache system: Stores results in `user_data/model_version_cache.json`
- Provider support: OpenAI (models.list API), Claude (pattern testing), Gemini (models API)

**User Experience:**
- Silent operation: No interruption if no new models found
- Error handling: Graceful degradation if APIs unavailable
- Documentation: Complete UI standards guide to maintain consistency

### 🎨 UI Polish & Standardization

**Checkbox Consistency:**
- ✅ **Standardized All Checkboxes**: Replaced 3 blue QCheckBox instances with green CheckmarkCheckBox
- 📏 **Refined Size**: Reduced checkbox size from 18x18px to 16x16px for cleaner appearance
- 📚 **Documentation**: Created UI_STANDARDS.md to prevent future inconsistencies
- 🎯 **Visual Consistency**: All checkboxes now use custom green style with white checkmarks

**Fixed Checkboxes:**
- "Enable LLM (AI) matching on segment selection"
- "Auto-generate markdown for imported documents"
- "Enable automatic model checking (once per day on startup)"

---

## [1.9.25] - December 8, 2025

### 🐧 Linux Compatibility Release

**Platform Support:**
- ✅ **Full Linux Compatibility**: Supervertaler now runs perfectly on Ubuntu and other Linux distributions
- ✅ **Removed Legacy Dependencies**: Eliminated tkinter imports from TMX editor module
- ✅ **Complete requirements.txt**: All dependencies now properly documented and installable
- ✅ **Graceful Platform Detection**: AutoFingers shows helpful message on Linux (Windows/memoQ-specific feature)

**Installation Improvements:**
- 📦 **One-Command Setup**: `pip install -r requirements.txt` installs all dependencies
- 📝 **Added Missing Dependencies**:
  - `pyyaml` - YAML support for Non-Translatables manager
  - `PyMuPDF` - PDF processing for PDF Rescue module
  - `sentence-transformers` - Semantic search for Supermemory
  - `keyboard` - Keyboard control for AutoFingers
  - `lxml` - XML processing for Trados DOCX handler
- 🛠️ **Platform-Specific Notes**: Clear documentation for Linux, Windows, and macOS compatibility
- 🔧 **Optional Dependencies**: Voice dictation and automation features clearly marked as optional

**Bug Fixes:**
- 🐛 **Fixed AutoFingers Import**: Made `pyautogui` import optional with graceful fallback for Linux
- 🐛 **Fixed TMX Editor**: Removed unnecessary tkinter dependency from core module
- 🐛 **Fixed Import Errors**: Proper error handling for platform-specific features

**Technical Changes:**
- 🔄 **AutoFingers Engine**: Added `HAS_PYAUTOGUI` flag for cross-platform compatibility
- 🔄 **Import Guards**: Platform-specific features now detect availability at runtime
- 📚 **Documentation**: Enhanced requirements.txt with feature descriptions and platform notes

**For Users:**
- 🎯 **Fresh Installation**: Works out-of-the-box on fresh Ubuntu installations
- 🎯 **Virtual Environment**: Full support for Python venv isolated installations
- 🎯 **Cross-Platform**: Same codebase works on Windows, Linux, and macOS

---

## [1.9.24] - December 7, 2025

### ✨ Smart Word Selection
- **Intelligent Text Selection**: Selecting part of a word automatically expands to the full word
  - Makes word selection faster and less stressful during translation
  - Works in both source (read-only) and target (editable) columns
  - Supports compound words with hyphens (e.g., "self-contained")
  - Supports contractions with apostrophes (e.g., "don't", "l'homme")
  - Threshold-based: Only expands selections under 50 characters (prevents interference with multi-word selections)
- **Settings Toggle**: New "Enable smart word selection" checkbox in Settings → General → Editor Settings
  - Enabled by default
  - Helpful tooltip explains the feature with examples
  - Can be disabled if user prefers traditional selection behavior
- **Implementation**:
  - Added `mouseReleaseEvent()` to both `ReadOnlyGridTextEditor` and `EditableGridTextEditor`
  - Word character detection includes alphanumeric, underscore, hyphen, and apostrophe
  - Boundary detection ensures expansion only occurs when selection is partial
  - Respects settings toggle across the application
- **Documentation**: Complete feature documentation in `SMART_WORD_SELECTION.md`
  - Implementation details, testing checklist, known limitations, future enhancements

### 🛡️ Supermemory Error Handling Improvements
- **Better DLL Error Messages**: Enhanced PyTorch DLL loading failure handling
  - `modules/supermemory.py` now catches `OSError` and `Exception` (not just `ImportError`)
  - Windows-specific DLL errors are properly caught and handled
  - Stores error message in `SENTENCE_TRANSFORMERS_ERROR` for debugging
- **Helpful Instructions**: Auto-detects DLL errors and provides actionable solutions
  - Detects "DLL", "c10.dll", or "torch" in error messages
  - Provides 3 specific fixes with direct links and exact commands:
    1. Install Visual C++ Redistributables (https://aka.ms/vs/17/release/vc_redist.x64.exe)
    2. Reinstall PyTorch with exact pip commands
    3. Disable Supermemory auto-init in Settings as fallback
  - Instructions appear automatically in the log when error occurs
- **Technical Details**:
  - Modified `Supervertaler.py`: Lines 4116-4126 (error handler in `_auto_init_supermemory()`)
  - Modified `modules/supermemory.py`: Lines 45-51 (exception catching)

---

## [1.9.23] - December 7, 2025

### 📄 Bilingual Table Landscape Orientation
- **Improved Visualization**: Supervertaler Bilingual Table exports now use landscape orientation
  - Better visualization of long segments (source and target columns have more horizontal space)
  - Applies to both "With Tags" and "Formatted" export options
  - Page dimensions automatically swapped for landscape layout
  - Maintains 0.5-inch margins on all sides
- **Technical Details**:
  - Added `WD_ORIENT.LANDSCAPE` to document sections
  - Swapped page width/height for proper landscape rendering
  - Modified `Supervertaler.py`: Lines 7820-7832 (document setup)

---

## [1.9.22] - December 7, 2025

### 🤖 Gemini 3 Pro Preview Support
- **Latest Google AI Model**: Added support for Gemini 3 Pro Preview (November 2025 release)
  - New model option in Settings → LLM Settings → Gemini Models dropdown
  - Listed as "gemini-3-pro-preview (Latest - Superior Performance)"
  - Works in both single segment translation (Ctrl+T) and batch translation
  - Performance: 10-20% improvement on average, 6-20x better on reasoning/math tasks
  - Pricing: $2/$12 per million tokens (vs $1.25/$10 for Gemini 2.5 Pro)
- **LLM Client Update**: Added all current Gemini models to supported list
  - `gemini-2.5-flash-lite` (Fastest & Most Economical)
  - `gemini-2.5-pro` (Premium - Complex Reasoning)
  - `gemini-3-pro-preview` (Latest - Superior Performance)
  - Updated module documentation to reflect Gemini 3 support
- **Files Modified**:
  - `Supervertaler.py`: Lines 10889-10902 (model dropdown and tooltip)
  - `modules/llm_clients.py`: Lines 8-11 (docs), 220-229 (supported models)

---

## [1.9.21] - December 6, 2025

### 🐛 Critical SDLPPX Handler Bug Fix
- **Fixed SDLRPX Export Failure After Project Reload**: Fixed "'str' object is not callable" error when exporting SDLRPX return packages after reopening a saved project
  - Root cause: Handler was initialized with path string instead of log_callback parameter
  - The path was incorrectly assigned to `self.log`, causing export to fail when trying to call log function
  - Now correctly initializes handler with `TradosPackageHandler(log_callback=self.log)` and calls `load_package(path)` separately
  - Also fixed missing `self.sdlppx_source_file` assignment during handler restoration
  - Full SDLPPX workflow now works correctly: import package → translate → save project → close → reopen → export SDLRPX ✓
- **Impact**: This bug prevented translators from exporting return packages after reopening saved SDLPPX projects, breaking the workflow for Trados Studio package handling

---

## [1.9.20] - December 5, 2025

### 📦 SDLPPX Project Persistence
- **Project Save/Restore**: SDLPPX package path now saved in .svproj files
  - Added `sdlppx_source_path` field to Project dataclass
  - Serialized in `to_dict()`, deserialized in `from_dict()`
  - Full round-trip workflow now persists across sessions
- **Handler Restoration**: SDLPPX handler automatically restored on project load
  - When opening a .svproj from an SDLPPX import, handler is recreated
  - SDLRPX export available immediately without reimporting
  - Log message confirms: "✓ Restored Trados package handler"
- **Export Bug Fix**: Fixed SDLRPX export showing "0 translations updated"
  - Export now reads from segment objects instead of table widget items
  - Notes column was never populated as QTableWidgetItem - data is in segment.notes
  - Verified translations correctly written to return package

---

## [1.9.19] - December 4, 2025

### 📦 Trados Studio Package Support
- **SDLPPX Import**: Import Trados Studio project packages directly
  - File → Import → Trados Studio → Package (SDLPPX)
  - Parses SDLXLIFF files within the package
  - Shows package info dialog with file list and segment counts
  - Preserves SDL-specific markup and segment IDs
  - Automatic language detection from package metadata
- **SDLRPX Export**: Create return packages for delivery
  - File → Export → Trados Studio → Return Package (SDLRPX)
  - Writes translations back to SDLXLIFF files
  - Creates properly formatted return package
  - Round-trip workflow for freelance translators
- **Menu Reorganization**: Grouped all Trados import/export options
  - New "Trados Studio" submenu under Import and Export
  - Contains both bilingual review DOCX and package options
- **New Module**: `modules/sdlppx_handler.py` (767 lines)
  - `TradosPackageHandler` class for package management
  - `SDLXLIFFParser` for parsing SDL-extended XLIFF files
  - Handles `<g>`, `<x/>`, `<mrk mtype="seg">` tags
  - Preserves SDL namespaces and attributes

---

## [1.9.18] - December 4, 2025

### 🔍 Supermemory Concordance Integration
- Concordance Search (Ctrl+K) now includes Supermemory semantic search
- Two-tab interface: TM Matches tab for exact text, Supermemory tab for meaning
- Active checkbox column in Supermemory to control which TMs are searched
- Fixed Trados bilingual DOCX round-trip issues (xml:space, language settings)
- Supermemory moved from Tools tab to Resources tab

---

## [1.9.17] - December 3, 2025

### 🧠 Supermemory Enhancements - Domain Management & Superlookup Integration

**Major upgrade to the vector-indexed translation memory system:**

**Domain Management System:**
- Added **Domain dataclass** with name, description, color, and active status
- New database schema: `domains` table and `domain` column in `indexed_tms`
- **8 default domains:** General, Patents, Medical, Legal, Technical, Marketing, Financial, Software
- **DomainManagerDialog:** Full CRUD interface with color pickers and active toggles
- Assign domains during TMX import with intuitive dropdown selector
- Color-coded domain tags in search results for visual categorization

**Enhanced Search & Filtering:**
- **Language pair filter:** Dropdown to filter by source-target language combination
- **Multi-domain filter:** Select multiple active domains to search within
- **Dynamic column headers:** Results table shows actual language codes (e.g., "Source (EN)", "Target (NL)")
- Search respects both language pair and domain filters simultaneously

**Superlookup Integration:**
- **New "Supermemory" tab** in Superlookup for unified terminology/TM lookup
- Semantic search results appear alongside TM, termbase, and MT matches
- Click to insert matches directly into target segment
- Seamless integration with existing Superlookup workflow

**Export Functionality:**
- **Export to TMX:** Full TMX export with language headers and segment metadata
- **Export to CSV:** Simple source-target pairs for spreadsheet workflows
- Export dialog lets you choose format before exporting

### Consolidated AI Settings

- Merged Gemini and Mistral settings into unified **"AI Settings"** tab
- Cleaner Settings panel with fewer tabs
- All API keys and model selections in one place

---

## [1.9.18] - December 4, 2025

### 🔍 Supermemory Concordance Integration & Trados Fixes

**Concordance Search now includes Supermemory semantic search:**

**Concordance Search Enhancements:**
- **Two-tab interface:** "TM Matches" (exact text) and "Supermemory" (semantic/meaning-based)
- Semantic search finds translations by meaning, not just exact words
- Tab headers show result counts (e.g., "📋 TM Matches (9)" and "🧠 Supermemory (25)")
- Results display similarity scores with color-coded High/Medium/Low indicators
- Window remembers position and size across sessions (saved to project)

**Supermemory UI Improvements:**
- **Moved to Resources tab** - now under Resources → Supermemory (was Tools)
- **Active checkbox column** in TM table - toggle which TMs are searched
- Only active TMs are included in Concordance semantic search
- Checkbox state persists in database

**Trados Bilingual DOCX Fixes:**
- Fixed `xml:space="preserve"` attribute on text elements for proper whitespace handling
- Fixed target language settings - runs now inherit from paragraph (was incorrectly setting nl-NL)
- Added language selection dialog on import (Trados files don't specify languages)
- Source file path now persisted in project for reliable re-export
- "Source File Not Found" now offers to browse for file in new location

**Other Improvements:**
- Renamed export menu items to "Supervertaler Bilingual Table" for clarity
- memoQ and CafeTran source paths also persisted in project
- Fixed Concordance accessing Supermemory engine (was checking wrong attribute)

---

## [1.9.16] - December 1, 2025

### 🖥️ Local LLM Support - Ollama Integration

**Run AI translation entirely on your computer with no API costs, complete privacy, and offline capability:**

**New Provider Option:**
- Added **"Local LLM (Ollama)"** as new provider in Settings → LLM Provider tab
- Appears alongside OpenAI, Anthropic, Google, etc. with familiar radio button selection
- Works with single translation, batch translation, and AI Assistant chat

**Intelligent Hardware Detection:**
- Automatically detects system RAM and GPU capabilities
- Recommends optimal model based on your hardware:
  - **4GB RAM:** qwen2.5:3b (2.5GB download) - Basic functionality
  - **8GB RAM:** qwen2.5:7b (5.5GB download) - Recommended default
  - **16GB+ RAM:** qwen2.5:14b (10GB download) - Premium quality
- GPU detection for NVIDIA, AMD, and Apple Silicon

**Built-in Setup Wizard:**
- One-click access via "Setup Local LLM..." button in Settings
- Guides users through complete Ollama installation
- Platform-specific instructions (Windows, macOS, Linux)
- Real-time connection testing to verify Ollama is running
- Model download with progress tracking and cancellation

**Recommended Models for Translation:**
- **qwen2.5** (3B/7B/14B) - Excellent multilingual capabilities, recommended for translation
- **llama3.2** (3B/7B) - Strong general purpose, good European languages
- **mistral:7b** - Fast inference, good quality/speed balance
- **gemma2:9b** - Google's efficient model, good multilingual

**Status Widget in Settings:**
- Shows real-time Ollama connection status
- Displays currently selected model
- Quick-access button to Setup dialog
- Hardware specification summary

**Technical Implementation:**
- `modules/local_llm_setup.py` (NEW) - Complete setup module with:
  - `LocalLLMSetupDialog` - Full wizard UI with model recommendations
  - `LocalLLMStatusWidget` - Compact status widget for Settings panel
  - `detect_system_specs()` - RAM and GPU detection
  - `get_model_recommendations()` - Hardware-based model suggestions
  - `ModelDownloadWorker` - Background download with progress
  - `ConnectionTestWorker` - Async connection verification
- `modules/llm_clients.py` - Extended with Ollama support:
  - `OLLAMA_MODELS` dict with 7 supported models
  - `check_ollama_status()` - Connection and model detection
  - `_call_ollama()` - REST API integration (OpenAI-compatible)
  - `translate()` routes to Ollama when selected

**Privacy & Cost Benefits:**
- All translation processing stays on your computer
- No data sent to external servers
- No API key required
- No per-token costs - unlimited translations
- Works completely offline after model download

---

## [1.9.15] - November 30, 2025

### 📋 Supervertaler Bilingual Table Export/Import

**New bilingual table format for proofreading and review workflows:**

**Export Options (File → Export):**
- **"Bilingual Table - With Tags (DOCX)"**: Exports 5-column table (Segment #, Source, Target, Status, Notes) with raw Supervertaler tags preserved. Intended for proofreaders to review and edit - can be re-imported after editing
- **"Bilingual Table - Formatted (DOCX)"**: Same structure but applies formatting: `<b>` becomes actual bold, `<i>` becomes italic, `<u>` becomes underline, list tags become visible markers (• for bullets, ◦ for nested). For client delivery or archiving - cannot be re-imported

**Import Option (File → Import):**
- **"Bilingual Table (DOCX) - Update Project"**: Re-imports edited bilingual table, compares with current project by segment number, shows preview of all changes (old vs new target), applies approved changes with status reset to "Not Started"

**Document Format:**
- Header with "Supervertaler Bilingual Table" title linking to Supervertaler.com
- Language names in column headers (e.g., "English", "Dutch" instead of "Source", "Target")
- Pink highlighting for tags in the With Tags version
- Footer with Supervertaler.com branding
- Decorative underlines for professional appearance

**Technical Implementation:**
- `export_review_table_with_tags()` - Wrapper for tag-visible export
- `export_review_table_formatted()` - Wrapper for formatted export with warning dialog
- `_export_review_table(apply_formatting)` - Core export logic with python-docx
- `_add_hyperlink_to_paragraph()` - Helper for Word hyperlinks via XML manipulation
- `import_review_table()` - Import logic with change detection and diff preview

---

## [1.9.14] - November 30, 2025

### 📤 Improved DOCX Export & Keyboard Navigation

**DOCX Export Improvements:**
- **Formatting Preservation:** Export now properly converts `<b>`, `<i>`, `<u>`, `<bi>` tags to actual Word formatting (bold, italic, underline)
- **Multi-Segment Paragraphs:** Export handles paragraphs containing multiple segments with partial replacement
- **Unicode Cleanup:** Removes problematic characters like U+FFFC (Object Replacement Character)
- **Tag Stripping:** Properly strips all list tags (`<li-o>`, `<li-b>`, `<li>`) while preserving formatting tags

**Keyboard Navigation Fix:**
- Ctrl+Home now properly navigates to first segment even when editing inside a grid cell
- Ctrl+End now properly navigates to last segment even when editing inside a grid cell
- Added `_get_main_window()` helper to both `EditableGridTextEditor` and `ReadOnlyGridTextEditor`

**Technical Changes:**
- `export_target_only_docx()`: Added `apply_formatted_text_to_paragraph()` for parsing tags into Word runs
- `export_target_only_docx()`: Added `replace_segments_in_text()` for partial segment replacement
- `export_target_only_docx()`: Added `clean_special_chars()` to remove Unicode replacement characters
- `EditableGridTextEditor.keyPressEvent()`: Added Ctrl+Home/End handlers
- `ReadOnlyGridTextEditor.event()`: Added Ctrl+Home/End handlers

---

## [1.9.13] - November 30, 2025

### 📄 Document Preview & List Formatting Tags

**New Preview tab shows formatted document view:**

**Preview Tab Features:**
- New "Preview" tab alongside Source/Target views in the main panel
- Shows formatted document with headings (H1-H6 with proper sizing), paragraphs, and lists
- List items display with correct prefix: numbers (1. 2. 3.) for ordered lists, bullets (•) for bullet points
- Click any text in preview to instantly navigate to that segment in the grid
- Read-only view for document context during translation

**List Type Detection from DOCX:**
- New `_get_list_type()` method in docx_handler.py examines Word's numPr XML structure
- Properly distinguishes numbered lists from bullet points by analyzing abstractNum definitions
- Looks for "bullet" in numFmt value or bullet characters (•, ○, ●, ■) in lvlText
- Caches list type lookups for performance

**New List Tags:**
- `<li-o>` - Ordered list items (numbered: 1. 2. 3.)
- `<li-b>` - Bullet list items (•)
- Both tags are colored with the tag highlighter
- Both work with Ctrl+, shortcut for quick insertion

**Type Column Improvements:**
- Type column now shows `#1`, `#2`, `#3` for ordered list items (numbered)
- Shows `•` for bullet list items
- Shows `¶` (paragraph mark) for continuation paragraphs instead of `#`
- Provides clearer visual distinction between list types

**Technical Implementation:**
- Added `_setup_preview_tab()` for Preview tab creation
- Added `_render_preview()` method with formatted text rendering
- Added `_render_formatted_text()` helper for styled QTextEdit output
- Updated tag regex pattern to support hyphenated tags: `[a-zA-Z][a-zA-Z0-9-]*`
- Preview connects to `_preview_navigation_requested()` for click-to-navigate

---

## [1.9.12] - November 28, 2025

### 📊 Progress Indicator Status Bar

**New permanent status bar showing real-time translation progress:**

**Progress Display:**
- **Words translated**: Shows X/Y words with percentage (counts words in segments that have translations)
- **Confirmed segments**: Shows X/Y segments with percentage (confirmed, tr_confirmed, proofread, approved statuses)
- **Remaining segments**: Count of segments still needing work (not_started, pretranslated, rejected statuses)

**Color Coding:**
- **Red** (<50%): Low progress - needs attention
- **Orange** (50-80%): Making progress - keep going
- **Green** (>80%): Almost done - near completion

**Auto-Updates:**
- Updates when project is loaded
- Updates when segment is confirmed (Ctrl+Enter)
- Updates after AI translation completes
- Updates after user finishes typing (debounced)
- Resets to "--" when project is closed

**Technical Implementation:**
- Added `_setup_progress_indicators()` method for status bar widget setup
- Added `update_progress_stats()` method for calculating and updating progress
- Added `_get_progress_color()` helper for color-based progress feedback
- Progress widgets are permanent status bar items (right-aligned)

---

## [1.9.11] - November 28, 2025

### 🔧 Non-Translatables: Case-Sensitive & Full-Word Matching

**Improved non-translatables matching to prevent false positives:**

**Matching Improvements:**
- Non-translatables matching is now **case-sensitive by default**
- Only matches **full words** (not partial words like "Product" inside "ProductName")
- Uses word boundary detection (`\b`) for accurate term matching
- Smart fallback for special characters like ® and ™ that don't work with word boundaries
- Prevents unwanted replacements in the middle of compound terms

**Bug Fixes:**
- Fixed crash when closing project: added missing `stop_termbase_batch_worker()` method
- Fixed `.svprompt` files not showing in Prompt Library tree (added extension to both library and manager)
- Added LLM refusal detection for batch translation with helpful error messages when AI refuses content

**Technical Details:**
- Changed `case_sensitive` default to `True` in `NonTranslatablesManager.matches()`
- Rewrote matching logic to use regex word boundaries for full-word matching
- Added proper error handling for OpenAI content policy refusals during batch translation

---

## [1.9.10] - November 28, 2025

### 🔧 TM Search Fixes & Flexible Language Matching

**Fixed TM matches not appearing in Translation Results panel:**

**Root Cause Analysis:**
- `tm_metadata_mgr` was only initialized when user opened TM List tab, but TM search runs immediately on segment navigation
- Database had mixed language formats ("Dutch", "nl", "nl-NL") but search only looked for ISO codes
- Legacy hardcoded `enabled_only=True` filter would search only 'project' and 'big_mama' TMs that don't exist

**Fixes Applied:**
- **Early initialization:** `tm_metadata_mgr` now initializes in `initialize_tm_database()` when project loads
- **Flexible language matching:** New `get_lang_match_variants()` function returns both ISO codes and full language names
- **Bypass legacy filter:** Added `enabled_only=False` to all `search_all()` calls
- **Fallback search:** When no TMs are explicitly activated, search now falls back to all TMs

**Database Improvements:**
- Cleaned public database (`user_data/Translation_Resources/supervertaler.db`) for new GitHub users
- Removed sample data that had orphaned TM entries without proper metadata
- Schema preserved - new users start with empty, properly structured database

**Code Cleanup:**
- Removed legacy `project` and `big_mama` TM hardcoding from `TMDatabase` class
- These were from the previous Supervertaler architecture and are no longer used
- All TMs now managed through `TMMetadataManager` with proper database storage

**Files Modified:**
- `Supervertaler.py` - TM metadata manager early init, enabled_only=False for searches
- `modules/translation_memory.py` - Removed legacy tm_metadata dict
- `modules/database_manager.py` - Flexible language matching in get_exact_match() and search_fuzzy_matches()
- `modules/tmx_generator.py` - Added get_lang_match_variants() and updated get_base_lang_code()

### 📊 Non-Translatables Entry Table Enhancements

**Sortable Columns:**
- Columns in the Non-Translatables entry table are now sortable by clicking on column headers
- Click on Pattern, Type, or other columns to sort alphabetically ascending/descending
- Default sort by Pattern column (ascending)
- Sorting is temporarily disabled during table refresh to prevent UI issues

**Delete Entries:**
- Right-click on selected entries to access context menu with delete option
- Press Delete key to remove selected entries
- Menu dynamically shows "Delete 1 entry" or "Delete N entries" based on selection
- Existing "🗑️ Remove Selected" button also still available

---

## [1.9.9] - November 27, 2025

### 🎨 memoQ-style Alternating Row Colors

**CafeTran Formatting Support:**
- Pipe symbols (|) now highlighted in red/bold in grid editor (like CafeTran)
- Ctrl+, inserts pipe symbols for CafeTran formatting (or wraps selection)
- Ctrl+Shift+S copies source text to target cell

**Keyboard Shortcuts Improvements:**
- Keyboard shortcuts table now sortable by clicking column headers
- Removed "Save Project As" shortcut (Ctrl+Shift+S now dedicated to copy source)

**Settings Changes:**
- Batch size default changed from 100 to 20 segments per API call

---

## [1.9.7] - November 27, 2025

### 🔄 CafeTran Bilingual DOCX Support

**Full import/export support for CafeTran bilingual table format:**

**CafeTran Import:**
- New **Import > CafeTran Bilingual Table (DOCX)...** menu option
- Validates CafeTran bilingual format (ID | Source | Target | Notes table)
- Extracts segments with pipe symbol formatting markers preserved
- Converts to internal segment format for translation
- Stores handler for round-trip export

**CafeTran Export:**
- New **Export > CafeTran Bilingual Table - Translated (DOCX)...** menu option
- Writes translations back to Target column
- Preserves pipe symbol formatting (bold/underline markers)
- Maintains original table structure
- File can be imported back into CafeTran

**Technical Implementation:**
- Uses `modules/cafetran_docx_handler.py` module
- `CafeTranDOCXHandler` class handles file I/O
- `FormattedSegment` class preserves pipe symbol markers
- Red/bold formatting for pipe symbols in export

---

## [1.9.4] - November 26, 2025

### 🏷️ Tag-Based Formatting System for memoQ Bilingual Files

**Complete inline formatting support for professional translation workflows with memoQ bilingual DOCX files.**

**Phase 1 - Import & Display:**
- Import memoQ bilingual DOCX preserves bold, italic, underline as `<b>`, `<i>`, `<u>` HTML-style tags
- New "🏷️ Tags ON/OFF" toggle button in grid toolbar
- WYSIWYG mode: Shows formatted text (bold appears bold)
- Tag mode: Shows raw tags like `<b>bold</b>` for precise editing
- Keyboard shortcut: **Ctrl+Alt+T** to toggle between modes
- Tags auto-enabled after import when formatting detected
- TagHighlighter colorizes tags with pink background for visibility

**Phase 2 - Export with Formatting:**
- Export converts `<b>`, `<i>`, `<u>` tags back to actual Word formatting
- New `tagged_text_to_runs()` function parses tags into Word runs
- Round-trip fidelity: Import → Edit → Export preserves formatting
- Handles nested tags correctly (e.g., `<b><i>bold italic</i></b>`)

**Phase 3 - AI Translation with Tags:**
- Updated default system prompt with inline formatting tag instructions
- AI translates text while preserving and repositioning tags intelligently
- Example: "Click the `<b>`Save`</b>` button" → "Klik op de knop `<b>`Opslaan`</b>`"
- Tags placed around corresponding translated words, not just same position

**Formatting Shortcuts in Target Editor:**
- **Ctrl+B** - Apply/toggle bold tags on selected text
- **Ctrl+I** - Apply/toggle italic tags on selected text
- **Ctrl+U** - Apply/toggle underline tags on selected text

**Helper Functions Added:**
- `runs_to_tagged_text()` - Convert Word runs to tagged text on import
- `tagged_text_to_runs()` - Parse tags back to Word runs on export
- `strip_formatting_tags()` - Remove tags for plain text
- `has_formatting_tags()` - Check if text contains formatting tags
- `get_formatted_html_display()` - Convert tags to HTML for WYSIWYG display

---

## [1.9.6] - November 27, 2025

### 📁 Custom File Extensions & Monolingual Export

**New Branded File Extensions:**
- **Projects:** `.svproj` (was `.json`) - Supervertaler Project files
- **Prompts:** `.svprompt` (was `.md`/`.json`) - Supervertaler Prompt files  
- **Non-Translatables:** `.svntl` (was `.ntl`) - Supervertaler Non-Translatable lists
- All formats maintain full backward compatibility - opens legacy files seamlessly
- New files created with branded extensions for professional consistency
- Industry standards retained: `.tmx` for TM exports, `.srx` planned for segmentation

**Monolingual DOCX Import Improvements:**
- Language pair selection dialog when importing monolingual DOCX files
- Dropdown selectors for source and target language (12 languages supported)
- Prevents language detection issues - user explicitly sets translation direction
- Removed unreliable auto-detect language feature

**Target-Only DOCX Export:**
- New **Export > Target Only (DOCX)...** menu option for monolingual exports
- Preserves original document structure (tables, formatting, styles, headers)
- Copies original DOCX as template before replacing text
- Replaces text in both paragraphs and table cells
- Falls back gracefully if original document unavailable

**Project Persistence:**
- Original DOCX path now saved in project files (`original_docx_path`)
- Path restored when reopening projects for reliable exports
- Enables structure-preserving exports even after closing and reopening

**Documentation Updates:**
- New modular documentation: QUICK_START.md, KEYBOARD_SHORTCUTS.md, CAT_WORKFLOW.md
- Archived legacy USER_GUIDE.md and INSTALLATION.md
- FAQ.md copied to repository root (fixes dead link)

---

## [1.9.5] - November 27, 2025

### 📤 Send Segments to TM & memoQ Tag Shortcuts

**Send Segments to TM (Bulk Operation):**
- New dialog under **Edit > Bulk Operations > Send Segments to TM**
- Send translated segments directly to selected Translation Memories
- **Scope filters:** All segments, Current selection, or specific row range
- **Status filters:** Filter by Translated, Reviewed, Approved, Needs Review, or Final status
- Select multiple TMs to write to simultaneously
- Shows count of segments that will be sent before execution
- Progress feedback with success/failure counts

**memoQ Tag Insertion Shortcut:**
- **Ctrl+,** (Ctrl+Comma) - Insert next memoQ tag pair or wrap selection
- Smart tag insertion: Analyzes source segment for memoQ tags (`[1}`, `{1]`, `[3]`, etc.)
- With selection: Wraps selected text with next unused tag pair
- Without selection: Inserts next available tag pair at cursor
- Works with paired tags (`[1}...{1]`) and standalone tags (`[3]`)
- Respects tag order from source segment for consistency

**UI Improvements:**
- Renamed "Translation Resources" tab to "Resources" for cleaner UI
- Resources tab contains TM, Termbase, and MT/Web resources sub-tabs

---

## [1.9.3] - November 26, 2025

### 📋 Session Log Tab & TM/Termbase Defaults Fix

**Session Log Tab:**
- Added Session Log tab to bottom panel alongside Comments and Termview
- Real-time log display with timestamps in monospace font
- Easy access to log messages without detaching window
- Read-only display with automatic scrolling to latest entries

**TM/Termbase Checkbox Defaults Fixed:**
- Read checkboxes now default to unchecked (inactive) when no project loaded
- Read checkboxes default to unchecked when no activation record exists
- Write checkboxes default to unchecked (read-only) by default
- All settings properly restored from project.json when project is loaded
- Fixed `is_tm_active()` in tm_metadata_manager.py to return False by default

**Quick Actions for Bulk Selection:**
- Added "Select All Read" and "Select All Write" checkboxes above TM table
- Added "Select All Read" and "Select All Write" checkboxes above Termbase table
- Green checkbox for Read, blue checkbox for Write matching table style
- Quickly activate/deactivate all resources with single click

---

## [1.9.2] - November 25, 2025

### ⚙️ Superlookup Settings UI Redesign

**Improved Resource Selection Interface:**
- Redesigned Settings tab with sub-tabs for TM, Termbase, MT, and Web Resources
- Each resource type now has dedicated full-height space in its own sub-tab
- Replaced cramped single-page layout with spacious tabbed interface

**Proper Checkbox Styling:**
- Replaced tiny multi-selection indicators with standard Supervertaler checkboxes
- 18x18px checkbox size with green (#4CAF50) background when checked
- White checkmark (✓) drawn on checked items matching AutoFingers style
- QScrollArea + CheckmarkCheckBox widgets instead of QListWidget
- Hover effects and proper visual feedback

**Technical Implementation:**
- `create_settings_tab()`: Creates QTabWidget with 4 sub-tabs
- `create_tm_settings_subtab()`: Full-height TM selection with checkboxes
- `create_termbase_settings_subtab()`: Full-height termbase selection
- `create_mt_settings_subtab()`: Placeholder for future MT integration
- `create_web_settings_subtab()`: Placeholder for future web resources
- CheckmarkCheckBox widgets in QScrollArea provide proper green checkboxes
- Fixed `cursor()` → `cursor` property access for database queries

**Bug Fixes:**
- Fixed Translation Memories list loading (was showing empty due to cursor() call error)
- Fixed termbase loading timing (lazy loading when Settings tab viewed)
- Proper checkbox state tracking with `setProperty()` and `property()` methods
- Select All/Clear All buttons now work with checkbox widgets instead of selection

**User Experience:**
- Much more spacious and easier to read
- Clear visual separation between resource types
- Checkboxes are now clearly visible and clickable
- Consistent styling across entire application

---

## [1.9.1] - November 24, 2025

### ↩️ Undo/Redo for Grid Edits

**New Feature: Complete Undo/Redo System**
- Full undo/redo support for all grid editing operations
- Keyboard shortcuts: Ctrl+Z (Undo), Ctrl+Y/Ctrl+Shift+Z (Redo)
- Edit menu actions with dynamic enabled/disabled states
- 100-level undo history to prevent memory issues

**What's Tracked:**
- Target text changes as you type
- Status changes (Not Started → Translated → Confirmed)
- Ctrl+Enter confirmations
- Find/Replace batch operations
- Document view edits

**Technical Implementation:**
- Dual stack system (undo_stack + redo_stack) tracks segment changes
- Records: segment_id, old_target, new_target, old_status, new_status
- Smart recording: Only captures actual changes, ignores no-ops
- Automatic redo stack clearing on new edits (standard undo behavior)
- Stack trimming to max 100 levels for memory efficiency
- Updates both segment data and grid display simultaneously

**Integration Points:**
- `on_target_text_changed()`: Text editing in grid cells
- `update_status_icon()`: Status changes via toolbar/ribbon
- `on_doc_status_change()`: Document view status changes
- `replace_all_matches()`: Batch find/replace operations
- Ctrl+Enter confirmation handler

**User Experience:**
- Menu actions show enabled/disabled state based on stack contents
- Seamless integration with existing editing workflow
- No performance impact on grid operations
- Professional CAT tool behavior (like memoQ/Trados)

---

## [1.9.0] - November 24, 2025

### 🔍 Termview - RYS-Style Inline Terminology Display

**New Feature: Visual Inline Terminology**
- Added "🔍 Termview" tab in bottom panel showing inline terminology like RYS Trados plugin
- Source text displayed as flowing words with translations appearing underneath matched terms
- Compact 8pt font with colored 2px top borders (pink for project termbase, blue for background)
- Text wrapping with FlowLayout to adapt to window width
- Click any translation to insert it into target segment
- Hover tooltips show full term details and metadata

**Technical Implementation:**
- `modules/termview_widget.py`: New widget with FlowLayout, TermBlock classes for visual display
- RYS-style tokenization preserves multi-word terms (e.g., "De uitvinding heeft betrekking op een werkwijze")
- Direct integration with Translation Results termbase cache for instant updates
- Smart refresh: Updates immediately after termbase search completes

**Termbase Search Enhancements:**
- Fixed punctuation handling: Terms like "gew.%" now matched correctly
- Changed from `strip()` to `rstrip()/lstrip()` to preserve internal punctuation
- Use lookaround word boundaries `(?<!\w)(?!\w)` for terms with punctuation
- Standard `\b` boundaries for regular words

**Bug Fixes:**
- Fixed data format mismatch between termbase cache dict and Termview list format
- Fixed timing issue where Termview updated before termbase search completed
- Fixed tokenization regex to capture terms with special characters
- Removed debug logging after successful implementation

### 🎯 Priority & Visual Improvements
- Project termbases (#1 priority) display with pink border for instant recognition
- Background termbases display with blue border
- Clean, minimal design with 1px padding and compact spacing

---

## [1.8.0] - November 23, 2025

### UI/UX Improvements
- **Tab Styling Refinement**: Reduced selected tab border-bottom from 3px to 1px for a more subtle, professional appearance
- **Visual Consistency**: Maintained light blue background highlighting (rgba(33, 150, 243, 0.08)) with thinner accent line
- **Applied Across Application**: Updated styling for all tab widgets including Resources, Modules, TM, Settings, Domain, Import, Results, and Prompt Manager tabs
- **Theme Manager Update**: Global tab styling now uses refined 1px border-bottom for consistent appearance

### Technical Changes
- Updated border-bottom styling in 12 locations across main application and modules
- Modified theme_manager.py for global tab appearance consistency
- Maintained focus removal and outline suppression for cleaner tab interactions

---

## [1.6.6] - November 23, 2025

### ✅ Simplified TM/Termbase Management System

**Major Redesign:**

- 🎯 **Simple Read/Write Checkbox System**
  - Removed confusing "Active" checkbox and "Project TM/Termbase" concepts
  - **Translation Memories:** Simple Read (green ✓) and Write (blue ✓) checkboxes
  - **Termbases:** Simple Read (green ✓) and Write (blue ✓) checkboxes  
  - All TMs and termbases start completely unchecked by default
  - Users explicitly check Read to use for matching, Write to allow updates
  
- 📊 **Auto-Priority System for Termbases**
  - Priorities 1-N automatically assigned to Read-enabled termbases
  - Priority #1 = Project Termbase (pink highlighting, highest priority)
  - Priority #2, #3, etc. = Background termbases (lower priorities)
  - No manual project termbase designation needed - just check Read boxes
  - Priority based on activation order (ranking in database)

- 🎨 **Cleaner Column Layout**
  - **TMs:** `TM Name | Languages | Entries | Read | Write | Last Modified | Description`
  - **Termbases:** `Type | Name | Languages | Terms | Read | Write | Priority`
  - Removed redundant columns and confusing labels
  - Type auto-shows "📌 Project" for priority #1, "Background" for others

- 🔒 **Read-Only Database Defaults**
  - New TMs created with `read_only=1` (Write unchecked by default)
  - New termbases created with `read_only=1` (Write unchecked by default)
  - Prevents accidental updates to reference memories
  - User must explicitly enable Write for TMs/termbases they want to update

**Benefits:**
- Much simpler mental model: Read = use for matching, Write = allow updates
- No more confusion about "Active" vs "Project" vs "Background"
- Project termbase is simply the highest priority (first activated)
- Clear visual feedback with color-coded checkboxes (green Read, blue Write)
- Safer defaults prevent accidental corruption of reference resources

---

## [1.7.9] - November 22, 2025

### 🔍 Find/Replace & TM Enhancements

**Fixed:**

- ✨ **Find/Replace Highlighting System** - Complete rewrite using consistent QTextCursor approach
  - "Find Next" now correctly highlights matches with yellow background
  - "Highlight All" button now actually highlights all matches in the grid
  - Font size no longer changes during navigation (previously shrunk with each find)
  - Switched from QLabel+HTML (which replaced widgets) to QTextCursor+QTextCharFormat (preserves existing widgets)
  - Matches same highlighting system used by filter boxes
  - Supports case-sensitive/insensitive, whole words, and entire segment modes

- ✨ **No More TM Saves During Find/Replace** - Eliminated slowdowns during search navigation
  - Added `find_replace_active` flag to disable background TM saves
  - Prevents segments from being saved to TM on every "Find Next" click
  - Re-enables TM saves when dialog closes
  - Also disables expensive TM/MT/LLM lookups during find/replace operations
  - Results in much faster navigation through search results

**Added:**

- 🌍 **Bidirectional TM Search** - TMs now search in both directions automatically
  - When translating nl→en, also searches en→nl TMs for reverse matches
  - Example: English source text can match Dutch source in reverse TM
  - Reverse matches clearly marked with "Reverse" indicator
  - Improves TM utilization by ~2x without any user action required

- 🌍 **Language Variant Matching** - Base language codes match all regional variants
  - "en" matches "en-US", "en-GB", "en-AU" automatically
  - "nl" matches "nl-NL", "nl-BE" automatically  
  - TMX import now handles language variants gracefully
  - User can choose to strip variants or preserve them during import
  - Supports bidirectional matching with variants (e.g., nl-BE → en-US works both ways)

- 💾 **Activated TM Persistence** - Projects remember which TMs are active
  - Activated TMs saved to `project.json` in `tm_settings.activated_tm_ids`
  - Automatically restored when project is reopened
  - No more manually re-activating TMs for each project session
  - Works per-project (different projects can have different active TMs)

- 📝 **TM Pre-Check in Batch Translation** - Saves API costs by checking TM first
  - Before making expensive API calls, checks if 100% TM matches exist
  - Auto-inserts TM matches and skips API translation for those segments
  - Shows clear log of how many API calls were saved
  - Can save significant costs on projects with high TM leverage
  - Controlled by "Check TM before API call" setting (enabled by default)

- 🎨 **Language Display Normalization** - Consistent language variant format
  - All language variants displayed as lowercase-UPPERCASE (e.g., nl-NL, en-US)
  - Previously: inconsistent formats like "nl-nl", "EN-us", "NL-BE"
  - Now: standardized as "nl-NL", "en-US", "nl-BE"
  - Applied in TM manager UI, TMX import dialogs, and all TM displays

**Technical Details:**

- **Find/Replace Highlighting:**
  - `highlight_search_term()` rewritten to use `QTextCursor` and `QTextCharFormat`
  - `highlight_all_matches()` rewritten to actually highlight instead of just filtering
  - Added `processEvents()` after grid load to ensure widgets exist before highlighting
  - Files: `Supervertaler.py` lines 15726-15792, 15982-16008

- **TM Save Prevention:**
  - Added `find_replace_active` flag check in `_handle_target_text_debounced_by_id()` (line 13660)
  - Added same check in `update_status_icon()` (line 13703)
  - Added check in `on_cell_selected()` to skip TM/MT/LLM lookups (line 14050)
  - Files: `Supervertaler.py` lines 13657-13664, 13699-13709, 14044-14051

- **Bidirectional Search:**
  - `get_exact_match()` now searches reverse direction if no forward match found
  - `search_fuzzy_matches()` includes reverse direction results
  - Results marked with `reverse_match: True` metadata
  - Files: `modules/database_manager.py` lines 635-732, 744-810

- **Language Variant Matching:**
  - Added `get_base_lang_code()` to extract base from variants (en-US → en)
  - Added `normalize_lang_variant()` for consistent display formatting
  - Added `languages_are_compatible()` for base code comparison
  - Database queries use LIKE pattern: `(source_lang = 'en' OR source_lang LIKE 'en-%')`
  - Files: `modules/tmx_generator.py` lines 119-156, `modules/database_manager.py` lines 652-676

- **TMX Import with Variants:**
  - `detect_tmx_languages()` reads all language codes from TMX
  - `check_language_compatibility()` analyzes variant mismatches
  - `_load_tmx_into_db()` accepts `strip_variants` parameter
  - User dialog offers "Import with variant stripping" vs "Create new TM"
  - Files: `modules/translation_memory.py` lines 408-557, `Supervertaler.py` lines 4807-4903

- **TM Persistence:**
  - Added `tm_settings` field to `Project` class (line 223)
  - `save_project_to_file()` saves activated TM IDs (lines 11442-11449)
  - `load_project()` restores activated TMs (lines 10797-10816)
  - Files: `Supervertaler.py` lines 220-285, 10794-10816, 11439-11449

**User Experience:**

- Find/Replace dialog now fast and responsive with proper highlighting
- "Highlight All" button finally works as expected
- No font size changes during search navigation
- TMs work across language variants automatically (no manual configuration)
- Projects remember your TM activation choices
- Batch translation saves money by checking TM first
- Clear visual feedback for all TM operations

---

## [1.7.8] - November 22, 2025

### 🔍 Filter Highlighting Fix

**Fixed:**

- ✨ **Filter Search Term Highlighting** - Fixed highlighting of search terms in filtered segments
  - Source and target filter boxes now correctly highlight matching terms in yellow
  - Previously used delegate-based highlighting which was bypassed by cell widgets
  - New implementation uses widget-internal highlighting with QTextCursor + QTextCharFormat
  - Case-insensitive matching: "test", "TEST", "TeSt" all match "test"
  - Multiple matches per cell are highlighted correctly
  - Highlights automatically clear when filters are removed

**Technical Details:**

- **Root Cause:** Source/target cells use `setCellWidget()` with QTextEdit widgets, which completely bypass `QStyledItemDelegate.paint()` method
- **Solution:** Created `_highlight_text_in_widget()` method that applies highlighting directly within QTextEdit widgets
- **Implementation:**
  - Uses `QTextCursor` to find all occurrences of search term in widget's document
  - Applies `QTextCharFormat` with yellow background (#FFFF00) to each match
  - Clears previous highlights before applying new ones
  - Modified `apply_filters()` to call widget highlighting instead of delegate approach
  - `clear_filters()` automatically clears highlights by reloading grid
- **Files Modified:**
  - `Supervertaler.py` (lines ~15765-15810): New `_highlight_text_in_widget()` method
  - `Supervertaler.py` (lines ~15779-15860): Modified `apply_filters()` to use widget highlighting
- **Documentation Added:**
  - `docs/FILTER_HIGHLIGHTING_FIX.md` - Complete technical explanation of the fix

**User Experience:**

- Filter boxes now work as expected with visible yellow highlighting
- Improves searchability and visual feedback when filtering segments
- No performance impact with large segment counts (tested with 219 segments)

---

## [1.7.7] - November 21, 2025

### 🎯 Termbase Display Customization

**Added:**

- ✨ **User-Configurable Termbase Sorting** - Control how termbase matches are displayed
  - Three sorting options available in Settings → General:
    - **Order of appearance in source text** (default) - Matches appear as they occur in the segment
    - **Alphabetical (A-Z)** - Matches sorted by source term alphabetically
    - **By length (longest first)** - Longer multi-word terms prioritized over shorter ones
  - Sorting preference persists across sessions
  - Only affects termbase matches; TM, MT, and LLM results maintain their existing order

- ✨ **Smart Substring Filtering** - Reduces termbase match clutter
  - Optional "Hide shorter termbase matches" checkbox in Settings → General
  - Automatically filters out shorter terms that are fully contained within longer matched terms
  - Example: If both "cooling" and "cooling system" match, only "cooling system" is shown
  - Helps focus on the most relevant multi-word terminology
  - Can be toggled on/off without restarting the application

**Enhanced:**

- 🔧 **Bold Font for Project Resources** - Project termbases and TMs now display with bold provider codes (TB, TM) instead of asterisks for cleaner visual distinction
- 🎨 **Translation Results Panel** - Added parent app reference for accessing user settings dynamically

**Technical Details:**

- Settings stored in `ui_preferences.json` under `general_settings`
- `TranslationResultsPanel` now accepts `parent_app` parameter for settings access
- New methods: `_sort_termbase_matches()` and `_filter_shorter_matches()` in `translation_results_panel.py`
- Sorting uses case-insensitive comparison for alphabetical mode
- Filtering uses substring detection with length comparison
- Files Modified:
  - `Supervertaler.py` (lines 2391-2393, 7377-7406, 8316-8360, 8930, 9548, 12604-12606)
  - `modules/translation_results_panel.py` (lines 626-628, 1201-1276, 1324-1329)

**User Experience:**

- Settings are immediately accessible via Settings → General → TM/Termbase Options
- Tooltips explain each option clearly
- Changes apply to all subsequent segment matches
- No performance impact on match retrieval

---

## [1.7.6] - November 20, 2025

### 💾 Auto Backup System

**Added:**

- ✨ **Automatic Backup System** - Prevents data loss during translation work
  - Auto-saves project.json at configurable intervals (1-60 minutes, default: 5 minutes)
  - Auto-exports TMX backup file in same folder as project.json
  - TMX backup includes all segments for maximum recovery capability
  - Settings UI in Settings → General tab with enable/disable toggle
  - Non-intrusive background operation with timestamp logging
  - Settings persist across sessions in ui_preferences.json
  - Timer automatically restarts when settings are changed

**Technical Details:**

- QTimer-based system with millisecond precision
- Uses existing `save_project_to_file()` and `TMXGenerator` methods
- Graceful error handling without interrupting workflow
- Only runs when project is open and has a file path
- TMX file named `{project_name}_backup.tmx` for easy identification

---

## [1.7.5] - November 20, 2025

### 🐛 Critical Bug Fix - Translation Memory Save Flood

**Fixed:**

- ✅ **TM Save Flood During Grid Operations** - CRITICAL FIX
  - **Issue:** Every time `load_segments_to_grid()` was called (startup, filtering, clear filters), all segments with status "translated"/"confirmed"/"approved" would trigger false TM database saves 1-2 seconds after grid load
  - **Symptoms:**
    - 10+ second UI freeze on projects with 200+ segments
    - Massive unnecessary database writes (219 saves on a 219-segment project)
    - Made filtering operations completely unusable
    - Could potentially corrupt data or cause performance issues on large projects
  - **Root Cause:** Qt internally queues document change events when `setPlainText()` is called on QTextEdit widgets, even when signals are blocked. When `blockSignals(False)` was called after grid loading, Qt delivered all these queued events, triggering `textChanged` for every segment. By that time, the suppression flag had already been restored, so the suppression check failed.
  - **Solution:**
    - Added `_initial_load_complete` flag to `EditableGridTextEditor` class
    - Signal handler now ignores the first spurious `textChanged` event after widget creation
    - All subsequent real user edits are processed normally
    - Clean, minimal fix that doesn't interfere with Qt's event system
  - **Testing:** Verified on BRANTS project (219 segments) - zero false TM saves during startup, filtering, and filter clearing
  - **Files Modified:** Supervertaler.py (lines 835, 11647-11651)

**Impact:**
- **Performance:** Grid loading is now instant with no post-load freeze
- **Database:** Eliminates 200+ unnecessary database writes per grid operation
- **User Experience:** Filtering and grid operations are now fast and responsive
- **Data Integrity:** Prevents potential database corruption from excessive writes

---

## [1.7.4] - November 20, 2025

### 💾 Project Persistence Improvements

**Enhanced:**

- ✅ **Primary Prompt Persistence** - Projects now remember your selected primary prompt
  - Automatically restores primary prompt when reopening project
  - Updates UI label to show active prompt name
  - Works with Unified Prompt Library system
  
- ✅ **Image Context Folder Persistence** - Projects remember loaded image folders
  - Image context folder path saved to project.json
  - Automatically reloads all images from saved folder on project open
  - Updates UI status label showing image count and folder name
  - Logs success/warnings if folder path has changed
  
- ✅ **Attached Prompts Persistence** - All attached prompts are restored
  - Maintains complete prompt configuration across sessions
  - Updates attached prompts list UI on restore

**Technical:**
- Changed from `library.set_primary_prompt()` to `_set_primary_prompt()` for UI updates
- Changed from `library.attach_prompt()` to `_attach_prompt()` for UI updates
- Added `image_context_folder` to `prompt_settings` in project.json
- Proper UI synchronization on project load for all prompt settings

**User Experience:**
Now when you save a project, it remembers:
- ✓ Which primary prompt you selected
- ✓ Which prompts you attached
- ✓ Which image folder you loaded
- ✓ All settings restore automatically on project open

---

## [1.7.3] - November 20, 2025

### 🧪 Prompt Preview & System Template Improvements

**New Features:**

**Added:**
- ✅ **Preview Combined Prompts Button** - New "🧪 Preview Prompts" button in Project Editor segment action bar
  - Shows complete assembled prompt that will be sent to AI
  - Displays System Template + Custom Prompts + current segment text
  - Real-time composition info (segment ID, languages, character count, attached prompts)
  - Visual context indicator showing which images will be sent alongside text
  - Clear tooltip explaining functionality

**Enhanced:**
- ✅ **System Template Editor** - Improved layout and usability in Settings → System Prompts
  - Increased text editor height from 400px to 500px
  - Added stretch factors for proper expansion to fill available space
  - Enabled word wrap at widget width for easier reading
  - Set plain text mode to prevent formatting issues
  
- ✅ **Figure Context Detection** - Fixed regex pattern for accurate figure reference detection
  - Now correctly matches "Figuur 3" → "3" instead of "3toont"
  - Properly handles subfigures (e.g., Figure 1A, 2B)
  - Requires space between "figuur/figure/fig" and number

**Improved:**
- ✅ **Image Context Preview** - Preview dialog now shows detailed image information
  - 🖼️ Displays which images will be sent with prompt (e.g., "Figure 3")
  - ⚠️ Warns if references detected but images not found
  - ℹ️ Shows info when images loaded but not referenced in segment
  - Yellow banner highlights when images are being sent as binary data

**Technical:**
- Updated `UnifiedPromptManagerQt._preview_combined_prompt()` to access actual segment data
- Added `_preview_combined_prompt_from_grid()` method in main app
- Fixed attribute reference from `self.unified_prompt_manager` to `self.prompt_manager_qt`
- Improved figure reference regex from `[\w\d]+(?:[\s\.\-]*[\w\d]+)?` to `\d+[a-zA-Z]?`

---

## [1.7.2] - November 19, 2025

### 🔧 Termbase Critical Fixes - Term Deduplication & Selection

**Major Bug Fixes:**

**Fixed:**
- ✅ **Multiple Translations Display** - Fixed critical deduplication bug where only one translation was kept for terms with same source text
  - Example: "inrichting → device" AND "inrichting → apparatus" now both display correctly
  - Root cause: Used `source_term` as dict key, now uses `term_id` to allow multiple translations
- ✅ **Termbase Selection** - Terms now save only to selected termbases (previously saved to all active termbases)
  - Filter logic working correctly with INTEGER termbase IDs
  - Debug logging confirmed type matching works as expected
- ✅ **Segment Highlighting Consistency** - Termbase highlighting now works consistently across all segments
  - Fixed cache iteration to handle new dict structure with `term_id` keys
  - Updated all code paths that consume termbase matches

**Technical Changes:**
- **Dictionary Structure Change:**
  - Changed from: `matches[source_term] = {...}` (only one translation per source)
  - Changed to: `matches[term_id] = {'source': source_term, 'translation': target_term, ...}` (multiple translations allowed)
- **Code Locations Updated:**
  - `find_termbase_matches_in_source()` - Changed dict key from source_term to term_id
  - `highlight_termbase_matches()` - Updated to extract source term from match_info
  - `DocumentView._create_highlighted_html()` - Updated iteration logic
  - `_get_cached_matches()` - Fixed to extract source term from dict values (2 locations)
  - All hover tooltip and double-click handlers updated

**Impact:**
- 🎯 **Better Term Disambiguation** - Users can now add multiple translations for same source term
- 🎨 **Accurate Highlighting** - All matching terms highlighted correctly in grid
- ✅ **Correct Termbase Selection** - Terms added only to user-selected termbases

---

## [1.7.1] - November 19, 2025

### 🎨 Termbase UI Polish - Visual Consistency Improvements

**Bug Fixes & UI Improvements:**

**Fixed:**
- ✅ **Deleted Term Highlighting** - Fixed issue where deleted termbase terms remained highlighted after deletion and navigation
- ✅ **Termbase Name Display** - Termbase names now correctly shown in Term Info metadata area
- ✅ **Term Count Updates** - Term counts in termbase list now update immediately after adding terms
- ✅ **Project Termbase Colors** - Fixed project termbases showing blue instead of pink in translation results
- ✅ **Ranking Metadata** - Added missing `ranking` field to TranslationMatch metadata in all code paths

**Improved:**
- 🎨 **Visual Consistency** - Project termbase matches now display with same style as background termbases (colored number badge only)
- 🎯 **Effective Project Detection** - Uses `ranking == 1` as fallback when `is_project_termbase` flag is false
- 🔄 **Real-time Refresh** - Termbase list UI refreshes immediately via callback after term addition
- 📊 **Database Query Fix** - Fixed TEXT/INTEGER comparison with CAST for accurate term counts

**Technical:**
- Modified `highlight_termbase_matches()` to clear formatting before early return
- Added `termbase_name` extraction and display in translation results panel
- Implemented `refresh_termbase_list()` callback storage and invocation
- Added explicit boolean conversion for `is_project_termbase` from SQLite
- Updated `CompactMatchItem.update_styling()` to use consistent badge-only coloring
- Fixed two locations where `ranking` was missing from TranslationMatch metadata

---

## [1.7.0] - November 18, 2025

### 📚 Project Termbases - Dedicated Project Terminology

**Project-Specific Terminology Management** - A powerful new termbase system that distinguishes between project-specific terminology (one per project) and background termbases (multiple allowed), with automatic term extraction from project source text.

### Added

**Project Termbase System:**
- 📌 **Project Termbase Designation** - Mark one termbase per project as the official project termbase
- 🎨 **Pink Highlighting** - Project termbase matches highlighted in light pink (RGB 255, 182, 193) in both grid and results panel
- 🔵 **Background Termbases** - Regular termbases use priority-based blue shading as before
- 🔍 **Term Extraction** - Automatically extract terminology from project source segments
- 🧠 **Smart Algorithm** - Frequency analysis, n-gram extraction, scoring based on capitalization and special characters
- 🌐 **Multi-Language Support** - Stop words for English, Dutch, German, French, Spanish
- 📊 **Preview & Select** - Review extracted terms with scores before adding to termbase
- 🎯 **Configurable Parameters** - Adjust min frequency, max n-gram size, language, term count
- ⚙️ **Standalone Module** - Term extractor designed as independent module (`modules/term_extractor.py`) for future CLI tool

**Termbases Tab Enhancements:**
- 📋 **Type Column** - Shows "📌 Project" in pink or "Background" for each termbase
- 🔘 **Set/Unset Buttons** - Easy designation of project termbases
- 🔍 **Extract Terms Button** - Launch term extraction dialog (only enabled with project loaded)
- 🎨 **Visual Distinction** - Project termbase names shown in pink
- 🔒 **Validation** - System enforces "only one project termbase per project" rule

**Database Schema:**
- 🗄️ **is_project_termbase Column** - Added to termbases table with migration
- ✅ **Backward Compatible** - Existing databases upgraded automatically

**Termbase Manager Extensions:**
- `set_as_project_termbase(termbase_id, project_id)` - Designate project termbase
- `unset_project_termbase(termbase_id)` - Remove designation
- `get_project_termbase(project_id)` - Retrieve project termbase
- Enhanced `create_termbase()` with `is_project_termbase` parameter and validation
- Enhanced `get_all_termbases()` to sort project termbase first

**Match Pipeline Integration:**
- 🔗 **Metadata Tracking** - `is_project_termbase` flag passed through entire match pipeline
- 🎨 **Grid Highlighting** - Light pink backgrounds for project termbase matches in source column
- 📋 **Results Panel** - Light pink number badges for project termbase matches

### Changed
- Updated termbase search to include `is_project_termbase` field
- Modified `highlight_termbase_matches()` to use pink for project termbases
- Enhanced `TranslationMatch` metadata to capture project termbase status
- Updated `CompactMatchItem` styling to handle three-way color logic (forbidden=black, project=pink, background=blue)

### Technical Details
- **Term Extraction Algorithm:**
  - N-gram extraction (unigrams, bigrams, trigrams)
  - Frequency-based scoring with logarithmic scaling
  - Bonuses for capitalization (+3), special characters (+2), n-gram size (+1.5 per word)
  - Term classification: proper_noun, technical, phrase, word
  - Configurable filtering by frequency, type, score
- **Color Scheme:**
  - Project Termbase: `#FFB6C1` (light pink)
  - Forbidden Terms: `#000000` (black)
  - Background Termbases: `#4d94ff` (blue with priority-based darkening)

### Use Cases
- **Starting New Projects** - Extract project-specific terminology automatically
- **Consistency** - Ensure project terminology has visual precedence
- **Background Knowledge** - Maintain general termbases alongside project-specific ones
- **Source-Only Termbases** - Perfect for extracting terms before translation begins

---

## [1.6.5] - November 18, 2025

### 📁 File Dialog Memory - Smart Directory Navigation

**File Dialogs Remember Your Last Location** - A quality-of-life improvement that significantly streamlines workflow by automatically remembering the last directory you navigated to across all file dialogs throughout the application.

### Added

**File Dialog Helper System:**
- 📁 **Last Directory Memory** - File dialogs automatically open in the last used directory
- 💾 **Persistent Storage** - Last directory saved to config file between sessions
- 🔄 **Universal Coverage** - Works for all dialog types (open file, save file, select folder, multiple files)
- 🎯 **Automatic Detection** - Extracts directory from file paths automatically
- 🛠️ **Helper Module** - Created `modules/file_dialog_helper.py` with wrapper functions

**Config Manager Enhancements:**
- Added `get_last_directory()` - Retrieve the last used directory
- Added `set_last_directory()` - Save a directory as the last used location
- Added `update_last_directory_from_file()` - Extract and save directory from file path

**Integration Points:**
- Image Extractor (add DOCX files, select folder, output directory)
- TMX import/export dialogs
- Project open/save dialogs
- Export dialogs (JSON, TMX, etc.)

**Benefits:**
- No more navigating from program root every time
- Improved workflow when working with files in the same folder
- Transparent operation - works automatically without configuration
- Persists between application sessions

### Technical Implementation
- Created `modules/file_dialog_helper.py` with `get_open_file_name()`, `get_save_file_name()`, `get_existing_directory()`, `get_open_file_names()` wrappers
- Extended `config_manager.py` with directory tracking methods
- Updated key file dialog calls in `Supervertaler.py` to use helper functions
- Last directory stored in `~/.supervertaler_config.json` (or dev mode equivalent)

---

## [1.6.4] - November 18, 2025

### 🌐 Superbrowser - Multi-Chat AI Browser

**Work with Multiple AI Chats Simultaneously** - A revolutionary new tab that displays ChatGPT, Claude, and Gemini side-by-side in resizable columns with persistent login sessions, perfect for comparing AI responses or maintaining multiple conversation threads.

### Added

**Superbrowser Tab:**
- 🌐 **Three-Column Layout** - ChatGPT, Claude, and Gemini displayed simultaneously in resizable columns
- 🔐 **Persistent Sessions** - Login credentials saved between sessions (no need to log in every time)
- 🔧 **Collapsible Configuration** - Hide/show URL configuration panel to maximize screen space
- 🎨 **Color-Coded Columns** - Each AI provider has distinct color (green, copper, blue)
- 🏠 **Navigation Controls** - URL bar, reload, and home buttons for each column
- 💾 **Profile Storage** - Separate persistent storage for each AI provider (cookies, cache, sessions)
- 📱 **Minimal Headers** - Tiny 10px headers maximize space for chat windows
- 🎯 **Dev Mode Support** - Uses `user_data_private/` for dev mode, `user_data/` for production

**Technical Implementation:**
- Created `modules/superbrowser.py` - Standalone module with `SuperbrowserWidget`
- Integrated QtWebEngine with OpenGL context sharing for proper rendering
- Added persistent profile management using `QWebEngineProfile`
- Implemented `ChatColumn` class for individual browser columns
- Added to Specialised Tools as "🌐 Superbrowser" tab

**Use Cases:**
- Compare how different AI models respond to the same prompt
- Maintain separate conversation threads for different projects
- Quick access to all major AI assistants without switching browser tabs
- Research and development with multiple AI perspectives

### Fixed
- QtWebEngine DLL compatibility issues resolved (version matching)
- OpenGL context sharing properly initialized before QApplication creation
- Profile storage paths follow application's dev mode patterns

### Dependencies
- Added `PyQt6-WebEngine>=6.8.0,<6.9.0` requirement (version matched to PyQt6 6.8.1)

---

## [1.6.3] - November 18, 2025

### ⚡ UI Responsiveness & Precision Scroll Enhancements

**Major Performance Improvements & memoQ-Style Navigation** - Comprehensive UI responsiveness optimizations including debug settings system, disabled LLM auto-matching by default, precision scroll buttons, and auto-center active segment feature.

### Added

**Debug Settings System:**
- 🐛 **Debug Settings Tab** - New dedicated tab in Settings dialog for debugging and performance tuning
- 📝 **Verbose Logging Toggle** - Enable/disable detailed debug logs (textChanged events, update cycles, cell selection)
- 📤 **Debug Log Export** - Export debug logs to timestamped files (`supervertaler_debug_log_YYYYMMDD_HHMMSS.txt`)
- 🔄 **Auto-export Option** - Automatically export debug logs on application exit
- 🗑️ **Clear Log Buffer** - Manual clear button for debug log buffer (10,000 entry limit)
- ⏱️ **Debounce Delay Control** - Spinbox to adjust target text debounce delay (100-5000ms range, default 1000ms)
- ⚠️ **Performance Warnings** - Clear warnings about performance impact of verbose logging

**Precision Scroll Controls:**
- ⬆️⬇️ **Precision Scroll Buttons** - memoQ-style ▲▼ buttons for fine-grained grid scrolling
- 🎯 **Fixed Pixel Scrolling** - Uses fixed pixel amounts (5-50px) instead of variable row heights for predictable movement
- 🎚️ **Adjustable Precision** - Spinbox setting (1-10 divisor) to control scroll increment size:
  - Divisor 1 = 50 pixels (coarse)
  - Divisor 3 = 40 pixels (default)
  - Divisor 5 = 30 pixels (fine)
  - Divisor 10 = 5 pixels (very fine)
- 📊 **Live Preview** - Setting shows "Coarse/Medium/Fine/Very fine" label based on divisor value
- 📍 **Smart Positioning** - Buttons positioned to left of scrollbar, never cut off or overlapping
- 🎨 **Hover Effects** - Blue highlight on hover, visual feedback on click
- 🔄 **Auto-repositioning** - Buttons reposition on window resize and table changes

**Auto-Center Active Segment:**
- 🎯 **Keep Active Segment Centered** - Optional toggle to auto-scroll and center selected segment in viewport
- 🔄 **CAT Tool Behavior** - Matches memoQ, Trados, and other professional CAT tools
- ✅ **Settings Persistence** - Auto-center preference saved to `ui_preferences.json`
- 🖱️ **Smooth Navigation** - Active segment always visible and centered when navigating

**Performance Optimizations:**
- 🚫 **LLM Auto-matching Disabled by Default** - Changed `enable_llm_matching` from `True` to `False` to prevent 10-20 second UI freezes
- ⚡ **Conditional Debug Logging** - All verbose logs wrapped in `if self.debug_mode_enabled:` checks
- ⏱️ **Increased Debounce Delay** - Target text change debounce increased from 500ms to 1000ms
- 🎛️ **LLM Matching Toggle** - Added checkbox in General Settings with warning tooltip
- 💾 **Settings Persistence** - Debug mode, LLM matching, precision scroll, and auto-center settings saved/loaded

**UI/UX Improvements:**
- 📑 **Precision Scroll Settings Section** - New section in General Settings with all scroll-related controls
- ℹ️ **Helpful Tooltips** - Detailed explanations for all new settings
- ⚠️ **Warning Messages** - Clear warnings about LLM performance impact (10-20 sec per segment)
- 🎨 **Consistent Styling** - Settings UI follows existing design patterns

### Changed

- 🔧 **Default LLM Behavior** - LLM translations no longer trigger automatically on segment selection (use "Translate with AI" button instead)
- ⏱️ **Debounce Timing** - Target text debounce delay increased from 500ms to 1000ms for better stability
- 📊 **Debug Logging** - Performance-heavy debug logs now conditional (only when debug mode enabled)
- 🎯 **Scroll Algorithm** - Precision scroll now uses fixed pixel amounts instead of row-height-based calculations

### Fixed

- 🐛 **UI Freezing on Segment Selection** - Eliminated 10-20 second freezes caused by automatic LLM API calls
- 🐛 **Unpredictable Scroll Jumping** - Fixed precision scroll skipping segments due to variable row heights
- 🐛 **Button Positioning** - Fixed scroll buttons being cut off by scrollbar
- 🐛 **Method Name Mismatch** - Fixed `create_tabbed_assistance_panel` vs `create_assistance_panel` naming error
- 🐛 **Duplicate Method Definition** - Removed duplicate `position_precision_scroll_buttons` method
- 🐛 **TranslationResultsPanel Initialization** - Fixed incorrect `main_window` and `match_limits` parameters

### Technical Details

**Files Modified:**
- `Supervertaler.py` - Core application with all new features
- `ui_preferences.json` - Stores debug_mode_enabled, debug_auto_export, enable_llm_matching, precision_scroll_divisor, auto_center_active_segment

**Performance Impact:**
- MT engines (1-2 sec) remain enabled for auto-matching ✅
- LLM translations (10-20 sec) now on-demand only (via button) ✅
- Debug logging overhead eliminated in production use ✅
- Smoother segment navigation with predictable scroll behavior ✅

**Location:**
- Settings → 🐛 Debug (Debug settings tab)
- Settings → General Settings (LLM matching toggle, precision scroll settings)
- Grid → Right edge (Precision scroll buttons ▲▼)

---

## [1.6.2] - November 17, 2025

### 🖼️ Image Extractor (Superimage)

**Extract Images from DOCX Files** - New tool for extracting all images from DOCX files with preview and batch processing capabilities.

### Added

**Image Extraction:**
- 📄 **DOCX Image Extractor** - Extract all images from DOCX files (located in word/media/ folder)
- 🖼️ **PNG Output** - Convert all image formats to PNG with sequential naming (Fig. 1.png, Fig. 2.png, etc.)
- 📁 **Auto-folder Mode** - Option to automatically create "Images" subfolder next to source DOCX
- 📚 **Batch Processing** - Add multiple DOCX files or entire folders for bulk extraction
- 🎯 **Custom Prefix** - Configurable filename prefix (default: "Fig.")

**Image Preview:**
- 👁️ **Click-to-Preview** - Click any extracted file in list to view in preview panel
- 🖼️ **Resizable Preview** - Horizontal splitter between results and preview (60% preview area)
- ⬅️➡️ **Navigation Buttons** - Previous/Next buttons synced with file list
- 🔍 **Auto-scaling** - Images automatically scaled to fit viewport while maintaining aspect ratio

**UI/UX:**
- 🎨 **Compact Layout** - Optimized vertical space with single-row controls
- 📝 **Resizable Status Log** - Extraction progress log with minimum 50px height
- 📋 **File List Management** - Add files, add folder, clear list functionality
- 🛠️ **Tools Menu Integration** - Quick access via Tools → Image Extractor (Superimage)

**Technical:**
- 🔧 **New Module** - `modules/image_extractor.py` with `ImageExtractor` class
- 📖 **Documentation** - Complete user guide in `modules/IMAGE_EXTRACTOR_README.md`
- 🧪 **Test Script** - `tests/test_image_extractor.py` for validation
- 🎨 **PIL/Pillow** - Image format conversion (RGBA→RGB with white background)

**Location:**
- Translation Resources → Reference Images tab
- Tools → Image Extractor (Superimage)...

---

## [1.6.1] - November 17, 2025

### 📚 Enhanced Termbase Metadata System

**Extended Metadata & Improved UX** - Comprehensive termbase metadata with notes, project, and client fields, plus instant refresh functionality.

### Added

**Enhanced Metadata Fields:**
- 📝 **Notes Field** - Multi-line notes field replacing old definition field for context, usage notes, and URLs
- 🔗 **Clickable URLs** - URLs in notes automatically become clickable links (opens in external browser)
- 📁 **Project Field** - Track which project a term belongs to
- 👤 **Client Field** - Associate terms with specific clients
- 🏷️ **Domain Field** - Already existed, now fully integrated throughout system

**Termbase Viewer Enhancements:**
- 📖 **Dedicated Termbase Viewer** - New panel at bottom of Translation Results showing selected termbase entry
- 🔄 **Refresh Data Button** - Manual refresh button to reload latest data from database
- ✏️ **Edit Button** - Direct access to edit dialog from termbase viewer
- 🖱️ **Right-Click Edit** - Context menu on termbase matches for quick editing
- ♻️ **Auto-Refresh on Edit** - Termbase viewer automatically updates after editing entry

**Improved Table Views:**
- 📊 **Extended Columns** - Edit Terms dialog now shows: Source, Target, Domain, Priority, Notes (truncated), Project, Client, Forbidden
- 📏 **Smart Column Widths** - Optimized column sizing for better visibility
- ✂️ **Notes Truncation** - Long notes truncated to 50 characters with "..." in table view

**Database Enhancements:**
- 🗄️ **Database Migration System** - Automated schema updates for backward compatibility
- ➕ **New Columns** - Added `notes`, `project`, `client` columns to `termbase_terms` table
- 🔗 **Synonyms Table** - Created `termbase_synonyms` table structure (foundation for future feature)
- 🔄 **Legacy Support** - Old `definition` column preserved for backward compatibility

### Fixed

**Metadata Flow Issues:**
- ✅ **Complete Metadata Chain** - All termbase metadata now flows correctly: Dialog → Database → Search → Display
- ✅ **Edit Button Caching** - Fixed issue where edit buttons didn't work until adding first new term
- ✅ **Thread-Safe Queries** - Background termbase worker now includes all metadata fields (term_id, termbase_id, etc.)
- ✅ **Initial Load** - Termbase matches loaded at startup now include full metadata for immediate editing
- ✅ **Field Consistency** - Standardized on "notes" (plural) throughout codebase

**UI/UX Improvements:**
- ✅ **Visible Refresh Button** - Changed from just "🔄" to "🔄 Refresh data" for better visibility
- ✅ **Metadata Display** - Termbase viewer shows all fields with proper formatting
- ✅ **URL Rendering** - QTextBrowser with `setOpenExternalLinks(True)` for clickable links
- ✅ **Edit Dialog Fields** - Updated TermMetadataDialog to show notes, project, client (removed old definition field)

### Changed

**API Updates:**
- 🔄 **termbase_manager.add_term()** - Updated signature to accept `notes`, `project`, `client` instead of `definition`
- 🔄 **termbase_manager.get_terms()** - Now returns all new fields in term dictionaries
- 🔄 **termbase_manager.update_term()** - Updated to handle new field structure
- 🔄 **database_manager.search_termbases()** - SELECT query includes all new columns
- 🔄 **TranslationMatch metadata** - All creation points include complete metadata with IDs

**Code Quality:**
- 📦 **Modular Migrations** - `database_migrations.py` handles all schema updates
- 🔒 **Type Safety** - Proper Optional types for new fields throughout
- 🧹 **Cleanup** - Removed all references to old "definition" field (except database column for compatibility)

### Technical Details

**Database Migration:**
```sql
-- Migration adds new columns to termbase_terms
ALTER TABLE termbase_terms ADD COLUMN notes TEXT;
ALTER TABLE termbase_terms ADD COLUMN project TEXT;
ALTER TABLE termbase_terms ADD COLUMN client TEXT;

-- New synonyms table (foundation for future feature)
CREATE TABLE IF NOT EXISTS termbase_synonyms (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    term_id INTEGER NOT NULL,
    synonym_text TEXT NOT NULL,
    language TEXT NOT NULL,
    created_date TEXT,
    FOREIGN KEY (term_id) REFERENCES termbase_terms(id) ON DELETE CASCADE
);
```

**Metadata Flow:**
1. **Add Term**: TermMetadataDialog → get_metadata() → add_term_pair_to_termbase() → termbase_mgr.add_term() → Database INSERT
2. **Load Terms**: Database SELECT → search_termbases() → TranslationMatch metadata → Termbase viewer display
3. **Edit Term**: Edit button → TermbaseEntryEditor → update_term() → Database UPDATE → Refresh viewer
4. **Cache Population**: Background worker → _search_termbases_thread_safe() → Complete metadata → termbase_cache

---

## [1.6.0] - November 16, 2025

### 📚 Complete Termbase System with Interactive Features

**The Ultimate Terminology Management** - Full-featured termbase system rivaling commercial CAT tools with memoQ-inspired interactive features.

### Added

**Core Termbase Features:**
- 📊 **SQLite-Based Storage** - Robust database backend for termbases and terms
- 🔍 **Real-Time Term Matching** - Automatic detection of termbase matches in source segments
- 🎨 **Priority-Based Highlighting** - Terms highlighted in source cells with color intensity matching priority (1-99)
- 🎯 **Visual Match Display** - All termbase matches shown in Translation Results panel with metadata
- ⚫ **Forbidden Term Marking** - Forbidden terms highlighted in black (source cells and translation results)
- 🗂️ **Multi-Termbase Support** - Create and manage multiple termbases per project
- ✅ **Termbase Activation** - Enable/disable specific termbases for each project

**Interactive Features (memoQ-Inspired):**
- 💡 **Hover Tooltips** - Mouse over highlighted terms to see translation, priority, and forbidden status
- 🖱️ **Double-Click Insertion** - Double-click any highlighted term to insert translation at cursor
- 📝 **Dual Selection Workflow** - Select source term → Tab → select target translation → Ctrl+E to add
- 🎹 **Keyboard Shortcuts** - Ctrl+E to add term pair, right-click context menu alternative

**Termbase Management UI:**
- 📋 **Termbase List** - View all termbases with term counts and activation toggles
- ➕ **Create/Delete** - Full CRUD operations with confirmation dialogs
- ✏️ **Edit Terms Dialog** - Modify source/target terms, priority (1-99), and forbidden flag
- 🔢 **Priority Editing** - Click priority cells to edit directly in table
- 🚫 **Forbidden Toggle** - Checkbox for marking terms as forbidden (do-not-use)
- 📊 **Metadata Entry** - Add definition, domain, priority, and forbidden status when creating terms

**Technical Implementation:**
- 🗄️ **Three-Table Schema** - `termbases`, `termbase_terms`, `termbase_activation` for flexible management
- 🔍 **FTS5 Full-Text Search** - Fast term matching even with large termbases
- 💾 **Smart Caching** - Term matches cached per segment for performance
- 🔄 **Automatic Refresh** - Adding/editing terms immediately updates highlighting and results
- 🎨 **QTextCharFormat Highlighting** - Non-intrusive background color without replacing widgets
- 🖱️ **Mouse Tracking** - Enable hover detection with `setMouseTracking(True)`
- 📍 **Position Detection** - `cursorForPosition()` for finding text under mouse cursor

**Color System:**
- 🔵 **Priority Colors** - Higher priority (lower number) = darker blue, lower priority = lighter blue
- ⚫ **Forbidden Terms** - Black background (#000000) with white text for maximum visibility
- 🎨 **Consistent Rendering** - Same color scheme in source highlights and translation results

**Workflow Integration:**
- ⚡ **Fast Term Entry** - Select in source → Tab → select in target → Ctrl+E → done
- 🔄 **Immediate Visibility** - New terms appear instantly in highlights and results
- 📊 **Project-Based Activation** - Each project remembers which termbases are active
- 🎯 **Settings Toggle** - Enable/disable grid highlighting in Settings → General

### Fixed
- ✅ Language code handling - Proper conversion from language names (Dutch → nl, English → en)
- ✅ Term search issues - Fixed "unknown" language codes preventing matches
- ✅ Activation persistence - Termbase toggles now save correctly across sessions
- ✅ Priority editing - Term priority changes now persist to database
- ✅ Delete functionality - Delete button now works with confirmation dialog
- ✅ Project ID tracking - Hash-based project ID for termbase activation
- ✅ Highlight consistency - Clear formatting before re-applying to prevent accumulation
- ✅ Cache clearing - Both termbase_cache and translation_matches_cache cleared after changes

### Technical Details
**Database Schema:**
```sql
-- Termbases table
CREATE TABLE termbases (
    id INTEGER PRIMARY KEY,
    name TEXT UNIQUE NOT NULL,
    description TEXT,
    created_date TEXT,
    modified_date TEXT
)

-- Termbase terms with FTS5 search
CREATE VIRTUAL TABLE termbase_terms USING fts5(
    termbase_id UNINDEXED,
    source_term,
    target_term,
    source_lang,
    target_lang,
    definition,
    domain,
    priority UNINDEXED,
    forbidden UNINDEXED,
    created_date UNINDEXED,
    modified_date UNINDEXED
)

-- Project-specific termbase activation
CREATE TABLE termbase_activation (
    project_id TEXT NOT NULL,
    termbase_id INTEGER NOT NULL,
    is_active INTEGER DEFAULT 1,
    PRIMARY KEY (project_id, termbase_id)
)
```

**Key Classes:**
- `TermbaseManager` - Database operations and term search
- `ReadOnlyGridTextEditor` - Source cell with highlighting, tooltip, and double-click
- `TermMetadataDialog` - Modal dialog for entering term metadata
- `find_termbase_matches_in_source()` - Search engine returning match dict
- `highlight_termbase_matches()` - Visual highlighting with priority/forbidden colors

### Documentation
- Added comprehensive termbase workflow documentation
- Updated keyboard shortcuts reference
- Documented color system and priority levels
- Added tooltip and double-click feature guides

---

## [1.5.1] - November 16, 2025

### ⌨️ Source/Target Tab Cycling for Termbase Workflow

**New Feature:**
- 🔄 **Tab Key Cycling** - Press `Tab` in target cell to jump to source cell, then `Tab` again to return to target
  - Enables fast termbase workflow: select term in source, Tab to target, select translation
  - Works bidirectionally: Target → Source → Target
  - Both source and target cells support text selection with keyboard and mouse
  - Arrow keys work in both cells for cursor movement
- 🔠 **Ctrl+Tab** - Insert actual tab character when needed (in both source and target)

**Technical Implementation:**
- Source cells (`ReadOnlyGridTextEditor`) now intercept Tab at the `event()` level for reliable cycling
- Target cells (`EditableGridTextEditor`) handle Tab in `keyPressEvent()`
- Text selection enabled in source cells via `TextSelectableByKeyboard | TextSelectableByMouse` flags
- Focus policy set to `StrongFocus` on both cell types

**Workflow Benefits:**
- Facilitates termbase entry: select source term → Tab → select target translation → add to termbase
- Maintains active text selections in both cells simultaneously for termbase operations
- No need to click between cells, keyboard-only navigation

---

## [1.5.0] - November 15, 2025

### 🔍 Translation Results Enhancement + Match Insertion Shortcuts

**Major Features:**
- 🎯 **Progressive Match Loading** - Termbase, TM, MT, and LLM results now accumulate instead of replacing each other
- ⌨️ **Match Navigation Shortcuts** - `Ctrl+Up/Down` to cycle through translation matches from the grid
- 🚀 **Quick Insert Shortcuts** - `Ctrl+1-9` to instantly insert specific matches at cursor position
- ⏎ **Smart Match Insertion** - `Ctrl+Space`, `Space`, or `Enter` in results panel to insert selected match
- 🏷️ **Tag Display Control** - Optional setting to show/hide HTML/XML tags in translation results (Settings → View Settings)
- 📊 **Status Management** - Manual edits now reset segment status to "Not started" requiring explicit confirmation

**Bug Fixes:**
- ✅ Fixed translation results panel showing only the last match type (now accumulates all: termbase → TM → MT → LLM)
- ✅ Fixed `add_matches()` method not found error (implemented progressive match accumulation)
- ✅ Fixed `save_mode` parameter errors in TM saving (removed deprecated parameter)
- ✅ Fixed match insertion not working (now correctly inserts at cursor position in target cell)
- ✅ Fixed `scroll_area` AttributeError (corrected to `matches_scroll`)

**Keyboard Shortcuts Added:**
- `Ctrl+Up` - Navigate to previous match in results panel
- `Ctrl+Down` - Navigate to next match in results panel
- `Ctrl+1` through `Ctrl+9` - Insert match #1-9 at cursor position
- `Ctrl+Space` - Insert currently selected match
- `Space` or `Enter` - Insert selected match (when focused on results panel)

**Documentation:**
- Updated shortcut manager with complete match navigation and insertion shortcuts
- Added comprehensive shortcut documentation in Settings → Shortcuts section

**Technical Improvements:**
- Implemented `add_matches()` method for progressive match accumulation
- Added `insert_match_by_number()` for direct match insertion by number
- Added `insert_selected_match()` for keyboard-driven match insertion
- Improved `on_match_inserted()` to insert at cursor position using `textCursor().insertText()`
- Added tag formatting control with `show_tags` class variable and `_format_text()` method

---

## [1.4.0] - November 12, 2025

### 🎤 Major Feature: Supervoice Voice Dictation + Detachable Log Window

**AI-Powered Hands-Free Translation Input** - OpenAI Whisper voice dictation with 100+ language support, plus multi-monitor log window capability.

### Added
- **🎤 Supervoice Voice Dictation Module**
  - AI-powered speech recognition using OpenAI Whisper
  - Support for 100+ languages (as many as Whisper can handle)
  - Press-to-start, press-to-stop recording with F9 global hotkey
  - 5 model sizes: tiny, base, small, medium, large (balance speed vs accuracy)
  - Configurable in Settings → 🎤 Supervoice
  - Automatic FFmpeg detection and bundling support
  - User-friendly error messages with installation instructions
  - Visual feedback: button color changes during recording
  - Seamless integration with segment editor and grid cells
  - Language auto-detection from project settings
  - Manual stop functionality (press F9 again to stop recording)
  - Future: Planned parallel dictation system for voice commands (confirm segment, go to top, filtering, workflow automation)

- **🪟 Detachable Log Window**
  - Log window can be detached into separate floating window
  - Perfect for multi-monitor setups
  - Synchronized auto-scroll between main and detached logs
  - "Detach Log" / "Attach Log" button in Settings
  - Remembers detached state across sessions
  - Independent positioning and sizing

- **📚 Comprehensive Documentation**
  - [VOICE_DICTATION_GUIDE.md](docs/VOICE_DICTATION_GUIDE.md) - Complete user guide
  - [VOICE_DICTATION_DEPLOYMENT.md](docs/VOICE_DICTATION_DEPLOYMENT.md) - Deployment options
  - [SUPERVOICE_TROUBLESHOOTING.md](docs/SUPERVOICE_TROUBLESHOOTING.md) - Troubleshooting guide
  - FFmpeg licensing information
  - Model selection recommendations
  - Corrupt model file recovery instructions

### Fixed
- **🐛 Voice Dictation Bug Fixes**
  - Fixed critical UnboundLocalError in `voice_dictation_lite.py:118` (duplicate `import os` statement)
  - Fixed language detection from project settings
  - Fixed button color restoration after recording
  - Fixed auto-scroll synchronization between log windows

### Changed
- **🔧 Version Update**
  - Updated version from 1.3.4 to 1.4.0
  - Updated all version strings in code and documentation
  - Updated window titles and welcome messages
  - Updated website (docs/index.html) with Supervoice module card
  - Updated hero badge to "v1.4.0 - Supervoice Voice Dictation"

### Technical
- New module: `modules/voice_dictation_lite.py` - Core dictation engine
- Enhanced `Supervertaler_Qt.py` - Integrated voice dictation and detachable log
- Updated `docs/index.html` - Added Supervoice feature highlight and module card
- Created FFmpeg detection and bundling infrastructure
- Whisper model caching in `%USERPROFILE%\.cache\whisper\`

---

## [1.3.3] - November 10, 2025

### 🏆 Major Feature: LLM Leaderboard + UI Standardization

**Translation Quality Benchmarking System** - Compare translation quality, speed, and cost across multiple LLM providers in a professional, standardized interface.

### Added
- **🏆 LLM Leaderboard Module** (Complete Implementation)
  - Benchmark translation quality across OpenAI, Claude, and Gemini models
  - chrF++ quality scoring for objective translation assessment
  - Speed and cost tracking for each translation
  - Multiple test datasets: Technical, Legal, Medical, Marketing (EN→NL, NL→EN)
  - Comprehensive Excel export with:
    - About sheet with clickable Supervertaler.com link
    - Summary sheet with rankings and statistics
    - Detailed results with all metrics
    - Dataset info in filename (e.g., `LLM_Leaderboard_Technical_EN-NL_20251110.xlsx`)
  - Auto-scrolling log for real-time progress monitoring
  - Standalone usage support with api_keys.example.txt template
  - Professional documentation in `modules/LLM_LEADERBOARD_STANDALONE.md`

- **🎨 Standardized Module Headers**
  - Consistent professional styling across all modules
  - Blue header color (#1976D2) matching Supervertaler branding
  - Light blue description boxes (#E3F2FD) with rounded corners
  - Trophy emoji 🏆 for LLM Leaderboard identity
  - Applied to: LLM Leaderboard, TMX Editor, AutoFingers, PDF Rescue

- **📊 Model Selection Enhancements**
  - Friendly model names in dropdowns (e.g., "GPT-5 (Reasoning)", "Claude Opus 4.1")
  - Support for latest models:
    - OpenAI: GPT-4o, GPT-4o Mini, GPT-5
    - Claude: Sonnet 4.5, Haiku 4.5, Opus 4.1
    - Gemini: 2.5 Flash, 2.5 Flash Lite, 2.5 Pro, 2.0 Flash (Exp)

### Fixed
- **🐛 LLM Leaderboard Bug Fixes**
  - Fixed Claude API call parameters (text vs custom_prompt)
  - Fixed Gemini API key mapping ("gemini" provider → "google" API key)
  - Fixed model dropdown display names (was showing generic names instead of selected models)
  - Fixed API key auto-creation from template file

### Changed
- **🔧 Excel Export Branding**
  - Title sheet matches UI header style with trophy emoji
  - Blue title color (#1976D2) for brand consistency
  - Clickable hyperlink to https://supervertaler.com/
  - Professional subtitle formatting

- **🔧 API Key Management**
  - Auto-creates `api_keys.txt` from `api_keys.example.txt` on first run
  - Supports standalone LLM Leaderboard usage outside Supervertaler

### Technical
- Enhanced `modules/llm_leaderboard.py` - Core benchmarking engine
- Enhanced `modules/superbench_ui.py` - Qt UI with standardized header
- Updated `modules/llm_clients.py` - Auto-create API keys functionality
- Updated `Supervertaler_Qt.py` - Gemini API key mapping fix
- Created `api_keys.example.txt` - Template for standalone usage
- Created `modules/LLM_LEADERBOARD_STANDALONE.md` - Complete documentation

---

## [1.3.2] - November 9, 2025

### 🎯 Major Feature: Segment-Level AI Access + Critical Bug Fix

**AI Assistant can now access and query individual segments from your translation project**

### Added
- **🔢 Segment-Level AI Actions** (Phase 2 Enhancement)
  - `get_segment_count` - Get total segments and translation progress
  - `get_segment_info` - Query specific segments by ID, multiple IDs, or range
  - AI can answer "How many segments?" and "What is segment 5?"
  - First 10 segments automatically included in AI context
  - Full segment properties: id, source, target, status, type, notes, match_percent, etc.

- **📊 Segment Information Display**
  - AI Assistant shows segment details in formatted chat bubbles
  - HTML entity escaping for CAT tool tags (`<tag>`, `&nbsp;`, etc.)
  - Proper handling of Trados, memoQ, Wordfast, CafeTran tags
  - Segments displayed in code blocks for readability

- **⚙️ Auto-Markdown Generation Setting**
  - Optional setting in Settings → General → AI Assistant Settings
  - "Auto-generate markdown for imported documents" checkbox
  - Automatically converts DOCX/PDF to markdown on import
  - Markdown saved to `user_data_private/AI_Assistant/current_document/`
  - Includes metadata JSON with conversion info

### Fixed
- **🐛 CRITICAL: Current Document Not Showing After Import**
  - Fixed attribute name mismatch: `self.prompt_manager` → `self.prompt_manager_qt`
  - Current document now appears in AI Assistant sidebar after import
  - Auto-markdown generation now triggers correctly
  - Context refresh now works properly

### Changed
- **🔧 AI Assistant Context Building** (`modules/unified_prompt_manager_qt.py`)
  - Added `_get_segment_info()` method for structured segment data
  - Added `generate_markdown_for_current_document()` public method
  - Modified context building to prioritize segment-level access
  - Document content fallback when segments unavailable

- **🔧 AI Actions System** (`modules/ai_actions.py`)
  - Added `parent_app` parameter to constructor
  - Added segment action handlers with full validation
  - Enhanced `format_action_results()` with segment display logic
  - Comprehensive HTML entity escaping (order-aware to prevent double-escaping)

- **🔧 Main Application** (`Supervertaler_Qt.py`)
  - Added auto-markdown setting to Settings UI
  - Setting persists in `ui_preferences.json`
  - Document import triggers markdown generation when enabled
  - Context refresh called after document import

### Technical
- **Segment Access Order:**
  1. `project.segments` - Full segment objects (PREFERRED)
  2. `parent_app.segments` - Currently loaded segments
  3. `project.source_segments` - Project source text
  4. Cached markdown conversion
  5. On-demand file conversion with markitdown

- **HTML Escaping Order:** `&` → `<` → `>` → `"` (prevents double-escaping)
- **Segment Data Structure:** Full dataclass with 12 properties per segment

### Testing
- ✅ Updated test suite (`test_ai_actions.py`)
- ✅ Added Test 9: get_segment_count action
- ✅ Added Test 10: get_segment_info action (single, multiple, range)
- ✅ All 10 tests passing

### Documentation
- Updated `docs/AI_ASSISTANT_INTEGRATION.md` with segment access details
- Added segment action examples and use cases
- Updated troubleshooting section

### Benefits
- ✅ **Segment-specific queries** - AI can find and analyze specific segments
- ✅ **Translation progress tracking** - AI reports completion status
- ✅ **CAT tool tag handling** - All tag types properly escaped and displayed
- ✅ **Auto-markdown option** - Users control document conversion
- ✅ **Fixed critical bug** - Current document now shows correctly

---

## [1.3.1] - November 9, 2025

### ✨ Major Feature: AI Assistant File Attachment Persistence (Phase 1)

**Complete persistent storage system for AI Assistant file attachments with view/manage UI**

### Added
- **📎 AttachmentManager Module** (`modules/ai_attachment_manager.py` - 390 lines)
  - Complete persistent storage system for attached files
  - Session-based organization (files grouped by date)
  - Master index tracking all attachments across sessions
  - Metadata storage with JSON (original name, path, type, size, date)
  - Full CRUD operations: attach, get, list, remove files
  - Statistics tracking (total files, size, sessions)

- **👁️ File Viewer Dialogs** (`modules/ai_file_viewer_dialog.py` - 160 lines)
  - FileViewerDialog - displays file content with metadata
  - Read-only markdown preview with monospace font
  - Copy to clipboard functionality
  - FileRemoveConfirmDialog - confirmation before deletion

- **🎨 Expandable Attached Files Panel** (AI Assistant context sidebar)
  - Collapsible "📎 Attached Files" section with expand/collapse button (▼/▶)
  - Dynamic file list showing name, type, size for each file
  - View button (👁) - opens file viewer dialog
  - Remove button (❌) - deletes from disk with confirmation
  - + button to attach new files
  - Auto-refresh on file operations

### Changed
- **🔧 AI Assistant Integration** (`modules/unified_prompt_manager_qt.py`)
  - Initialized AttachmentManager in `__init__`
  - Modified `_attach_file()` to save files to persistent storage
  - Added `_load_persisted_attachments()` method - loads files on startup
  - Created `_create_attached_files_section()` - expandable panel UI
  - Added `_refresh_attached_files_list()` - dynamic file list updates
  - Added `_create_file_item_widget()` - individual file items with buttons
  - Added `_view_file()` - opens FileViewerDialog
  - Added `_remove_file()` - removes from disk and memory
  - Added `_toggle_attached_files()` - expand/collapse functionality
  - Updated `_update_context_sidebar()` to refresh file list
  - Updated `_load_conversation_history()` to refresh UI after load

### Technical
- **Storage Structure:**
  - Base: `user_data_private/AI_Assistant/`
  - Attachments: `attachments/{session_id}/{file_hash}.md`
  - Metadata: `attachments/{session_id}/{file_hash}.meta.json`
  - Master index: `index.json`
- **Session Management:** Date-based sessions (YYYYMMDD format)
- **File Hashing:** SHA256-based unique IDs (path_hash + content_hash)
- **Backward Compatibility:** Old `self.attached_files` list still maintained

### Testing
- ✅ Created comprehensive test suite (`test_attachment_manager.py`)
- ✅ All 8 tests passing (imports, init, session, attach, list, get, stats, remove)
- ✅ UTF-8 console output handling for Windows

### Benefits
- ✅ **Files no longer lost** when application closes
- ✅ **Users can view** attached files anytime via viewer dialog
- ✅ **Users can remove** unwanted files with confirmation
- ✅ **Session organization** keeps files organized by date
- ✅ **Persistent across app restarts** - automatic reload on startup

### Documentation
- Updated `docs/PROJECT_CONTEXT.md` with Phase 1 implementation details
- Created `docs/AI_ASSISTANT_ENHANCEMENT_PLAN.md` with full specification
- Updated website (`docs/index.html`) to reflect new features

### Next
- Phase 2: AI Actions System (allow AI to create/modify prompts in library)

---

## [1.2.2] - November 6, 2025

### 🎨 Major Enhancement: Translation Results, Document Formatting & Tag System

**Fixed translation results display, enhanced document view with formatting, and activated the tag system!**

### Fixed
- **🐛 Translation Results Panels Not Working** - CRITICAL FIX
  - Removed lingering `assistance_widget` references that blocked match processing
  - Fixed termbase, TM, MT, and LLM matches not displaying in panels
  - Updated all 6 locations where matches were being set to use `results_panels`
  - All three views (Grid, List, Document) now show matches correctly

- **🐛 Menu Bar Blocked by Error Indicator** 
  - Removed 15+ obsolete `assistance_widget` references causing Qt errors
  - Fixed red error triangle that blocked File and Edit menus
  - Updated zoom functions, font settings, and close project cleanup

### Added
- **✅ Document View Formatting**
  - Renders inline formatting tags: `<b>bold</b>`, `<i>italic</i>`, `<u>underline</u>`, `<bi>bold+italic</bi>`
  - New list item tag: `<li>content</li>` renders with orange bullet (•)
  - Proper QTextCharFormat application for bold, italic, underline
  - Tag parsing with formatting stack for nested tags

- **✅ Enhanced Type Column**
  - Shows **H1, H2, H3, H4** for heading levels (blue background)
  - Shows **Title** for document titles
  - Shows **Sub** for subtitles
  - Shows **li** for list items (green background)
  - Shows **¶** for regular paragraphs
  - Color-coded for easy document structure visualization

- **✅ List Item Tag System**
  - DOCX import detects bullets and numbered lists
  - Automatically wraps list items in `<li>` tags
  - Detection works on Word numbering format, bullet characters, and numbered prefixes
  - Tags preserved through translation and export workflow

### Technical
- Updated `tag_manager.py` to support `<li>` tag (TAG_PATTERN regex)
- Enhanced `docx_handler.py` to detect and tag list items during import
- Document view parses tags and renders with proper formatting
- Type column detects `<li>` tags, heading styles, and text patterns
- Tag colors: Bold=#CC0000, Italic=#0066CC, Underline=#009900, BoldItalic=#CC00CC, ListItem=#FF6600

---

## [1.2.1] - November 6, 2025

### 🎨 UI Enhancement: Unified Tabbed Interface

**Added consistent tabbed panel structure to both Grid and List views for improved workflow!**

### Added
- **✅ Tabbed Panel in Grid View**
  - Tab 1: Translation Results (TM, MT, LLM, Termbase matches)
  - Tab 2: Segment Editor (source/target editing, status selector)
  - Tab 3: Notes (segment notes with save functionality)
  - Enables segment editing directly in Grid View (like Tkinter edition)

- **✅ Tabbed Panel in List View**
  - Same 3-tab structure as Grid View for consistency
  - Translation Results | Segment Editor | Notes
  - Replaces single-panel layout with flexible tabbed interface

- **✅ Synchronized Panel Updates**
  - Clicking segment in any view updates ALL tabs in ALL views
  - Editing in any panel automatically syncs to other panels
  - Prevents infinite loops with signal blocking
  - Multiple independent widget instances for Grid/List views

### Fixed
- **🐛 Widget Parenting Issues** - Fixed Qt single-parent constraint violations
  - Created separate TranslationResultsPanel instances for each view
  - Stored widget references on panel objects for flexible access
  - Maintains `results_panels` and `tabbed_panels` lists for batch updates

- **🐛 Signal Handler Crashes** - Fixed AttributeError when editing segments
  - Updated `on_tab_target_change()`, `on_tab_segment_status_change()`, `on_tab_notes_change()`
  - Handlers now iterate all panels instead of accessing non-existent attributes
  - Proper error handling per panel to prevent cascade failures

### Technical
- Unified panel creation via `create_tabbed_assistance_panel()`
- Widget reference storage pattern: `panel.editor_widget.source_editor`
- Centralized update function: `update_tab_segment_editor()` iterates all panels
- Signal blocking prevents infinite update loops during synchronization

---

## [1.2.0] - November 6, 2025 🎉

### 🎯 MAJOR RELEASE: Complete Translation Matching System

**The Supervertaler CAT tool now provides comprehensive translation assistance with all match types working together!**

### Added
- **✅ Google Cloud Translation API Integration**
  - Machine translation matches displayed alongside TM and LLM results
  - Uses Google Translate REST API v2 for direct API key authentication
  - Automatic language detection support
  - High-quality neural machine translation
  - Provider badge: "MT" in match display

- **✅ Multi-LLM Support (OpenAI, Claude, Gemini)**
  - **OpenAI GPT** integration (GPT-4o, GPT-5, o1, o3)
  - **Claude 3.5 Sonnet** integration (Anthropic)
  - **Google Gemini** integration (Gemini 2.0 Flash, 1.5 Pro)
  - All three LLM providers work simultaneously
  - Each provides translations with confidence scores
  - Provider badges: "OA" (OpenAI), "CL" (Claude), "GM" (Gemini)

- **✅ Complete Match Chaining System**
  - **Termbase matches** → Displayed immediately (yellow highlight)
  - **TM matches** → Displayed after 1.5s delay (prevents excessive API calls)
  - **MT matches** → Google Translate integrated in delayed search
  - **LLM matches** → All enabled LLMs called in parallel
  - All match types preserved and displayed together in Translation Results Panel

- **✅ Flexible API Key Management**
  - Supports both `google` and `google_translate` key names for Google Cloud Translation
  - Supports both `gemini` and `google` key names for Gemini API
  - Backward compatibility with existing configurations
  - Standalone `load_api_keys()` function in `modules/llm_clients.py`

### Fixed
- **🐛 Termbase Match Preservation** - Termbase matches no longer disappear when TM/MT/LLM results load
  - Root cause: Delayed search wasn't receiving termbase matches parameter
  - Solution: Pass `current_termbase_matches` to `_add_mt_and_llm_matches()`
  - Termbase matches now persist throughout the entire search process

- **🐛 Google Translate Authentication** - Fixed "Client.__init__() got an unexpected keyword argument 'api_key'"
  - Switched from google-cloud-translate SDK to direct REST API calls
  - Simpler authentication using API key in URL parameters
  - More reliable and easier to configure

- **🐛 Gemini Integration** - Gemini now properly called when using `google` API key
  - Added fallback to check both `gemini` and `google` key names
  - Fixed LLM wrapper to support Google's API key for Gemini

### Technical Implementation
- **File: `modules/llm_clients.py`**
  - Added standalone `load_api_keys()` function (lines 27-76)
  - Fixed `get_google_translation()` to use REST API instead of SDK
  - Backward compatible API key naming (checks multiple key names)
  - Module can now operate independently without main application

- **File: `Supervertaler_Qt.py`**
  - Enhanced `_add_mt_and_llm_matches()` with comprehensive logging
  - Fixed Gemini integration to check both key naming conventions
  - Improved match chaining with proper termbase preservation
  - Debounced search (1.5s delay) prevents excessive API calls

### Performance Optimizations
- **Debounced Search** - 1.5-second delay before calling TM/MT/LLM APIs
- **Timer Cancellation** - Previous searches cancelled when user moves to new segment
- **Immediate Termbase Display** - Termbase matches shown instantly (no delay)
- **Parallel LLM Calls** - All LLM providers called simultaneously for faster results

### Dependencies
- `requests` - For Google Translate REST API calls (standard library)
- `openai` - OpenAI GPT integration
- `anthropic` - Claude integration
- `google-generativeai` - Gemini integration
- `httpx==0.28.1` - HTTP client (version locked for LLM compatibility)

### Documentation
- Updated `docs/PROJECT_CONTEXT.md` with November 6, 2025 development activity
- Documented all LLM & MT integration details
- Listed resolved issues and technical decisions

### Match Display
All match types now display in the Translation Results Panel:
- **Termbases** (Yellow section) - Term matches from termbase databases
- **Translation Memory** (Blue section) - Fuzzy matches from TM database
- **Machine Translation** (Orange section) - Google Cloud Translation
- **LLM** (Purple section) - OpenAI GPT, Claude, and/or Gemini translations

Each match shows:
- Provider badge (NT/TM/MT/OA/CL/GM)
- Relevance percentage (0-100%)
- Target translation text
- Source context (when available)

---

## [1.1.9] - November 6, 2025

### Added
- **⌨️ Keyboard Shortcuts Manager** - Comprehensive keyboard shortcuts management system
  - New Settings tab: "⌨️ Keyboard Shortcuts"
  - View all 40+ keyboard shortcuts organized by category (File, Edit, Translation, View, Resources, Match Insertion, etc.)
  - Search/filter shortcuts by action, category, or key combination
  - Edit shortcuts with custom key capture widget
  - Conflict detection with warnings
  - Reset individual shortcuts or all shortcuts to defaults
  - Export shortcuts to JSON (share with team)
  - Import shortcuts from JSON
  - **Export HTML Cheatsheet** - Beautiful, printable keyboard reference with professional styling
  - Modular architecture: `modules/shortcut_manager.py` and `modules/keyboard_shortcuts_widget.py`

### Technical Details
- **ShortcutManager** class - Backend logic for managing shortcuts
- **KeyboardShortcutsWidget** - Full-featured UI for Settings tab
- **KeySequenceEdit** - Custom widget for capturing key presses
- **Conflict detection** - Real-time warnings for duplicate shortcuts
- **Context-aware shortcuts** - Different contexts (editor, grid, match panel) to prevent conflicts
- Data stored in `user_data/shortcuts.json`

### Documentation
- Added `Keyboard_Shortcuts_Implementation.md` in development docs
- Added `Competitive_Analysis_CotranslatorAI.md` in development docs

### Improved
- **Repository Philosophy** - Continued modular architecture to keep main file maintainable
- **AI-Friendly Codebase** - Complex features extracted to focused modules (easier for AI agents to understand)

---

## [1.1.8] - November 5, 2025

### Fixed
- **🎯 Prompt Generation (CRITICAL FIX):** Fixed incomplete prompt generation in Prompt Assistant
  - **Root Cause:** Using `client.translate()` for text generation instead of proper chat completion API
  - **Solution:** Switched to direct LLM API calls (OpenAI/Claude/Gemini) with proper message structure
  - Domain Prompts now generate complete 3-5 paragraph prompts (was 2 sentences)
  - Project Prompts now include full termbase tables + intro/closing paragraphs (was partial/truncated)
  - Added truncation detection and warnings for all providers
  - Temperature set to 0.4 for creative generation (was 0.3)
  - Max tokens set to 8000 (with full flexibility, not constrained by translation wrapper)
- **Documentation:** Added complete debugging session documentation (docs/2025-11-05.md)

### Technical Details
- Removed hybrid approach (programmatic termbase extraction + AI generation)
- Reverted to pure AI-only approach matching working tkinter version
- Direct API calls now match tkinter implementation exactly:
  - OpenAI: `chat.completions.create()` with system/user messages
  - Claude: `messages.create()` with proper system parameter
  - Gemini: `generate_content()` with combined prompt
- All providers now check `finish_reason`/`stop_reason` for truncation

### Impact
- **Generate Prompts** feature now works perfectly, producing complete professional prompts
- Critical feature that was broken is now fully functional
- Matches quality and completeness of tkinter version

---

## [1.1.7] - November 4, 2025

### Major Changes
- **🏠 Home Screen Redesign:** Complete restructuring of the primary workspace
  - Editor (Grid/List/Document views) on the left with Prompt Manager on the right
  - Resizable horizontal splitter between editor and prompt manager
  - Translation results panel moved to bottom of grid in compact form
  - Real-time prompt tweaking while viewing changes in the grid
  - Removed separate Editor and Prompt Manager tabs (integrated into Home)

### Strategic Refocus
- **🎯 Companion Tool Philosophy:** Pivoted from full CAT tool to companion tool
  - Grid simplified for viewing/reviewing (minor edits only)
  - Focus on AI-powered features and specialized modules
  - Documentation updated to reflect companion tool approach

### Added
- **Custom Styled Widgets:** Beautiful checkboxes and radio buttons with white checkmarks
  - `CheckmarkCheckBox` class for all checkboxes
  - `CustomRadioButton` class for LLM Provider selection
  - Square indicators with green background when checked, white checkmark overlay
- **Prompt Manager Enhancements:**
  - Preview Combined Prompt button shows exact prompt sent to AI
  - Deactivate buttons for Domain and Project prompts
  - Prompt Assistant tab moved to first position

### Improved
- **Grid Simplification:**
  - Double-click only editing (removed F2 key) - companion tool philosophy
  - Simplified styling with subtle colors for review-focused interface
  - Light blue selection highlight instead of bright blue
- **Segment Number Styling:**
  - All segment numbers start with black foreground
  - Only selected segment number highlighted in orange (like memoQ)
  - Fixed black numbers issue after navigation

### Fixed
- **Filter Crash:** Added safety checks for table and filter widgets
- **removeWidget Error:** Fixed QSplitter widget removal (use setParent instead)
- **Project Loading:** Fixed doc_segment_widgets AttributeError
- **Translation Results Panel:** Now properly visible at bottom of grid

### Technical
- Improved widget reparenting logic for splitter management
- Enhanced error handling in filter operations
- Better initialization of view state variables

---

## [1.1.6] - November 3, 2025

### Added
- **🔍 Detachable Superlookup:** Multi-screen support for Superlookup module
  - Detach button on Home tab to open Superlookup in separate window
  - Perfect for multi-monitor workflows - move lookup to second screen while translating
  - Proper window positioning and multi-monitor detection
  - Reattach functionality to return to embedded mode

### Improved
- **🏠 Home Tab Enhancements:**
  - Integrated About section directly into header with improved visibility
  - Better text styling with purple gradient for subtitle and version (larger, bold)
  - Reorganized layout: About in header, Resources & Support next, Projects at bottom
  - Projects section with distinct background color for visual separation
  - Superlookup prominently featured on right side of Home tab

### Fixed
- **Multi-Monitor Support:** Fixed window positioning for detached Superlookup
  - Correct screen detection using `QApplication.screenAt()` API
  - Proper window activation and focus handling
  - Window flags configured for proper minimize/maximize behavior
  - Improved error handling for window detachment process

### Technical
- Updated window positioning logic for Qt6 compatibility
- Enhanced screen detection for multi-monitor setups
- Improved window activation using QTimer for reliable focus management

---

## [1.1.5] - November 2, 2025

### Added
- **🏠 New Home Tab:** Brand new first-screen experience
  - Integrated About section with version info and purple gradient header
  - Quick access to resources (Website, GitHub, Discussions, Documentation)
  - Project management panel for recent projects
  - Embedded Superlookup for instant translations
  - Clean, modern design with proper visual hierarchy
  
- **Major UI Reorganization:** Complete restructuring of main interface
  - **Tab Order Redesigned:** 
    1. 🏠 Home (NEW - welcome screen)
    2. 💡 Prompt Manager (moved up from #5)
    3. 📝 Editor (renamed from "Project Editor")
    4. 🗂️ Resources (organized nested tabs)
    5. 🧩 Modules (renamed from "Specialised Modules")
    6. ⚙️ Settings (moved from Tools menu, includes Log)
  - **Navigation Menu:** Added "Go to Home" action (🏠 Home menu item)
  - **Removed Quick Access Sidebar:** Functionality integrated into Home tab
  - Cleaner, more intuitive workflow with logical feature grouping

- **Multiple View Modes:** Three different ways to view and edit your translation project
  - **Grid View (Ctrl+1):** Spreadsheet-like table view - perfect for quick segment-by-segment editing
  - **List View (Ctrl+2):** Segment list on left, editor panel on right - ideal for focused translation work
  - **Document View (Ctrl+3):** Natural document flow with clickable segments - great for review and context
  - View switcher toolbar with quick access buttons
  - All views share the same translation results pane (TM, LLM, MT, Termbase matches)
  - All views stay synchronized - changes in one view instantly reflected in others
  - Keyboard shortcuts (Ctrl+1/2/3) for rapid view switching

### Improved
- **Translation Results Pane:** Now visible and functional in all three view modes
  - Properly integrated into Grid, List, and Document views
  - Dynamic reparenting when switching between views
  - Consistent assistance panel across all view modes

### Technical
- **View Management:** Implemented QStackedWidget architecture for seamless view switching
  - Each view maintains its own splitter layout
  - Shared assistance widget dynamically moved between views
  - Clean separation of view-specific logic

---

## [1.1.4] - November 2, 2025

### Added
- **Encoding Repair Tool:** Full port from tkinter edition with standalone capability
  - Detect and fix text encoding corruption (mojibake) in translation files
  - Scan single files or entire folders recursively
  - Automatic backup creation (.backup files) before repair
  - Supports common corruption patterns (en/em dashes, quotes, ellipsis, bullets, etc.)
  - Clean Qt interface matching other modules (PDF Rescue, TMX Editor style)
  - **Standalone Mode:** Run independently with `python modules/encoding_repair_Qt.py`
  - **Embedded Mode:** Integrated as a tab in Supervertaler Qt
  - Test file available at `docs/tests/test_encoding_corruption.txt` for user testing

### Improved
- **Prompt Manager:** Fixed System Prompts tab to show list widget (matching Domain Prompts layout)
  - Added proper list/editor splitter layout for consistency
  - System Prompts now use shared editor panel with metadata fields hidden
  - Better visual consistency across all prompt tabs

### Fixed
- **About Dialog:** Updated with clickable website link (https://supervertaler.com/)
  - Changed description from "Professional Translation Memory & CAT Tool" to "AI-powered tool for translators & writers"
  - Improved dialog layout with better formatting

### Technical
- **Module Architecture:** Created `encoding_repair_Qt.py` as standalone, reusable module
  - Uses existing `encoding_repair.py` backend (shared with tkinter version)
  - Proper path handling for standalone execution
  - Consistent with other Qt modules (PDF Rescue, TMX Editor patterns)

---

## [1.1.3] - November 2, 2025

### Added
- **Prompt Manager:** Complete 4-Layer Prompt Architecture system integrated into Qt Edition
  - **Layer 1 - System Prompts:** Editable infrastructure prompts (CAT tags, formatting rules, language conventions)
  - **Layer 2 - Domain Prompts:** Domain-specific translation expertise (Legal, Medical, Technical, Financial, etc.)
  - **Layer 3 - Project Prompts:** Client and project-specific instructions and rules
  - **Layer 4 - Style Guides:** Language-specific formatting guidelines (numbers, dates, typography)
  - **Prompt Assistant:** AI-powered prompt refinement using natural language (unique to Supervertaler!)
  - **Full UI Integration:** Beautiful tab interface with activation system and preview
  - **Standardized Headers:** Consistent UI/UX matching other modules (TMX Editor, PDF Rescue, AutoFingers)
  - **Import/Export:** Save, reset, import, and export prompts for sharing and backup

### Website
- **4-Layer Architecture Documentation:** Comprehensive new section on website explaining the unique approach
- **Visual Design:** Color-coded layer cards with detailed explanations
- **Navigation:** Added dedicated navigation link for Architecture section
- **Hero Section:** Updated badges and feature highlights to showcase new architecture
- **Footer Links:** Integrated architecture documentation into site navigation

### Technical
- **Terminology Standardization:** Renamed all infrastructure/Custom Instructions references to System/Project Prompts
- **Code Quality:** Systematic refactoring with consistent naming conventions throughout
- **Module Architecture:** `prompt_manager_qt.py` created as standalone, reusable module
- **Backward Compatibility:** Maintained compatibility with existing prompt library files

---

## [1.1.2] - November 1, 2025

### Improved
- **PDF Rescue:** Simplified to OCR-only mode (removed dual-mode complexity)
  - Removed text extraction mode and 504 lines of complex layout detection code
  - Reverted to simple, reliable image-based OCR workflow
  - Updated UI description to clarify OCR-only purpose
  - Better results with simpler approach

### Fixed
- **PDF Rescue Prompt:** Restored original concise prompt that produced better OCR results
  - Removed verbose "CRITICAL ACCURACY RULES" that degraded performance
  - Simplified instructions for clearer AI guidance
  - Improved OCR accuracy with focused prompts

- **PDF Rescue DOCX Export:** Fixed excessive line breaks in Word documents
  - Changed paragraph detection from single newlines to double newlines
  - Single newlines now treated as spaces within paragraphs
  - Reduced paragraph spacing from 12pt to 6pt for tighter layout
  - Applied fix to both formatted and non-formatted export modes

### Added
- **PDF Rescue Branding:** Added clickable hyperlink in DOCX exports
  - "Supervertaler" text now links to https://supervertaler.com/
  - Professional branding with working hyperlinks in Word documents

- **Website Navigation:** Added "Modules" link to header navigation
  - Appears after "Features" in main menu
  - Provides direct access to modules documentation

### Removed
- **Website:** Removed "AI-First Philosophy" section (93 lines)
  - Streamlined website content
  - Removed from navigation menu
  - Content deemed redundant with other sections

---

## [1.1.1] - November 1, 2025

### Improved
- **AutoFingers Settings:** Simplified behavior settings by removing redundant "Use Alt+N" checkbox
  - Now uses single "Confirm segments" checkbox: checked = Ctrl+Enter (confirm), unchecked = Alt+N (skip confirmation)
  - More intuitive UI with clearer label and comprehensive tooltip
  - Maintains backward compatibility with existing settings files

---

## [1.1.0] - November 1, 2025

### Added
- **TMX Editor:** Professional translation memory editor integrated into Qt Edition
  - **Database-Backed TMX System:** Handle massive TMX files (1GB+) with SQLite backend
  - **Dual Loading Modes:** Choose RAM mode (fast for small files) or Database mode (handles any size)
  - **Smart Mode Selection:** Auto mode intelligently selects best loading method based on file size
  - **Inline Editing:** Edit source and target text directly in the grid (no popup dialogs)
  - **Real-time Highlighting:** Search terms highlighted with green background (Heartsome-style)
  - **Heartsome-Inspired UI:** Three-panel layout with top header (language selectors + filters), center grid, and right attributes panel
  - **Filtering:** Advanced search with case-insensitive matching and tag filtering
  - **Pagination:** Efficient 50 TUs per page for smooth performance
  - **Export/Import:** Save edited TMX files and export to new files
  - **Progress Indicators:** Clear progress bars with batch operations for fast loading
  - **Custom Checkboxes:** Consistent green checkmark style matching AutoFingers design

### Improved
- **Database Integration:** New TMX database tables (`tmx_files`, `tmx_translation_units`, `tmx_segments`) with foreign keys and indexes
- **Batch Operations:** Database commits every 100 TUs for 10-50x faster loading performance
- **UI Consistency:** Mode selection dialog uses custom CheckmarkCheckBox style throughout
- **Progress Feedback:** Immediate progress bar display with clearer blue styling

### Technical
- **Database Schema:** Added three new tables for TMX storage with proper indexing
- **Mode Detection:** Automatic recommendation based on file size thresholds (50MB, 100MB)
- **Transaction Management:** Optimized database operations with batch commits
- **Memory Efficiency:** Database mode frees RAM immediately after loading

---

## [1.0.2] - October 31, 2025

### Fixed
- **Broken Emoji Icons:** Fixed broken emoji characters in tab labels for Termbases (🏷️), Prompt Manager (💡), Encoding Repair (🔧), and Tracked Changes (🔄)
- **Checkbox Rendering:** Improved checkmark visibility on small displays with better padding and scaling

### Added
- **Startup Settings:** Added option to automatically restore last opened project on startup (Tools → Options → General → Startup Settings)
- **Font Size Persistence:** Added font size settings panel (Tools → Options → View/Display Settings) to save and restore:
  - Grid font size (7-72 pt)
  - Match list font size (7-16 pt)
  - Compare boxes font size (7-14 pt)
- **Auto-Save Font Sizes:** Font sizes are automatically saved when adjusted via zoom controls (Ctrl++/Ctrl+- for grid, Ctrl+Shift++/Ctrl+Shift+- for results pane)

### Improved
- **Checkbox Styling:** Implemented custom green checkboxes with white checkmarks (Option 1 style) for AutoFingers Behavior section - more intuitive than previous blue/white design
- **AutoFingers Layout:** Reorganized Settings section into 2-column grid layout (Languages/Timing on left, Behavior/Save on right) for better organization
- **Small Screen Support:** Moved Activity Log to right side of Settings for improved space utilization on laptop displays

---

## [1.0.1] - October 29, 2025

### Fixed
- **Terminology Standardization:** Replaced all "glossary" references with "termbase" throughout codebase
- **Database Schema:** Fixed NOT NULL constraint errors on `termbase_terms.source_lang` and `termbase_terms.target_lang` (changed to `DEFAULT 'unknown'`)
- **Method Naming:** Renamed `create_glossary_results_tab()` → `create_termbase_results_tab()`
- **Project Object Access:** Fixed Project attribute access patterns (changed from dict `.get()` to object attribute `.id`)
- **Tab Label:** Updated from "Term Bases" → "Termbases" (single word)

### Changed
- **Database Tables:** Renamed `glossary_terms` → `termbase_terms`, `glossary_id` → `termbase_id`
- **SQL Queries:** Updated all queries to use new table/column names

### Added
- **Sample Data:** Created 3 test termbases (Medical, Legal, Technical) with 48 total terms for testing

---

## [1.0.0] - October 28, 2025

### Added
- **Qt Edition Launch:** Initial release of PyQt6-based modern CAT interface
- **Translation Memory:** Full-text search with fuzzy matching and relevance scoring
- **Termbases:** Multiple termbase support with global and project-specific scopes
- **CAT Editor:** Segment-based translation editing interface
- **Project Management:** Create, manage, and switch between translation projects
- **Auto-fingers:** Smart terminology suggestions based on context
- **AI Integration:** OpenAI GPT and Claude support with configurable API keys
- **Database Backend:** SQLite persistent storage with 7 core tables

---

## Versioning Strategy

- **Major.Minor.Patch** (e.g., 1.0.1)
  - **Major:** Significant architecture changes or breaking changes
  - **Minor:** New features or substantial improvements
  - **Patch:** Bug fixes and minor adjustments

---

## Future Roadmap

### Planned for v1.1.0
- Terminology Search (Ctrl+P)
- Concordance Search (Ctrl+K)
- Create/Edit termbase dialogs

### Planned for v1.2.0
- TMX Editor with visual highlighting
- Advanced filtering options
- Custom keyboard shortcuts

### Planned for v2.0.0
- Full feature parity with Tkinter edition
- Deprecation of Tkinter edition

---

**Note:** This changelog focuses exclusively on the Qt Edition. See [CHANGELOG_Tkinter.md](CHANGELOG_Tkinter.md) for Classic edition history.

**Last Updated:** October 30, 2025
- ✅ Fixed Project object access pattern (changed from dict `.get()` to object attributes)
- ✅ Fixed database schema issues in private database folder

### 📋 Terminology Standardization
- Replaced all "glossary" references with "termbase" throughout codebase
- Updated database table: `glossary_terms` → `termbase_terms`
- Updated column: `glossary_id` → `termbase_id`
- Unified UI labels to use "Termbases" (one word, consistent)
- **Files Updated**: 5+ Python files, database schema, UI labels

### 🎯 Known Issues
- Terminology Search (Ctrl+P) - Planned for next release
- Concordance Search (Ctrl+K) - Planned for next release

---

## [v1.0.0] - 2025-10-29 🎯 Phase 5.3 - Advanced Ribbon Features Complete

### 🎨 Major UX Enhancements - ALL 5 FEATURES IMPLEMENTED

**1. ✅ Context-Sensitive Ribbon**
- Ribbon automatically switches based on active tab
- Superlookup tab → Shows Translation ribbon
- Project Editor tab → Shows Home ribbon
- Intelligent tab selection for better workflow

**2. ✅ Quick Access Toolbar (QAT)**
- Mini toolbar above ribbon with most-used commands
- **Actions**: New 📄, Open 📂, Save 💾, Superlookup 🔍, Translate 🤖
- **Minimize Ribbon toggle** ⌃ - Collapse ribbon to tabs-only
- Always visible for quick access to favorites
- Icon-only buttons for compact display

**3. ✅ Quick Access Sidebar** (NEW)
- memoQ-style left navigation panel
- **Collapsible sections**:
  - **Quick Actions**: New, Open, Save
  - **Translation Tools**: Superlookup, AutoFingers, TM Manager
  - **Recent Files**: Double-click to open
- Resizable via splitter
- Toggle on/off via View menu

**4. ✅ Ribbon Minimization**
- Minimize ribbon to tabs-only mode (saves vertical space)
- Click tabs to show ribbon temporarily
- Toggle via ⌃ button in QAT

**5. ✅ Ribbon Customization Foundation**
- Signal-based architecture for easy customization
- Action mapping system for flexibility
- Extensible group/button structure

### 📦 New Modules
- `modules/quick_access_sidebar.py` - Reusable sidebar components
- `modules/project_home_panel.py` - Project-specific home panel

### 🔧 Technical Improvements
- Renamed splitters for clarity (sidebar_splitter, editor_splitter)
- Connected sidebar actions to ribbon action handler
- Automatic recent files update
- Context-sensitive ribbon switching
- Professional multi-panel layout

---

## [v1.0.0 - Phase 5.2] - 2025-10-29 🎨 Ribbon Interface - Modern CAT UI

### ✨ Major Features
- ✅ **Modern Ribbon Interface** - Similar to memoQ, Trados Studio, Microsoft Office
- ✅ **Four Ribbon Tabs**:
  - **Home**: New, Open, Save, Copy, Paste, Find, Replace, Go To
  - **Translation**: Translate, Batch Translate, TM Manager, Superlookup
  - **View**: Zoom In/Out, Auto-Resize Rows, Themes
  - **Tools**: AutoFingers, Options
- ✅ **Grouped Buttons** - Related functions organized into visual groups
- ✅ **Emoji Icons** - Clear, colorful visual indicators
- ✅ **Hover Effects** - Modern button styling with transparency and borders
- ✅ **Full Integration** - All actions connected to existing functionality

### 🎯 Architecture
- Created `modules/ribbon_widget.py` - Reusable ribbon components
- Tab-based ribbon system with dynamic button groups
- Action signals connected to main window handlers
- Professional styling matching modern CAT tools

---

## [v1.0.0 - Phase 5.1] - 2025-10-28 📊 Translation Results Panel Complete

### ✨ Features Implemented
- ✅ **Compact Stacked Layout** - Collapsible match sections (NT, MT, TM, Termbases)
- ✅ **Relevance Display** - Shows match percentages and confidence levels
- ✅ **Metadata Display** - Domain, context, date information
- ✅ **Drag/Drop Support** - Insert matches into translation field
- ✅ **Compare Boxes** - Side-by-side comparison (Source | TM Source | TM Target)
- ✅ **Diff Highlighting** - Red/green styling for visual comparison
- ✅ **Segment Info** - Metadata and notes display
- ✅ **Integration** - Fully integrated into Project Editor tab

### 📦 New Module
- `modules/translation_results_panel.py` - Compact, production-ready results display

### 🎯 Layout
- Stacked match sections with collapsible headers
- Compact match items for efficient use of space
- Relevance percentage display
- Metadata columns (domain, context, source)
- Notes and segment information panel

---

## [v1.0.0 - Phase 5.0] - 2025-10-27 🚀 Qt Edition Launch

### ✨ Core Features
- ✅ **PyQt6 Framework** - Modern, cross-platform UI
- ✅ **Dual-Tab Interface**:
  - Project Editor - Main translation workspace
  - Superlookup - Dictionary/search tool
- ✅ **Project Management** - Load/save translation projects
- ✅ **Translation Memory** - Full TMX support
- ✅ **Segment Grid** - Professional translation grid view
- ✅ **AI Integration** - Multiple LLM provider support (OpenAI, Anthropic, etc.)
- ✅ **Keyboard Shortcuts** - Comprehensive hotkey system
- ✅ **AutoHotkey Integration** - System-wide lookup support

### 🎯 Application Structure
- Professional CAT tool architecture
- Modular design for extensibility
- Clean separation of concerns
- Database-backed translation memory
- Responsive UI with drag/drop support

---

## Release History - Previous Phases

For Qt development history before Phase 5.0, see `docs/RELEASE_Qt_v1.0.0_Phase5.md`

---

## Version Numbering

Supervertaler Qt uses semantic versioning:
- **MAJOR** - Major feature additions or breaking changes
- **MINOR** - New features, backward compatible
- **PATCH** - Bug fixes and improvements
- **PHASE** - Development phase tracking (Phase 5+)

**Current**: v1.0.2 (Phase 5.4)
