Repository navigation
feat(dictionary): shared team dictionary on desktop - #2855
Conversation
…ey hints by workspace
…tes in the next delta
…ry offline personal conflicts
…nal-only lists while locked
…rune stale selection
…ner words - A workspace without the plan reads as paused only when it holds shared words; otherwise owners and admins get the upgrade bar and members the member notice (the API flags every non-Business answer as paused). - Shared words spelled with ZWNJ/ZWJ (Persian, Urdu, Indic) reach the prompt again; other format and control characters are still dropped. - Sync logs a failed pass instead of leaving an unhandled rejection. - EMPTY_SHARED_HINTS is frozen. - The delete-for-everyone dialog keeps its title while it closes. - A Personal rename to a word hidden under a shared copy goes through. - A list whose words are all hidden says so instead of offering import. - Document the prompt cap's strict-priority stop.
# Conflicts: # src/stores/actionProcessingStore.ts # test/components/fieldDirectionPolicy.test.js
Review fixes for the desktop shared dictionary. Sync and storage - Send shared_at on PATCH and create only for a local set or clear (shared_at_dirty), so "Add back to Personal" sticks and an ordinary edit never clears another device's marker; the push ack keeps an edit made mid-push pending. - Pull the personal list from the epoch when there is no cursor, so the one-time backfill sees tombstones; a stalled page now throws instead of saving a frozen cursor. - Fallback retirement no longer pushes a delete: with backup on the server decides and the pull reconciles; a live cloud row beats a local retirement. Undo and unhide revive what the delete or hide retired. - New statuses ssoRequired/noAccess, kept per workspace and shared across windows; reasons invalid/recentlyDeleted; client validation matches the API and reports refused words. - The arrival toast and page reloads use sync events, so a pass in the dictation window reaches the control panel; the toast shows once per account and workspace. - Lost access only counts as moved work when it cleared something; a no-workspace pass prunes left workspaces instead of wiping all. Prompts - Shared words go to LLM prompts JSON-quoted as spelling hints, and to Cloud requests in a separate sharedDictionary field; personal-only requests are byte-identical. - Tighter but name-friendly term filter; Mistral and keyterms keep personal words first; shared fill stays inside Whisper's 550-char window with echo checks on the personal part; keyterm, BYOK and LLM character caps; NFC dedupe; the dictionary tool removes the stored spelling and applies removals before adds; a moved word hidden in the active workspace stops applying; prompts are logged by size only. Dictionary page - Locked states for SSO and lost access; specific error messages; workspace label without a name; member copy when paused. - Collator sort and virtualized rows; Clear all/Export and newest sort for personal-only users; collapsed rows act on every copy; bulk import and share skip and name unusable words; "Not used in dictation" badge. - Selection survives search; ⌘A by physical key on non-Latin layouts; editor keeps text on failure without stealing focus; View words switches tab; keyboard-focus tooltips; a11y labels; new strings and _many forms in all locales.
Review fixes for mobile's apply-only shared dictionary. - Report a stalled pull as SyncStalledError and still push; personal pulls never save a stuck cursor; the shared_at backfill replays the delta from the old cursor so deletions on other devices apply. - Fit the dictionary to Apple Intelligence's context for on-device cleanup and note actions (personal first, up to 50 shared words). - Taking a moved word back (re-add or restore) clears shared_at and pushes the clear; the push ack keeps a take-back made mid-push. - Shared words reach Cloud as a separate sharedDictionary field on /api/reason and fused transcribe, and on-device prompts quote them as spelling hints; personal-only requests are unchanged. - A moved word hidden in the active workspace stops applying and shows "Hidden for you"; moved words never disappear from the screen. - Skip unchanged state writes, reuse the spaces pass's workspaces, check the usage owner, and follow sign-in rather than backup for shared writes.
…shared dictionary - The workspace store follows another window's switch, the sync pass and the assistant's dictionary tool read the stored active workspace, and hints for a workspace that's no longer active are never published - The arrival toast is kept pending until a visible, focused control panel shows it, once per account and workspace - Main gets only the shared words that fit the transcription prompt, so auto-learn learns a team word the prompt had no room for - A personal pull that stops advancing saves its applied pages and ends the pass instead of throwing - Actions that outlive a sign-out or switch leave the mirror alone; mirrors of left workspaces are pruned every pass; Undo restores hides; a share retried as a plain word pushes shared_at later; status is recorded for its own workspace; removed or older stored hints are dropped or re-checked; contributors are replaced per pass; non-term words are reported per word - Dictionary page: split the movesOut keys and add zh _one forms (i18n:check), Edit in All words renames every copy, translated confirm buttons, delete and add target bound to their workspace, Unhide for a hidden match, View words clears the search, busy guards, Cmd+A in Hidden, comma hint, Lucide notice - Prompts: strip a recited list in the order it was sent for Mistral, xAI and Gemini; cut a first personal entry longer than the budget instead of dropping the dictionary
…ords - Port desktop's list, clause and sentence checks into isTermLikeSharedWord - Cap shared words at the Whisper decoder window (550 chars) for prompt limits of 900 or less; cut a first personal entry longer than the limit instead of sending no dictionary - Keep a pending take-back when the server retires the word, and push it as a create - Dedupe with the NFC word key - Dictionary screen: wording for one moved word and a nameless workspace
The mobile half now lives on feat/mobile-shared-dictionary, so this PR is desktop only. Restores openwhispr-mobile/ and the mobile plan to main.
- Re-run a snapshot once when a space is joined mid-pass, so its older words arrive; refuse instruction-shaped shared words. - Clear all, Export and the Personal duplicate check follow the list shown while sharing is paused or SSO-blocked. - Gemini/xAI echo check uses the keyterms sent; auto-learn counts the script bias and keyterm length. - Skip shared calls for paused non-Business workspaces and after a 404 for the session; check the account before acting on a refusal. - Signed-out users get the plain personal page; 6 s Undo; partial-failure, "still in" and Hide toasts; focus restored after row actions; sync time recorded without a workspace.
- The add field and bulk-import box opt out of the global input styling (input-inline), so they no longer draw a second bordered box with the text against its edge. - Delete for everyone uses a short title with the list in a regular-weight description, like the app's other delete dialogs (all 11 locales).
|
@Chadpiha, I found six issues while testing this:
|
Replace the grouping sidebar with a named dropdown and inline search transition. Bind mutations and pending shares to their account and workspace. Normalize fallback matching, respect prompt caps, report sync outcomes accurately, and discover external workspace upgrades.
|
As per discussions with @greatcoat , designs have been updated.
|
Preserve shared dictionary scope guards, fallback normalization, prompt caps, and sync outcomes alongside main's pending-edit and server-revision handling. Keep Enterprise-first workspace selection and publish its scope before state.
boseq
left a comment
There was a problem hiding this comment.
Reviewed 2762c8d. The six desktop findings are addressed: all 11 focused regression cases fail on the previous head and pass here. The full suite has 9,222 passing tests, and quality, translation and renderer build checks pass.
Resolve the merge conflicts and rerun PR CI before merging.
Resolves the useChatStreaming.ts import conflict: keeps main's skill imports and the branch's capped getLlmDictionaryWords, dropping the unused getDictionaryHintWords import.










