feat(transcribe): keyboard shortcuts for the transcribe power path + cheatsheet overlay #327
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Transcription is repetitive specialist work done by a small pool of Kurrent-literate family members (60+, working on laptops/tablets). Their productivity is limited by hand-travel to the mouse. No keyboard shortcuts exist today — every region switch, mode toggle, and save requires a click.
Given the primary transcriber persona, this is a high-leverage ergonomics play. 15 minutes of shortcut muscle memory saves hours over a backlog.
Non-goals
Proposed shortcuts
jkeCmd/Ctrl+EnterCmd/Ctrl+sEscnt?Rules:
Cmd/Ctrl+Enterworks inside the transcription textarea (must save and advance).?opens the cheatsheet regardless of focus (but not while a modifier is held, to avoid conflict with QWERTZ).Escor clicking the backdrop.Cmdlabels; Windows/Linux seeCtrl. Detect vianavigator.platform.Implementation plan
Frontend
frontend/src/lib/actions/transcribeShortcuts.ts:keydownlistener onwindow.frontend/src/lib/components/TranscribePanel.svelte:goToNextRegion,toggleMode,saveAndNext, etc.).frontend/src/lib/components/ShortcutCheatsheet.svelte:?, rendered as<dialog>witharia-modal="true".prefers-reduced-motionfor any open/close animation.?in the coaching card from #320 ("Tipp: Drücken Sie?für Tastatur-Kürzel").i18n
12–14 new Paraglide keys:
shortcut_next_region,shortcut_prev_region,shortcut_toggle_mode,shortcut_save_and_next,shortcut_save,shortcut_discard,shortcut_new_region,shortcut_toggle_training,shortcut_help,cheatsheet_title,cheatsheet_close,cheatsheet_platform_hint.Tests
Cmd+Enter→ saveAndNext fires. Focus in textarea + plainj→ no navigation.?, closes onEsc, displays all shortcut rows.j, assert next region highlighted; pressk, assert previous;e, assert mode toggled;?, assert cheatsheet visible;Esc, assert cheatsheet closed.Cmd; on Windows,Ctrl.Verification
Manual: transcribe 5 regions of a document using the keyboard only, no mouse. Confirm all shortcuts work; no accidental activations while typing into the textarea; cheatsheet reachable via
?.Acceptance criteria
Cmd/Ctrl+Enterworks inside the transcription textarea to save-and-next?, closes withEscand backdrop click?)Critical files
Related
ttoggles the per-region training flag introduced there.👨💻 Markus Keller — Application Architect
Observations
transcribeShortcuts.ts) that attaches akeydownlistener onwindow. This is architecturally sound for a panel-scoped concern but the "reads a store flag" guard mentioned in the spec does not match the existing codebase —transcribeModeis a plain$statevariable local to+page.svelte, not a Svelte store. The action would need to receive the panel-open state as a callback parameter rather than reading a shared store.frontend/src/lib/actions/transcribeShortcuts.tsis the correct location, consistent withclickOutside.tsandradioGroupNav.tswhich live infrontend/src/lib/shared/actions/. The spec usessrc/lib/actions/— this path does not exist; the correct directory issrc/lib/shared/actions/.nshortcut ("start drawing a new region") implies triggering a draw-mode toggle on the annotation layer. Looking at the code,onTranscriptionDrawflows from+page.svelte→DocumentViewer→ the PDF annotation layer. The shortcut action cannot directly invoke draw mode — it must dispatch an event or call a callback that the page wires toonTranscriptionDraw. The issue's implementation plan glosses over this; the callback API needs to include astartDrawMode()entry point.tshortcut ("toggle mark for training") operates ontrainingLabelsinTranscriptionEditView, which is owned by+page.svelteviaonToggleTrainingLabel. Same pattern: needs a callback in the action's API, not a direct store access.ShortcutCheatsheetcomponent is correctly proposed as a standalone component with<dialog>andaria-modal. This fits the existing component architecture cleanly.Recommendations
frontend/src/lib/actions/transcribeShortcuts.ts→frontend/src/lib/shared/actions/transcribeShortcuts.ts. Update the Critical Files list accordingly.navigator.platformis deprecated (MDN: deprecated since 2021). Usenavigator.userAgentData?.platformwith fallback tonavigator.platformfor Safari compatibility, or use the simplernavigator.userAgent.includes('Mac')which is stable and already used by many production apps.src/lib/shared/actions/index if one exists, or document the action at the top of the file, consistent with the existing pattern of standalone exports in that directory.CLAUDE.mdfrontend routes table does not need updating — this is a panel overlay, not a route).Open Decisions
frontend/src/lib/components/ShortcutCheatsheet.svelte, but that path does not exist — the project uses domain-scoped lib directories. Should this live infrontend/src/lib/document/transcription/ShortcutCheatsheet.svelte(domain-scoped) orfrontend/src/lib/shared/primitives/ShortcutCheatsheet.svelte(reusable across future panels)? Domain-scoped is cleaner for v1 since there is exactly one consumer. Thesrc/lib/components/path in the issue is simply wrong — the team should pick one of the two correct options before implementation starts.👨💻 Felix Brandt — Fullstack Developer
Observations
clickOutside.ts,radioGroupNav.ts) give a clear pattern: export a function that takes a node and options, return{ destroy }. The newtranscribeShortcuts.tsaction should follow this exact shape.Cmd/Ctrl+Enter." In the TranscribePanel, the text input is a TipTap-backedPersonMentionEditor. TipTap renders acontenteditablediv, not a native<input>or<textarea>. The guard must checkevent.target instanceof HTMLElement && event.target.isContentEditable— checkingtagName === 'INPUT'ortagName === 'TEXTAREA'alone will miss TipTap. This is a known footgun documented in the project memory.Cmd/Ctrl+Enterinside the textarea must firesaveAndNext. TipTap interceptsEnterby default butCmd/Ctrl+Enteris not bound by TipTap's default keymap — the globalwindowlistener will receive it. However, TipTap will also receive it if aKeyboardShortcutextension is registered. Confirm no TipTap extension already captures this combination before implementation.eshortcut ("toggle between Read and Edit mode") maps toonModeChangeinTranscriptionPanelHeader. The action callbacktoggleModeshould flip the current mode, requiring the action to receive the current mode state or a stateful toggle function from the page.jandkfor region navigation: the sorted blocks list is owned byTranscriptionEditView(viasortedBlocks = $derived([...blocks].sort(...))). Navigation must find the current active block and advance the index. The action callback should callgoToNextRegion()/goToPrevRegion()on the page, which setsactiveAnnotationId— the same mechanism already used when clicking an annotation on the PDF.Escconflict:Escis used in the spec both for "discard edits (with confirm)" and "close cheatsheet". The spec correctly notes that cheatsheetEsctakes precedence. The cheatsheet component should handle its ownEscvia the<dialog>'s nativecloseevent or a local listener — not the global shortcut action.ShortcutCheatsheetshould be a standalone, focused component under 60 lines. The cheatsheet table is purely presentational — no state beyond "is open", which comes from a prop.Recommendations
Cmd/Ctrl+Enter(which fires regardless) and?(which fires regardless of focus, per the spec).radioGroupNav.tsexactly: the action returns{ destroy, update }whereupdateallows the parent to refresh callbacks when reactive state changes. This prevents stale closures whenpanelModeorblockschange.transcribeShortcuts.spec.tsshould usedocument.dispatchEvent(new KeyboardEvent('keydown', {...}))with a stub panel API object. This pattern already works in the test suite (see existingdispatchEventusage documented in project memory).nshortcut: label it clearly in the callback API asstartDrawMode— this is distinct from "create a block". The shortcut puts the annotation layer into draw mode; the user then draws the region. The action does not create anything directly.?shortcut: useevent.key === '?'— on QWERTZ (German keyboard),?is typed withShift+ß. The spec says "not while a modifier is held" — this means guard with!event.ctrlKey && !event.altKey && !event.metaKeybut allowevent.shiftKey(since?requires Shift on QWERTZ). Test this on a German keyboard layout in the E2E platform-labels test.👨💻 Tobias Wendt — DevOps & Platform Engineer
Observations
frontend/e2e/transcribe-shortcuts.spec.ts) will add Playwright tests that require the full stack (SvelteKit + Spring Boot + PostgreSQL + MinIO). This matches the existing E2E pattern (seeannotations.spec.tswhich seeds data via API inbeforeAll). No CI changes are needed if the existing E2E job already coverse2e/*.spec.tsby glob.userAgentin Playwright. Playwright supports this viapage.setExtraHTTPHeadersor theuserAgentcontext option. This can be done within the single spec file without any CI configuration changes.de,en,es). The Paraglide Vite plugin regeneratessrc/lib/paraglide/automatically on dev/build — no manual step needed in CI. The generated files should remain in.gitignorestatus or tracked per the existing convention (check current.gitignoreforparaglide/entries).Recommendations
annotations.spec.tspattern withrequest.post('/api/documents', ...)inbeforeAll) rather than depending on a pre-existing fixture. Fixture-dependent E2E tests are the primary cause of non-deterministic CI failures in this project.userAgentto includeMacintoshin one context and verifyCmdappears in the cheatsheet; set a non-Mac agent and verifyCtrl. Do not spin up two full browser instances for this — usetest.describewithuse: { userAgent: '...' }override within the same spec.<8 minutesE2E target as long as the new spec follows the existing seed-via-API pattern (no canvas drawing, no file uploads beyond whatannotations.spec.tsalready does). A rough estimate: 5–6 new E2E tests, ~45–60 seconds added. Acceptable.docker-compose.ymlchanges required. No new environment variables required. No Caddy config changes required. This is as low-ops as a feature gets.👨💻 Elicit — Requirements Engineer
Observations
Requirement completeness: The spec is dense and implementation-ready. However, three behavioral gaps need resolution before coding starts:
GAP 1 —
nshortcut scope is ambiguous. "Start drawing a new region" implies the annotation layer enters draw mode. But what happens when the panel is in Read mode and the user pressesn? The spec does not say whethernshould auto-switch to Edit mode first, fire only in Edit mode, or be inactive in Read mode. The acceptance criteria only state "all 9 shortcuts work as specified" — no mode-specific guards are listed per shortcut.GAP 2 —
Escconflict resolution. Two behaviors shareEsc: (1) "discard current region's unsaved edits (with confirm modal)" and (2) "close the cheatsheet overlay". The spec says cheatsheet takes precedence when open. But what if the cheatsheet is closed AND focus is inside the textarea AND the user has unsaved edits — shouldEscdiscard, or isEscinactive when focus is inside an editable? The spec's focus guard says shortcuts are "inactive when focus is in a non-save text input" — which would meanEscis also inactive. This contradicts the stated behavior ofEscas a global discard trigger. The two rules conflict.GAP 3 —
tshortcut with no active region. If no region is active (no block focused), what shouldtdo? Toggle training on nothing? The spec impliestoperates on "the current region" — but what is the current region when none is selected? Silent no-op, or scroll to the first block?Non-functional requirement gap: The spec mentions
prefers-reduced-motionfor cheatsheet animation but says nothing about reduced-motion handling for the shortcut-driven navigation transitions themselves. When pressingj, the active block scrolls into view (scrollIntoView). This scroll is animated — it should respectprefersReducedMotion(already managed in the page withbehavior: 'smooth' | 'instant'). The shortcut action's callbacks should receive this flag or the page-level implementation should handle it transparently (it will, sincescrollIntoViewis called in the page, not the action).User story coverage: The spec covers the primary user (experienced transcriber on laptop). It does not address tablet users — the project memory notes that transcribers also work on tablets. On a tablet with a software keyboard,
j/kare on the keyboard but the keyboard may be hidden when the user is not actively typing. Keyboard shortcuts are still accessible on tablets with a hardware keyboard paired, so this is not a blocker, but the cheatsheet hint on the coach card should include a note that shortcuts require a physical keyboard.Recommendations
Escto only fire the discard behavior when focus is outside any input field AND the cheatsheet is closed. When focus is inside TipTap'scontenteditable,Escshould be a no-op from the global shortcut system (TipTap may handle it internally). Add this as an explicit row in the acceptance criteria table.nto Edit mode only. If the panel is in Read mode, pressingnshould be a no-op (not auto-switch modes). This avoids surprise mode changes. Add this to the acceptance criteria.tas a no-op when no block is active. Do not auto-scroll or auto-select. Add this edge case to the test plan.transcribe_coach_title,mode_read,mode_edit). Use the same snake_case pattern:shortcut_next_region,cheatsheet_title, etc.Open Decisions
Escbehavior when unsaved edits exist and focus is outside the textarea: shouldEscdiscard immediately-with-confirm, or should it be a no-op? The current spec implies discard-with-confirm but the focus guard makes this unreachable in the most common scenario (user just finished typing, focus is still in TipTap). Clarify whether the intended workflow is: user clicks elsewhere → focus leaves TipTap → then pressesEscto discard. If yes, document this two-step flow in the cheatsheet and coach card tip.👨💻 Nora "NullX" Steiner — Security Engineer
Observations
This is a pure frontend, client-side interaction feature with no backend changes. The attack surface is minimal. Two security-adjacent concerns are worth flagging:
1.
windowkeydown listener and clickjacking / iframe context. If the app is embedded in an iframe (e.g., by a malicious third-party page), a globalwindowkeydown listener will receive events fired by the parent frame only if same-origin. Cross-origin iframes cannot inject key events into the embedded page. This is not a meaningful risk given the app is a family-internal tool, but theX-Frame-Options: DENYheader already set via Caddy (perdevops.mdreferences) eliminates the iframe scenario entirely. No additional action needed.2.
navigator.platformdeprecation as a security-adjacent concern. The spec usesnavigator.platformto detect Mac/Windows. This API is deprecated and returns a frozen value that can be spoofed by user scripts or extensions. The only consequence here is that the cheatsheet shows the wrong modifier label (Cmdinstead ofCtrlor vice versa). This is a UX defect, not a security defect. However, since the issue calls outnavigator.platformexplicitly, usenavigator.userAgentData?.platform(Secure Context required, available in Chrome/Edge) withnavigator.platformas the fallback. Add??null-safety sinceuserAgentDatais undefined in Firefox and Safari.3.
<dialog>+aria-modal— focus trap. The cheatsheet is proposed as a<dialog>element. Native<dialog>does not implement a focus trap by default in all browsers (Safari historically had gaps). Usedialog.showModal()rather than togglingopenattribute directly —showModal()activates the top-layer, which provides a native focus trap and blocks interaction with the rest of the page. This is both the accessible and secure approach: it prevents a scenario where a keyboard user tabs out of the modal and accidentally triggers another shortcut while the cheatsheet is open.4. No XSS surface. The cheatsheet is entirely static content — i18n strings from Paraglide (compile-time generated, not runtime-user-input). The key labels (
j,k,?, etc.) are hardcoded string literals. There is no dynamic content interpolation in the cheatsheet that could create an XSS vector.Recommendations
dialog.showModal()(notopenattribute toggle) for the cheatsheet. In Svelte 5, bind the dialog element and calldialogEl.showModal()/dialogEl.close()reactively. This is the correct pattern for modal dialogs and ensures the native focus trap.document.fullscreenElementis not null (user entered browser full-screen), shortcuts should still work — this is not a security concern but worth noting that thewindowlistener scope is unaffected by full-screen mode.Cmd/Ctrl+Sshortcut will be intercepted by the browser's native "Save Page" dialog on most browsers on some platforms. Test this explicitly. On Windows/Linux,Ctrl+Striggers "Save As" in Chrome if focus is onwindow(not on a form element). Since the TranscriptionEditView uses TipTap'scontenteditable, pressingCtrl+Swhile focus is in TipTap may or may not bubble to the window listener. The spec should clarify whetherCtrl+Scallsevent.preventDefault()to suppress the browser's Save dialog.Open Decisions
Ctrl+Sbrowser conflict: Should the shortcut action callevent.preventDefault()forCmd/Ctrl+Sto suppress the browser's native Save dialog? CallingpreventDefault()on key events is standard practice for shortcut handlers but should be an explicit, documented decision. Not calling it meansCtrl+Striggers both the block save AND the browser dialog on Windows/Linux — a bad experience. Recommend: always callevent.preventDefault()for all shortcuts registered by this action.👨💻 Sara Holt — QA Engineer
Observations
The test plan is solid in structure but has coverage gaps and one testability risk.
Testability risk — the action's dependency on
transcribeModestate. The spec says the action reads "a store flag" to determine if the panel is open. Looking at the actual code,transcribeModeis a$statevariable local to+page.svelte— not a Svelte store, and not directly importable in a unit test. The correct design (passingisPanelOpen: () => booleanas a callback) makes the action fully testable without mounting any Svelte component. Verify the implementation follows this callback pattern before writing tests, or the unit tests will be forced to mount+page.svelte— a bad sign.Gap in component tests — the discard-with-confirm flow. The spec says
Esctriggers a confirm modal before discarding. ThegetConfirmService()pattern is used throughout the codebase (seeTranscriptionBlock.svelte) and can be mocked. The unit test must assert: (1) confirm modal is invoked with a destructive flag, (2) if the user cancels,discardCurrentis NOT called, (3) if the user confirms,discardCurrentIS called. This three-step behavior needs three separate test cases.E2E test plan gap —
Cmd/Ctrl+Enterinside TipTap. The spec lists this as a distinct test case. The existingannotations.spec.tsseeds transcription blocks via API. The shortcut E2E test can reuse this pattern. However, typing into a TipTapcontenteditablein Playwright requirespage.locator('[contenteditable]').click()thenpage.keyboard.type(...)— standard Playwright keyboard interaction works oncontenteditable. The test should verify that focus is inside the editor ANDCtrl+EnterfiressaveAndNext, not just thatEnteralone doesn't navigate.Missing test: shortcut inactive on non-save inputs. The spec mentions that shortcuts should be inactive when focus is inside "a non-save text input." There are other inputs in the page context (e.g., the label input if one exists, or filter fields). The test plan should include a case where focus is in a plain
<input>outside the transcription block — pressingjshould NOT navigate. This is especially important given the TipTap /contenteditabledistinction.Missing test:
?shortcut with cheatsheet already open. If?is pressed while the cheatsheet is open, should it close the cheatsheet (toggle) or be a no-op? The spec says the cheatsheet closes withEscand backdrop click —?is not listed as a close trigger. The test should assert that pressing?a second time does NOT close the cheatsheet (it's a toggle-or-not decision that should be explicit).Recommendations
describeblock per shortcut key, with nesteditblocks for: (a) fires correctly, (b) does not fire when panel is closed, (c) does not fire when focus is in non-save input. Usevi.fn()stub for all callbacks.ShortcutCheatsheet.spec.ts):it('is not in the DOM when closed')—expect(dialog).not.toBeInTheDocument()ortoHaveAttribute('open', undefined)it('opens when ? is pressed')it('shows all 9 shortcut rows')it('closes on Esc')it('closes on backdrop click')it('shows Cmd on Mac user agent, Ctrl on non-Mac')beforeAllpattern: seed a document with ≥ 2 pre-existing transcription blocks via API (reuseannotations.spec.tspattern). Do not rely on OCR or manual drawing in CI.AxeBuilderon the open cheatsheet state — the acceptance criteria requirearia-modal, labelled heading, and focus trap. Wire this into the existingannotations.spec.tsaxe check pattern or the new spec.Open Decisions
?as toggle vs. open-only: if the user presses?while the cheatsheet is already open, should it close? Recommend open-only (pressing?again is a no-op;Esccloses). This avoids a scenario where?accidentally closes the cheatsheet when the user was trying to open a second one or was expecting toggle behavior from another tool.👨💻 Leonie Voss — UX Design Lead
Observations
The feature is well-motivated for the 60+ transcriber persona on a laptop. Muscle memory over a large backlog is exactly the right framing. A few design details need attention.
The
?shortcut hint in the coach card is under-specified. The issue says "mention?in the coaching card from #320." Looking atTranscribeCoachEmptyState.svelte, the coach card is a structured 3-step ol with a footer link row. Adding a fourth step or a floating badge is the right pattern. The hint should be: compact, visually secondary to the three steps (smaller font, subdued color), and use a<kbd>element for the key. Example:Tipp: Drücken Sie <kbd>?</kbd> für eine Übersicht aller Tastenkürzel.The<kbd>element is semantic and renders in the browser's monospace font — perfect for key labels without custom CSS.The cheatsheet layout spec is missing visual hierarchy details. "Two-column table of key + action label" is stated but does not specify: grouping (navigation shortcuts vs. mode shortcuts vs. utility), column proportions, or font treatment. For the 60+ user on a laptop at arm's length, recommend:
font-sans font-mono text-sm, displayed as<kbd>chips with a borderfont-serif text-sm text-inkfor readability at 16px minimummax-w-mdon desktop, full-width on mobile (though shortcuts are keyboard-only, the cheatsheet can be read on tablet)Platform label rendering: "Mac users see
Cmd; Windows/Linux seeCtrl." On QWERTZ keyboards (the primary audience is German), theCmd/Ctrlkey is also displayed as⌘on Mac keyboards in the OS UI. Consider showing the symbol⌘rather than the word "Cmd" for Mac — this is what the OS shows and what users will look for on the key. For Windows/Linux, "Strg" is the German label forCtrl. If the UI is in German locale, showing "Strg" instead of "Ctrl" is more natural for the 60+ audience.Focus trap in the cheatsheet dialog (coordination with Nora's point): when
showModal()is used, the browser traps focus inside the dialog. However, the close button must be the first focusable element (or at minimum, focus must land on it on open). This is the WCAG 2.1 pattern for dialogs. Implement:dialog.showModal(); closeButton.focus()immediately after opening.prefers-reduced-motionfor the cheatsheet overlay animation. The spec mentions this. Use a CSS-only approach:This is zero JavaScript and respects user preference natively.
Recommendations
<kbd>styling to the project's Tailwind config or use inline classes:rounded border border-line bg-muted px-1.5 py-0.5 font-mono text-xs text-ink shadow-sm— this matches the existing card and border token system.aria-label(e.g.,{m.cheatsheet_close()}) since it will be icon-only. Minimum 44×44px touch target on the close button even though this is a keyboard-primary feature — mobile users may open it via programmatic trigger.Esc→ confirm modal): ensure the confirm modal that appears afterEscis also<dialog>withshowModal(). The existinggetConfirmService()pattern handles this — confirm the confirm modal correctly traps focus and doesn't interfere with the cheatsheet's closed state.cheatsheet_titlekey — "Tastaturkürzel" is the natural German term. "Shortcuts" is understood but sounds English in a German-first app serving a 60+ audience.?hint as a footer item in the existingborder-t pt-3.5footer row ofTranscribeCoachEmptyState.svelte, not as a fourth step. It reads as a tip, not a required action.Decision Queue
Consolidated open decisions from all personas that need a human call before implementation starts. Grouped by theme.
Theme A — Component & File Locations
DQ-A1 (Markus): Where does
ShortcutCheatsheet.sveltelive?frontend/src/lib/document/transcription/ShortcutCheatsheet.svelte— domain-scoped, single consumer in v1, follows the existing structurefrontend/src/lib/shared/primitives/ShortcutCheatsheet.svelte— reusable if other panels later get shortcut overlaysThe
src/lib/components/path in the issue does not exist in this codebase. Either option 1 or 2 must be chosen before implementation. Recommendation: Option 1 (domain-scoped) — YAGNI until a second panel needs it.Theme B —
EscKey BehaviorDQ-B1 (Elicit, Sara):
Escis specified for two behaviors: (a) discard unsaved edits with a confirm modal, and (b) close the cheatsheet. The focus-guard rule ("shortcuts inactive when focus is in a non-save input") makes case (a) unreachable in the most common scenario — the user just finished typing, focus is still in TipTap's contenteditable.Clarify the intended sequence:
Escfor discard only fires when focus is outside any editable element. User must click away from TipTap first, then pressEsc. Document this two-step flow.Escfires for discard even when focus is inside TipTap, in addition toCmd/Ctrl+Enterwhich is the only other exception to the focus guard.Recommendation: Option 1 — consistent with the stated focus guard rule, avoids surprise mid-typing discard.
DQ-B2 (Nora): Should the shortcut handler call
event.preventDefault()for all registered shortcuts to suppress browser defaults (particularlyCtrl+Striggering "Save Page")?Recommendation: Yes — always call
event.preventDefault()for shortcuts the action handles. This is standard practice for app-level shortcut systems and avoids the browser Save dialog conflict on Windows/Linux.Theme C —
?Cheatsheet Toggle vs Open-OnlyDQ-C1 (Sara): If
?is pressed while the cheatsheet is already open, should it close (toggle) or be a no-op?Recommendation: Open-only —
?opens,Escand backdrop click close. This matches the mental model of the cheatsheet as a reference overlay, not a toggle switch, and avoids accidentally closing it.Theme D — Platform Labels in German Locale
DQ-D1 (Leonie): Should the modifier key label use:
Cmd/Ctrl(English, as specified)⌘/Strg(OS-native and German locale-appropriate)Recommendation:
⌘for Mac,Strgfor Windows/Linux when the UI is in German locale — the 60+ German-speaking transcriber will look forStrgon their keyboard, notCtrl. Wire this through the existing i18n system: add locale-aware variants inde.jsonusing "Strg" and inen.json/es.jsonusing "Ctrl".Theme E —
nandtShortcut Mode RestrictionsDQ-E1 (Elicit): Should
n(start drawing a new region) be active in Read mode, active in Edit mode only, or auto-switch to Edit mode?Recommendation: Edit mode only, no auto-switch — auto-switching modes on
nis a surprise behavior. If the user pressesnin Read mode, it should be a silent no-op.DQ-E2 (Elicit): Should
t(toggle training mark) be a no-op when no block is active, or should it auto-focus the first block?Recommendation: Silent no-op — consistent with standard shortcut behavior on desktop apps where commands with no applicable target are disabled.