Keyman / per-language keyboard integration (BL-16524)#8047
Conversation
Proof of concept for embedding KeymanWeb in Bloom's page-editing view. How far this got (hard-coded to Thai, as far as I'm aware): - Focusing a .bloom-editable with lang="th" (or th-*) lazy-loads the KeymanWeb engine from the Keyman CDN on first use, activates the Thai Kedmanee keyboard, and shows the floating on-screen keyboard. - Physical keys are remapped to Kedmanee (d->ก, k->า, l->ส, etc.). - Non-Thai fields are left completely alone; the OSK is hidden again when focus leaves a Thai field, so the keyboard is active ONLY in Thai fields. - Verified live in an all-xmatter Thai/English test book: Thai remaps, English types literally, OSK shows/hides as expected, no console errors. Implementation: - New keymanWebIntegration.ts, called from the delegated focusin handler in bloomEditing.ts. attachType "manual"; the exact "thai_kedmanee" stub is registered and we wait for its code to load (HasLoaded) before binding, to avoid a first-focus race. root/resources/fonts are pointed at the CDN so the OSK font loads from there instead of 404ing against Bloom's server. - A save-time scrub in Cleanup() removes KeymanWeb's attach artifacts (keymanweb-font class, inputmode, dir="ltr", empty style) from editable divs. Known limitations / not done: - Hard-coded to Thai and to the Kedmanee keyboard id; no per-language configuration yet. - Loads from the Keyman CDN at runtime, so it needs internet in the edit view; a shipping version should vendor the engine + keyboard for offline. - The Cleanup() scrub only covers page-body divs. Xmatter content round- trips through bloomDataDiv, so KeymanWeb residue still persists in saved xmatter fields; that path needs its own scrub (likely C#-side). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Design docs for generalizing the KeymanWeb POC (af6f479) into a real per-language keyboard setting: - keyboard-setting-plan.md: the agreed design (Automatic cascade: OS input method first, cached KeymanWeb fallback second, resolved per machine; active FieldWorks-style OS switching in v1 gated behind a WebView2/TSF spike; offline-first with vendored engine and collection-cached keyboards) plus ordered work items, verification strategy, and ranked risks. - implementation-handoff.md: context for the implementing agent — decision rationale (do not relitigate), live-verified Keyman API and libpalaso facts, open questions for John, and suggested skills. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds src/BloomBrowserUI/keymanweb/: the KMW engine, OSK stylesheets and OSK font, fetched pinned from s.keyman.com (MIT license included). The README documents exact sources, how the minimal file set was determined (grepping the engine for the resources it lazily fetches), the update procedure, and how the folder reaches output/browser in dev mode (both dev.mjs's own static-copy watcher and vite build's static copy handle it; confirmed by a running watcher having already copied it). The vendored files must stay byte-identical to upstream, so the folder is excluded from prettier (.prettierignore) and from git EOL/whitespace munging (.gitattributes) - the pre-commit hook reformatted the minified engine on the first commit attempt, doubling its size. Implemented by a Claude Sonnet teammate under Claude Fable orchestration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the hard-coded Thai POC in keymanWebIntegration.ts with the
general mechanism from Design/Keyboards/keyboard-setting-plan.md item 5:
on every bloom-editable focus, POST keyboarding/fieldFocused {lang} and
either attach the server-chosen KeymanWeb keyboard (lazy vendored-engine
load, local keyboard stub via addKeyboards, HasLoaded poll before
activation, OSK shown) or get out of the way (C# switches the OS input
method; OSK hidden). Langs z/*/empty are skipped. A per-language cache
avoids re-registration while still posting every focus so the C# side
can switch OS keyboards. The C# endpoint lands separately (plan item 4);
this codes against its fixed contract.
Longpress interplay (plan item 8): long-press alternates are variants of
the physical key, which is meaningless under KMW remapping, and both
libraries grab the same key events - so jquery.longpress now bails out
on elements KMW has attached to, tracked via a WeakSet in
longPressShared.ts (extends the BL-1071 window-level policy to field
granularity). OS-switched fields keep longpress.
Vitest specs cover the skip list, cache behavior, the engine-never-loads
path for useKmw:false, and the longpress tracker (7 tests).
Implemented by a Claude Sonnet teammate under Claude Fable orchestration.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plan item 0 verdict. libpalaso KeyboardController.Activate() (TF_IPPMF_FORPROCESS) does NOT affect typing in WebView2 - the input lives in a separate msedgewebview2.exe process, confirmed empirically via per-thread GetKeyboardLayout and navigator.keyboard.getLayoutMap. Active switching in v1 remains feasible: WM_INPUTLANGCHANGEREQUEST posted to Bloom's foreground top-level window switches the webview's input thread (Bloom-scoped; other apps unaffected). Items 2/4 must use libpalaso for enumeration only and carry HKLs for activation. A 30-second real-typing confirmation is still recommended (key injection was not possible during the spike; the user was in a live meeting). Spiked by a Claude Opus teammate under Claude Fable orchestration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plan item 1. WritingSystem gains Keyboard (a resolution policy string: "" = Automatic, "system:<libpalaso id>" = pinned installed input method, "kmw:<keyboardId>@<bcp47>" = pinned KeymanWeb keyboard) and CachedKmwFallbackKeyboard (top Keyman search suggestion, used only by Automatic). Both serialize in the legacy numbered form and the unnumbered <Language> form following the FontName pattern, are copied in Clone(), and are skipped for sign languages. The new KeyboardSetting value type parses/serializes the policy string, degrading malformed user data to Automatic rather than throwing. Tests cover round-trips of both XML forms, defaults, Clone, and Parse edge cases. Implemented by a Claude Sonnet teammate under Claude Fable orchestration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plan item 7, closing the gap flagged in the KeymanWeb POC: KMW's attachToControl decorates editables with a keymanweb-font class, an inputmode attribute, and dir="ltr". The JS-side scrub (bloomEditing.ts Cleanup) strips them from pages, but xmatter/data-div propagation runs in C#, so BookData now (a) never copies keymanweb-font/inputmode into the data-div and force-removes them when absent (healing POC-era pollution), and (b) skips dir="ltr" value-sensitively in GetAttributesToSave - Bloom itself only writes dir="rtl", which must survive. Tests cover fresh pollution (data-div + re-emitted page both clean), healing of pre-existing pollution, and dir="rtl" survival. Implemented by a Claude Sonnet teammate under Claude Fable orchestration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plan item 3. KeymanCloudClient talks to the two verified Keyman endpoints (search 2.0 for the per-language keyboard list ordered by finalWeight; cloud 4.0 for download metadata including font/oskFont) with a 5s metadata timeout, returning empty/null quickly when offline. JSON parsing lives in pure static methods unit-tested against canned real API responses (fetched 2026-07-09). CollectionKeyboardCache stores <collection>/Keyboards/<id>.js, an <id>.json manifest (incl. display + OSK font families/files) and a fonts/ subfolder, written atomically (temp+rename, manifest last so its presence implies completeness); GetJsUrl serves the cached js via BloomServer's localhost mapping. Offline typing then never needs the network once a keyboard is cached. Implemented by a Claude Opus teammate under Claude Fable orchestration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds "Keyboards" to every hard-coded synced-folder list (change watcher, files-to-monitor, local->repo copy, repo->local extract, repo mod-time). Because Keyboards contains a fonts/ subfolder and the existing folder sync zipped top-level files only, RobustZip gains a recursive WriteAllFilesToZip (via BloomZipFile.AddDirectoryContents), now used for all three synced folders - Allowed Words and Sample Texts are flat, so their zips are unchanged. Extraction already recreates subfolders. Known limitation (pre-existing cleanup design): the delete-if-not-in-repo pruning still scans top-level only. The TC round-trip test now covers a Keyboards folder including a file in fonts/. Implemented by a Claude Opus teammate under Claude Fable orchestration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plan item 2, using the spike's corrected mechanism. Program.cs runs KeyboardController.Initialize() as a low-priority UI-thread startup action (try/catch + NonFatalProblem; ~160-500ms one-time) and Shutdown() beside Sldr.Cleanup(). New OsKeyboards uses libpalaso ONLY for enumeration (AvailableKeyboards/TryGetKeyboard) and resolves an HKL per language; activation posts WM_INPUTLANGCHANGEREQUEST to Bloom's Shell window (the only channel the spike proved reaches the separate msedgewebview2.exe input thread - libpalaso's process-scoped Activate() does not). TIP-backed definitions (e.g. installed Keyman-for-Windows) are flagged: HKL posting selects the language but in-language profile selection was unverified (no Keyman installed on the spike machine). Implemented by Claude Sonnet teammates under Claude Fable orchestration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plan item 4. KeyboardResolver runs the per-machine cascade (pinned system -> pinned kmw -> Automatic: best OS match, else cached KMW fallback, else default) with a per-session ConcurrentDictionary cache (safe because keyboard changes require restart) and a background fetch of the top Keyman suggestion when Automatic finds no OS keyboard online - writing only CachedKmwFallbackKeyboard, marshalled to the UI thread and skipped while the settings dialog is open (plan risk #6). KeyboardingConfigApi drops all spike code and adds the real POST keyboarding/fieldFocused (handleOnUiThread): resolve, switch the OS keyboard only when the target differs from the last activation (never per keystroke), and reply the fixed contract the browser integration consumes. useKmw is true only when the keyboard is fully cached locally; otherwise it replies useKmw:false, activates default, and kicks a deduped background download so the next focus succeeds. CollectionKeyboardCache gains IsCached/GetFontUrl for the endpoint. Implemented by Claude Sonnet teammates under Claude Fable orchestration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plan item 6 (browser half). keyboardSection.tsx renders under each language's font row (languages 1-3) in the Book Making tab: an MUI select offering Automatic (with dynamic "Currently: <resolves-to>" subtext), installed OS input methods, and Keyman online keyboards ordered by popularity, with an offline hint when the cloud list is empty. It reads settings/keyboardsForLanguage and posts settings/setKeyboardForLanguage (the C# endpoints land separately). Also completes the font wiring the earlier JS slice deferred: the fieldFocused response's font/oskFont family+urls are now passed into keyman.addKeyboards' language spec so the OSK renders non-Latin keycaps. The KMW font-spec property names (font/oskFont with family + source) were verified against the vendored engine's own internalizeFont helper, not guessed. New localizable strings went into Bloom.xlf (primary settings UI); priority-file choice is pending John's confirmation per the xlf-strings skill. Implemented by a Claude Sonnet teammate under Claude Fable orchestration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds render-based unit tests for the keyboard chooser (three groups, Automatic subtext, correct raw setting string posted for system vs cloud selections, offline hint). This introduces @testing-library/react + @testing-library/dom as devDependencies and is the first render-based component test in this project - isolated in its own commit so it can be reverted independently if we prefer a different component-testing approach. The alternative (pure mocks, no render) could not meaningfully exercise the MUI Select's grouped menu. Implemented by a Claude Sonnet teammate under Claude Fable orchestration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plan item 6 (C# half), completing the pairing with the React keyboard chooser and resolving the "Cannot Find API Endpoint" dialog that appeared when the UI shipped ahead of its backend. GET settings/keyboardsForLanguage returns the contract the component consumes: current setting, language tag, what Automatic resolves to on this machine (real cascade: best OS keyboard, else cached KMW fallback, else "Default"), installed OS input methods, and Keyman cloud keyboards (empty offline). POST settings/setKeyboardForLanguage stores a pending selection, mirroring PendingFontSelections. Unlike fonts (which flag restart at POST time), the keyboard restart is flagged at dialog OK-commit: UpdateLanguageSettings now writes the pending keyboards into the WritingSystems, returns whether any changed, and for a newly-pinned kmw: keyboard kicks a background EnsureDownloaded so its files are cached before the restart. This avoids a spurious restart if the user tries a keyboard and reverts before clicking OK. The keyboard params are optional so existing font callers/tests are unaffected. Implemented by a Claude Sonnet teammate under Claude Fable orchestration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…man) Book Making settings tab layout: - Give the font, keyboard, and page-numbering selects a shared width and height so they line up (the plain-text keyboard/page-number boxes were sizing narrower than the font boxes). Adds bookMakingSelectCss in commonTabSettings and lets WinFormsStyleSelect/FontSelectComponent accept a className so the size can be applied (the old width prop was dropped). - Add vertical spacing between each language block and before Page Numbering. - Reorder each language block: font, then keyboard, then Special Script Settings last. Also snapshots ongoing Keyman work on this branch: - Editor Keyman-web integration and canvas control text menu wiring. - New editableControls (EditableControls + manager) with tests. - Keyboarding backend (KeyboardResolver, KeymanCloudClient, KeyboardSetting) and the keyboarding/collection-settings API endpoints, with tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Group the font, keyboard, and page-numbering controls on the left of the
Book Making settings tab into a light rounded panel with thin dividers
between language blocks, and match the select controls' rounded/light
border appearance ("Refined rhythm", design option 1A). Bump the settings
dialog's tab height to fit the taller panel.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
| Filename | Overview |
|---|---|
| src/BloomExe/Keyboarding/KeyboardResolver.cs | New class implementing the keyboard cascade. Well-structured with injected dependencies for testability; background fetch is properly isolated via a _runInBackground delegate so the UI thread is never blocked. |
| src/BloomExe/Keyboarding/KeymanCloudClient.cs | New Keyman API client using a shared static HttpClient. JSON parsers are pure static methods for isolated unit testing. Blocking .Result calls are safe since the client is always called from background threads. |
| src/BloomExe/Keyboarding/CollectionKeyboardCache.cs | New on-disk keyboard cache. Writes are atomic (temp-file-then-rename), manifest is written last to ensure integrity, and partial downloads never leave corrupt state. |
| src/BloomExe/web/controllers/KeyboardingConfigApi.cs | New edit-view keyboard API. fieldFocused correctly runs on the UI thread; network/download work is dispatched to Task.Run via the resolver's background delegate. |
| src/BloomExe/web/controllers/CollectionSettingsApi.cs | Adds two settings-dialog keyboard endpoints. keyboardsForLanguage correctly runs off the UI thread; minor unsynchronized double-check in EnsureKeyboardServicesBuilt (see comment). |
| src/BloomBrowserUI/bookEdit/js/keymanWebIntegration.ts | Browser-side KMW integration. Engine loads lazily; oskHideIsProgrammatic flag correctly distinguishes programmatic from user-initiated hides. Unhandled rejections are caught by .catch in the caller. |
| src/BloomBrowserUI/react_components/keyboardSection.tsx | New settings UI for per-language keyboard selection. Uses useMountEffect correctly and handles the offline case gracefully. |
| src/BloomExe/Keyboarding/OsKeyboards.cs | New OS keyboard enumeration and activation using WM_INPUTLANGCHANGEREQUEST. TIP residual risk is clearly documented. All methods are correctly UI-thread-only. |
| src/BloomExe/Collection/CollectionSettingsDialog.cs | Adds PendingKeyboardSelections and wires it through UpdateLanguageSettings. EnsureKeyboardCachedInBackground fire-and-forgets a pre-warm download for pinned KMW keyboards on OK. |
| src/BloomTests/Keyboarding/KeyboardResolverTests.cs | Comprehensive resolver tests covering all cascade branches and background fetch side effects with fake collaborators. |
Reviews (2): Last reviewed commit: "Run the Book Making keyboard cloud searc..." | Re-trigger Greptile
| <trans-unit id="CollectionSettingsDialog.BookMakingTab.KeyboardFor" translate="no"> | ||
| <source xml:lang="en">Keyboard for {0}</source> | ||
| <note>ID: CollectionSettingsDialog.BookMakingTab.KeyboardFor</note> | ||
| <note>{0} is a language name.</note> | ||
| </trans-unit> | ||
| <trans-unit id="CollectionSettingsDialog.BookMakingTab.Keyboard.Automatic" translate="no"> | ||
| <source xml:lang="en">Automatic</source> | ||
| <note>ID: CollectionSettingsDialog.BookMakingTab.Keyboard.Automatic</note> | ||
| <note>First item of the per-language Keyboard dropdown in the Book Making tab of Settings. Means "let Bloom figure out the best keyboard for this language on this machine".</note> | ||
| </trans-unit> | ||
| <trans-unit id="CollectionSettingsDialog.BookMakingTab.Keyboard.Off" translate="no"> | ||
| <source xml:lang="en">Off</source> | ||
| <note>ID: CollectionSettingsDialog.BookMakingTab.Keyboard.Off</note> | ||
| <note>An item in the per-language Keyboard dropdown in the Book Making tab of Settings. Means Bloom should not change or supply a keyboard for this language; it leaves whatever keyboard the user has active.</note> | ||
| </trans-unit> | ||
| <trans-unit id="CollectionSettingsDialog.BookMakingTab.Keyboard.AutomaticResolvesTo" translate="no"> | ||
| <source xml:lang="en">Currently: {0}</source> | ||
| <note>ID: CollectionSettingsDialog.BookMakingTab.Keyboard.AutomaticResolvesTo</note> | ||
| <note>Secondary/subtext line under "Automatic" in the per-language Keyboard dropdown. {0} is the name of the keyboard or input method that Automatic currently resolves to on this machine (may itself be untranslated, e.g. an OS input method name).</note> | ||
| </trans-unit> | ||
| <trans-unit id="CollectionSettingsDialog.BookMakingTab.Keyboard.InstalledGroup" translate="no"> | ||
| <source xml:lang="en">Installed input methods</source> | ||
| <note>ID: CollectionSettingsDialog.BookMakingTab.Keyboard.InstalledGroup</note> | ||
| <note>Group header in the per-language Keyboard dropdown, above the list of keyboards/input methods already installed on this machine.</note> | ||
| </trans-unit> | ||
| <trans-unit id="CollectionSettingsDialog.BookMakingTab.Keyboard.CloudGroup" translate="no"> | ||
| <source xml:lang="en">Keyman (online) keyboards</source> | ||
| <note>ID: CollectionSettingsDialog.BookMakingTab.Keyboard.CloudGroup</note> | ||
| <note>Group header in the per-language Keyboard dropdown, above the list of keyboards available from the Keyman online keyboard service. "Keyman" is a product name and must not be translated.</note> | ||
| </trans-unit> | ||
| <trans-unit id="CollectionSettingsDialog.BookMakingTab.Keyboard.CloudUnavailable" translate="no"> | ||
| <source xml:lang="en">Not available offline</source> | ||
| <note>ID: CollectionSettingsDialog.BookMakingTab.Keyboard.CloudUnavailable</note> | ||
| <note>Shown in place of the online Keyman keyboard list, in the per-language Keyboard dropdown, when Bloom cannot reach the Keyman server (e.g. no internet connection).</note> | ||
| </trans-unit> | ||
| <trans-unit id="CollectionSettingsDialog.BookMakingTab.Keyboard.DownloadCount" translate="no"> | ||
| <source xml:lang="en">{0} downloads</source> | ||
| <note>ID: CollectionSettingsDialog.BookMakingTab.Keyboard.DownloadCount</note> | ||
| <note>Small subtext shown under a Keyman online keyboard's name in the per-language Keyboard dropdown. {0} is a number of times that keyboard has been downloaded.</note> | ||
| </trans-unit> | ||
| <trans-unit id="CollectionSettingsDialog.BookMakingTab.Keyboard.NoneResolvesTo" translate="no"> | ||
| <source xml:lang="en">Default</source> | ||
| <note>ID: CollectionSettingsDialog.BookMakingTab.Keyboard.NoneResolvesTo</note> | ||
| <note>Used as the "Currently: {0}" value under "Automatic" in the per-language Keyboard dropdown, when this machine has no matching OS input method and no Keyman keyboard has been suggested yet for this language.</note> | ||
| </trans-unit> |
There was a problem hiding this comment.
All new keyboard strings carry
translate="no"
Every one of the ten new <trans-unit> entries — "Keyboard for {0}", "Automatic", "Off", "Currently: {0}", "Installed input methods", "Keyman (online) keyboards", etc. — has translate="no". That attribute tells Crowdin to skip them, so they will never be translated into any other language. Comparable existing strings in this file (e.g. CollectionSettingsDialog.BookMakingTab.DefaultFontFor) do not carry this attribute. If the intent is to ship these strings un-translated initially and add translations later, the translate="no" needs to be removed when they are ready for Crowdin pickup.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
[Devin] Bug: Keyboard font file changes in Team Collections are not detected for syncing (TeamCollection.cs:903)
The file-monitoring list for Team Collection sync uses Directory.EnumerateFiles(folderPath, "*") without SearchOption.AllDirectories, so files inside subfolders are omitted. Changes to cached keyboard fonts in the Keyboards/fonts/ subfolder are therefore never included in the sync checksum, and teammates never receive an updated font.
Suggested fix: enumerate with SearchOption.AllDirectories for the Keyboards folder (or the relevant monitored roots).
| const langName = getLanguageDisplayName( | ||
| editable?.getAttribute("lang") ?? null, | ||
| ); |
There was a problem hiding this comment.
[Devin] Investigate: Language-tag replacement removed data-languageTipContent; context controls now compute the name at render time (CanvasElementContextControls.tsx:70-72)
The old AddLanguageTags stamped data-languageTipContent on each editable and this component read it directly. The new code calls getLanguageDisplayName(editable?.getAttribute('lang')) → theOneLocalizationManager.getLanguageName(langCode) at render time instead. Believed functionally equivalent, but the lookup path and timing changed. Worth verifying the displayed names still match the old behavior for CJK and other non-Latin scripts.
|
|
||
| // SearchKeyboardsForLanguage already treats offline/failure as "no results" internally, so | ||
| // no extra try/catch is needed here to keep this endpoint responsive. | ||
| var cloudResults = _keymanCloudClient.SearchKeyboardsForLanguage(tag); |
There was a problem hiding this comment.
[Devin] Investigate: Settings dialog blocks the UI thread with a synchronous HTTP call to the Keyman cloud API (CollectionSettingsApi.cs:675)
GetKeyboardsForLanguage calls _keymanCloudClient.SearchKeyboardsForLanguage(tag), which does s_httpClient.GetAsync(url).Result. The endpoint is registered with handleOnUiThread: true, so the WinForms UI thread blocks for up to the 5s HttpClient timeout — the settings dialog freezes while the cloud list loads (worst case when offline). The OS-keyboard enumeration genuinely needs the UI thread, but the cloud search does not; consider moving just the cloud search off-thread.
(Greptile flagged the same line as P1.)
|
[Claude Opus 4.8] Consulted Devin on 2026-07-10 19:04 UTC up to commit 19f7fb7. Findings (1 Bug, 2 Investigate) mirrored as inline review threads; Informational items skipped. |
Master migrated the front-end from yarn to pnpm (delete yarn.lock, add pnpm-lock.yaml + pnpm-workspace.yaml, packageManager pnpm@11.5.2, volta -> vite-plus). Adopt that on this branch: - take master's pnpm-based package.json and re-add this branch's two new devDependencies (@testing-library/dom 10.4.1, @testing-library/react 16.3.2, pinned to the versions pnpm resolved); - remove yarn.lock; regenerate pnpm-lock.yaml via pnpm install. Also brings in the rest of master (57 commits) including the React 18 and vite-plus changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…BL-16524) Separate keyboard acquisition from resolution. KeyboardResolver now re-ensures a KeymanWeb keyboard's files are on disk on EVERY focus (new EnsureAcquired step) rather than only once per session, so a keyboard is (re)acquired on demand whenever it is needed but missing: a deleted .keyboards cache, a teammate who has the setting but not the files, or an offline first focus all self-heal on the next focus. Acquisition already runs in the background and no-ops when the files are present, so calling it every focus is cheap. Removes the two now-redundant in-cascade EnsureCached calls and adds a regression test. Also: the Book Making keyboard chooser now shows a pinned Keyman keyboard's id when the cloud list is unavailable (offline) instead of misreporting "Automatic"; it self-heals to the friendly name once back online. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
[Claude Opus 4.8] Bug:
x["language1Name"] = _bookSelection.CurrentSelection.CollectionSettings.Language1.Name;If Repro (observed at runtime): switch collections mid-session ( Discovered while runtime-checking the canvas-element context control on a CJK book (BL-16524). It's a fail-fast crash rather than a graceful no-op. Worth confirming whether any normal UI flow can request this endpoint with no current selection (e.g. transient states during collection switch / book close); if so, guard |
|
[Claude Opus 4.8] Question/possible bug: Language & Field-type submenus never appear on ordinary pages ( The canvas-element context menu's isCustomPage: page?.classList.contains("bloom-customLayout") ?? false,and enforced in Observed at runtime: on real pages in a normal book, the page element is classed Note the language-name label on the context control (the This may be intentional (perhaps |
|
[Claude Opus 4.8] Data oddity: stray While inspecting a trilingual CJK book (Language1
The (Surfaced during the BL-16524 runtime check of the canvas-element context control.) |
…ons (BL-16524) The per-collection KeymanWeb keyboard cache is renamed Keyboards -> .keyboards and is no longer synced through Team Collections. With keyboards now acquired on demand (KeyboardResolver re-ensures a keyboard's files on every focus), a teammate does not need the lead's cached files, so syncing the cache added weight and a subfolder change-detection bug for no benefit. - Rename the cache folder to .keyboards; the leading dot marks it local-only, per the convention that dot-prefixed folders are never synced. - Remove the .keyboards entries from Team Collection sync (FilesToMonitorForCollection, the OnChanged filter, CopyLocalFolderToRepo, ExtractFolder, GetMaxModifyTime). This returns FolderTeamCollection.cs, TeamCollection.cs and RobustZip.cs (dropping the now-unused WriteAllFilesToZip helper) to their master state. - Update FolderTeamCollectionTests to assert .keyboards is NOT synced to teammates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The keyboardsForLanguage settings endpoint did a synchronous Keyman cloud HTTP search on the WinForms UI thread (handleOnUiThread:true), freezing the settings dialog for up to the 5s HTTP timeout every time the Book Making tab opened, worst case when offline. Register the endpoint handleOnUiThread:false so the cloud search runs on a server worker thread, and marshal only the UI-thread-only OS keyboard enumeration back to the UI thread via a new RunOnUiThread helper (DialogBeingEdited.Invoke). Add KeymanCloudClientNetworkTests to pin the offline-degrades-to-empty / reconnect-repopulates behavior the chooser depends on. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This change is
Devin review