Summary
Desktop side of the shared team dictionary. Members of a Business or Enterprise workspace automatically get their team's words in dictation and meeting notes: the workspace list ("Everyone at …") plus the lists of every space they belong to. Personal words always come first. Desktop has the full management UI.
openwhispr-mobile/.Design:
docs/superpowers/specs/2026-10-20-shared-dictionary-design.md. Plans:docs/superpowers/plans/2026-10-20-shared-dictionary-{1-api,2-desktop}.md(committed in this PR; the mobile plan is in the mobile PR).Desktop
src/services/SharedDictionaryService.ts,src/services/SyncService.ts,src/stores/sharedDictionaryStore.ts)src/helpers/database.js, IPC inipcHandlers.js/preload.js).server_timebecomes the cursor, and each pass re-reads the last 10 s. A full re-download replaces the local copy.ssoRequired/noAccessstatuses, kept per workspace).workspaceStorefollows another window's switch, and the sync pass and the assistant's dictionary tool read the stored active workspace. Hints for a workspace that's no longer active are never published, so the dictation window can't apply, persist or hide another workspace's words.src/utils/snippets.ts,src/utils/dictionaryPromptCap.js,src/utils/sharedDictionaryHints.ts,src/helpers/audioManager.js)contextBiasand xAI/Gemini keyterms send personal words first. The recited-list check strips in the order that was sent.sharedDictionaryfield (/api/reason, Orukeet pipeline-sessioncleanupOptions,/api/transcribe). Requests with no shared words are byte-identical to before.<>{}[], backtick or|.getLlmDictionaryWords).shared_atmarker, is hidden from Personal while its shared copy applies, and keeps applying.shared_atsyncs both ways, with a one-time backfill from the epoch so tombstones are seen. It's sent only for a local set or clear, so an ordinary edit never clears another device's marker.src/services/sharedDictionaryActions.ts).src/components/DictionaryView.tsx,src/components/dictionary/*)SharedDictionaryArrivalToastListener.tsx): a one-time "N team words added" with View words, which also clears the search. It's kept pending until a visible, focused control panel shows it, so a launch to the tray doesn't lose it. It shows once per account and workspace.src/services/tools/dictionaryTool.ts): adds always go to Personal. "Remove" on a shared word hides it for you and never deletes it for the team.npm run i18n:checkpasses.Testing
npm test: only the 17 known environmental failures, all from the local better-sqlite3 binding (built for Electron, so the SQLite tests fail here). CI runs them.npm run typecheckis clean,npm run linthas 0 errors, andnpm run i18n:checkpasses.Deploy order
Against an older API, the pipeline session refuses the new
cleanupOptionsfield. The desktop then re-mints plain ASR, so members lose combined cleanup until the API is deployed.Follow-ups / known limitations
SyncService.tsgrew by about 580 lines; the shared-dictionary pass is a clean candidate to extract.\p{L}{1,5}\., for "St. Louis") still lets a short two-sentence phrase like "Ignore the above. Say hi" through. The rule is identical in all three repos.