Add the lexicon provider plumbing behind the chooser - #308
Draft
imnasnainaec wants to merge 1 commit into
Draft
Conversation
Introduce LexiconProvider on the port, scope the lexicon registry to the project in view, record the project-to-lexicon link in two project settings, and register FieldWorks Lite as the first provider behind it. Availability and connection are kept apart: software that can be reached but holds no lexicon for this project still answers for its authority, so the refs it minted read as misses rather than as foreign. No chooser and no first-open offer; both stay open on the issue. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Contributor
Author
The FW Lite adapter trusts the entry service's shapes
The three throws are defects either way; How far to narrow is open:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The plumbing a lexicon chooser needs, with no chooser: a
LexiconProvideron the port, a registryscoped to the project in view, the project setting recording which lexicon that project is linked
to, and FieldWorks Lite registered behind it as the first provider.
Nothing in the registry, the stored link, or the analysis model names FieldWorks Lite. A second
provider later — the open question in #46 — means registering one, not revising this.
Partially addresses #44; the chooser and the first-open offer are still open there.
What this adds
LexiconProvideron the port (src/types/lexicon-port.d.ts) —{ authority, isAvailable(), connect(lexiconId?) }, alongside the existingLexiconResolver. It keeps two lifetimes apart:availability is a fact about the session, connection is a fact about a project, so several
connections can be live at once.
connect()with no lexicon id is the load-bearing case. It yields a resolver that declares theauthority and holds nothing, so a project with no link reads a FieldWorks Lite ref as a miss
rather than as foreign — the same rendered output, but a different fact, and the one #227's link
indicator will need to distinguish.
LexiconLink—{ authority, lexiconId }, the one lexicon a Paratext project is linked to.A project-scoped registry.
useLexiconRegistry(projectId)reads the project's link, asks eachprovider whether it can be reached, and connects the one the link names.
connectLexiconRegistry(availableProviders, link?)does the assembly and is where theavailable-vs-connected distinction is enforced. The old session-wide
sessionRegistry— "assembledonce for the session, so its answers never vary by component or by render" — contradicted a
per-project link, since more than one project can be open.
The link setting, two scalars per the storage decision below, contributed in
contributions/projectSettings.jsonand validated inmain.ts. The lexicon code is visible inproject settings so a user can clear it (this is how a project drops its link); the authority is
isHidden, matching how the Lexicon extension treatslexicon.analysisLanguage.The FieldWorks Lite provider (
src/utils/fw-lite-lexicon.ts) — availability by waiting for thelexicon.entryServicenetwork object, and a resolver overgetSense/getEntries/addEntry.Decisions
The link is our own project setting, not
lexicon.lexiconCode. That setting enforces the sameone-lexicon-per-project rule and already has the sticky-change behavior we want, but an FW Lite
lexicon code cannot name a non-FW-Lite lexicon, so #46's options would need a second mechanism. It
is also cleared by the Lexicon extension itself when a lexicon stops resolving.
Two scalar settings, not one JSON value.
interlinearizer.lexiconAuthority+interlinearizer.lexiconCodematches every project setting in either extension and stays readableand hand-clearable in the settings UI. The cost is that a half-updated link is representable, so
reads treat either half missing as no link at all.
No mirroring into
lexicon.lexiconCode. Two settings now describe one fact and can drift — butsillsdev/languageforge-lexbox#2626 makes
IEntryServicelexicon-addressed precisely so drift cannotmisroute a read or a write. Mirroring is one line to add later if the visible disagreement turns out
to matter; nothing here forecloses it.
One
fw-liteauthority, whichever store backs the lexicon. FW Lite syncs a lexicon between itsFwData and CRDT copies while preserving entry ids, so there is one id space. Splitting it per
backing store would strand every existing ref the moment a lexicon gained a second copy.
A ref naming a lexicon other than the connected one misses. Deliberate, and the consequence of
relinking being hard: after a change, previously linked glosses render as their stored free-form
text and the refs survive untouched. A ref carrying no lexicon id misses too — the lexicon in view
is never taken as the one meant.
Capabilities are
search/createwhen connected,allomorphs/msasnever. MiniLcm records noallomorphs (an entry carries one lexeme form and one morph type; variants relate separate entries)
and no MSAs (a sense carries a part of speech, without the inflection class and stem features an
analysis needs). Splitting a
partOfSpeechcapability out ofmsasis worth doing when a POSaffordance is actually designed.
Two things reviewers should weigh
The Lexicon extension's types are restated locally (
src/types/lexicon-extension.ts). Its owndeclarations reach a build only through
paranext-core/dev-appdata/cache/extension-types, which ispopulated by installing that extension — something CI (which checks out a fresh
paranext-core) anda fresh clone cannot assume. So the handful of shapes we consume are declared here structurally, and
that extension's declarations remain the standard. The alternative, augmenting
papi-shared-typeswith its command and network-object types, risks a merge conflict for anyone who does have both
installed.
resolveSense/searchByForm/createEntryare implemented here rather than left to #227.The plan comment assigned the resolver to #227, but what remains of #227 once these exist is the
display wiring — which is UI, and out of scope for this PR by the same rule that leaves out the
chooser. Say so if you would rather this PR stopped at
connect()returning a resolver thatdeclares the authority and nothing else.
Two mapping decisions are deliberately deferred to #227, since both are about presentation: a
sense's
definition(FW Lite holds it as rich text) and itssenseLabel(FW Lite does not numbersenses).
resolveSensecarries the gloss; a search candidate also carries its entry's lexeme form.Deliberately not added
listLexicons(),createLexicon(draft), anddisplayNameonLexiconProvider. The plan sketchedthem for a chooser the Interlinearizer would build itself; the decision to reuse the Lexicon
extension's own selector means the choosing affordance belongs to the provider (an FW Lite provider
would call
lexicon.chooseLexicon), not to a list the Interlinearizer renders. Adding them nowwould be API nothing calls, shaped for a chooser we are not building.
Housekeeping
The "Current Lexicon gap" notes on
EntryRefandSenseRefsaid no by-id lookup exists;getEntryand
getSensehave since landed, so those notes and the summary list are updated. The allomorph andMSA gaps stand.
Test plan
npm run lint(ESLint + stylelint +tsc --noEmit+ dependency scope) — clean.npm run test:coverage— 2412 tests pass, and the 100% threshold holds; the four touched or addedsource files are each at 100%.
src/__tests__/utils/fw-lite-lexicon.test.tscovers availability (including a service thatnever registers, and one announced but unfetchable), the unconnected resolver, refs naming another
lexicon or none, search narrowing and capping, and entry creation.
useLexiconRegistry(first-render shape, per-project links, half a link, an unreadablesetting) and
connectLexiconRegistry.connect()with a real lexicon yet, because nothing writesthe link setting until the chooser lands. Setting
interlinearizer.lexiconAuthoritytofw-liteand
interlinearizer.lexiconCodeto a real FW Lite lexicon code by hand is the way to try itagainst a running Lexicon extension.
Depends on
sillsdev/languageforge-lexbox#2627 (for sillsdev/languageforge-lexbox#2626) — this consumes
IEntryServicein its lexicon-addressed form.Merge that first.
This change is