SF-3746 Allow navigating to any drafted book if it exists#3740
SF-3746 Allow navigating to any drafted book if it exists#3740RaymondLuong3 merged 3 commits intomasterfrom
Conversation
src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.ts
Show resolved
Hide resolved
src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.ts
Outdated
Show resolved
Hide resolved
src/SIL.XForge.Scripture/ClientApp/src/assets/i18n/checking_en.json
Outdated
Show resolved
Hide resolved
| const chapterCounts: Record<string, number> = { | ||
| GEN: 50, | ||
| EXO: 40, | ||
| LEV: 27, | ||
| NUM: 36, | ||
| DEU: 34, | ||
| JOS: 24, | ||
| JDG: 21, | ||
| RUT: 4, | ||
| '1SA': 31, | ||
| '2SA': 24, | ||
| '1KI': 22, | ||
| '2KI': 25, | ||
| '1CH': 29, | ||
| '2CH': 36, | ||
| EZR: 10, | ||
| NEH: 13, | ||
| EST: 10, | ||
| JOB: 42, | ||
| PSA: 150, | ||
| PRO: 31, | ||
| ECC: 12, | ||
| SNG: 8, | ||
| ISA: 66, | ||
| JER: 52, | ||
| LAM: 5, | ||
| EZK: 48, | ||
| DAN: 12, | ||
| HOS: 14, | ||
| JOL: 3, | ||
| AMO: 9, | ||
| OBA: 1, | ||
| JON: 4, | ||
| MIC: 7, | ||
| NAM: 3, | ||
| HAB: 3, | ||
| ZEP: 3, | ||
| HAG: 2, | ||
| ZEC: 14, | ||
| MAL: 4, | ||
| MAT: 28, | ||
| MRK: 16, | ||
| LUK: 24, | ||
| JHN: 21, | ||
| ACT: 28, | ||
| ROM: 16, | ||
| '1CO': 16, | ||
| '2CO': 13, | ||
| GAL: 6, | ||
| EPH: 6, | ||
| PHP: 4, | ||
| COL: 4, | ||
| '1TH': 5, | ||
| '2TH': 3, | ||
| '1TI': 6, | ||
| '2TI': 4, | ||
| TIT: 3, | ||
| PHM: 1, | ||
| HEB: 13, | ||
| JAS: 5, | ||
| '1PE': 5, | ||
| '2PE': 3, | ||
| '1JN': 5, | ||
| '2JN': 1, | ||
| '3JN': 1, | ||
| JUD: 1, | ||
| REV: 22, | ||
| TOB: 14, | ||
| JDT: 16, | ||
| ESG: 10, | ||
| WIS: 19, | ||
| SIR: 51, | ||
| BAR: 6, | ||
| LJE: 1, | ||
| S3Y: 1, | ||
| SUS: 1, | ||
| BEL: 1, | ||
| '1MA': 16, | ||
| '2MA': 15, | ||
| '3MA': 7, | ||
| '4MA': 18, | ||
| '1ES': 9, | ||
| '2ES': 16, | ||
| MAN: 1, | ||
| PS2: 1, | ||
| ODA: 14, | ||
| PSS: 18, | ||
| JSA: 24, | ||
| JDB: 21, | ||
| TBS: 14, | ||
| SST: 1, | ||
| DNT: 12, | ||
| BLT: 1, | ||
| DAG: 14, | ||
| PS3: 4, | ||
| '2BA': 77, | ||
| LBA: 9, | ||
| JUB: 34, | ||
| ENO: 42, | ||
| '1MQ': 36, | ||
| '2MQ': 20, | ||
| '3MQ': 10, | ||
| REP: 6, | ||
| '4BA': 5, | ||
| LAO: 1 | ||
| }; |
There was a problem hiding this comment.
🟡 Incorrect keys '5ES' and '6ES' in chapterCounts should be '5EZ' and '6EZ'
The new chapterCounts table uses keys '5ES' and '6ES' for 5 Ezra and 6 Ezra, but the canonical book IDs used throughout the codebase (in verseCounts at progress.service.ts:102-103, and in all localization files like checking_en.json:155-156) are '5EZ' and '6EZ'. When expectedBookChapters is called with Canon.bookNumberToId(bookNum) for these books, it will look up '5EZ'/'6EZ' in chapterCounts, find no match, and fall back to 1 instead of the correct value 2.
Was this helpful? React with 👍 or 👎 to provide feedback.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #3740 +/- ##
==========================================
- Coverage 81.32% 81.32% -0.01%
==========================================
Files 620 620
Lines 39086 39100 +14
Branches 6375 6378 +3
==========================================
+ Hits 31787 31798 +11
+ Misses 6329 6317 -12
- Partials 970 985 +15 ☔ View full report in Codecov by Sentry. |
pmachapman
left a comment
There was a problem hiding this comment.
Just some minor fixes!
@pmachapman reviewed 7 files and all commit messages, and made 5 comments.
Reviewable status: all files reviewed, 7 unresolved discussions (waiting on RaymondLuong3).
src/SIL.XForge.Scripture/ClientApp/src/app/shared/progress-service/progress.service.ts line 100 at r2 (raw file):
DNT: 424, BLT: 42, '3ES': 944,
While you are updating this file, would you please be able to fix this value to:
'3ES': 434,
(3ES = 1ES, not 2ES as I mistakenly thought when I wrote this)
Code quote:
'3ES': 944,src/SIL.XForge.Scripture/ClientApp/src/app/shared/progress-service/progress.service.ts line 212 at r2 (raw file):
DNT: 12, BLT: 1, DAG: 14,
Can you please add the following values (based on vul.vrs):
BLT: 1,
'3ES': 9,
EZA: 12,
'5EZ': 2,
'6EZ': 2,
DAG: 14,(this will fix the Devin warning below)
Code quote:
BLT: 1,
DAG: 14,src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.ts line 819 at r2 (raw file):
// Reload config if the checksum has been reset on the server if (this.projectUserConfigDoc.data.selectedSegmentChecksum == null) { await this.loadProjectUserConfig(bookNum);
I think devin may be right under certain edge cases? Should this be:
await this.loadProjectUserConfig(this._bookNum);Code quote:
await this.loadProjectUserConfig(bookNum);src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.ts line 843 at r2 (raw file):
const allChapters: number = Math.max( this.text?.chapters[this.text.chapters.length - 1].number ?? 1,
Just in case the chapters array is empty, this should be:
this.text?.chapters[this.text.chapters.length - 1]?.number ?? 1,Code quote:
this.text?.chapters[this.text.chapters.length - 1].number ?? 1,c5c1636 to
f8b3ef4
Compare
RaymondLuong3
left a comment
There was a problem hiding this comment.
@RaymondLuong3 made 5 comments and resolved 2 discussions.
Reviewable status: 5 of 7 files reviewed, 5 unresolved discussions (waiting on pmachapman).
src/SIL.XForge.Scripture/ClientApp/src/app/shared/progress-service/progress.service.ts line 100 at r2 (raw file):
Previously, pmachapman (Peter Chapman) wrote…
While you are updating this file, would you please be able to fix this value to:
'3ES': 434,(3ES = 1ES, not 2ES as I mistakenly thought when I wrote this)
Done.
src/SIL.XForge.Scripture/ClientApp/src/app/shared/progress-service/progress.service.ts line 212 at r2 (raw file):
Previously, pmachapman (Peter Chapman) wrote…
Can you please add the following values (based on vul.vrs):
BLT: 1, '3ES': 9, EZA: 12, '5EZ': 2, '6EZ': 2, DAG: 14,(this will fix the Devin warning below)
Done.
src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.ts line 819 at r2 (raw file):
Previously, pmachapman (Peter Chapman) wrote…
I think devin may be right under certain edge cases? Should this be:
await this.loadProjectUserConfig(this._bookNum);
Done.
src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.ts line 843 at r2 (raw file):
Previously, pmachapman (Peter Chapman) wrote…
Just in case the chapters array is empty, this should be:
this.text?.chapters[this.text.chapters.length - 1]?.number ?? 1,
Done.
src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.ts
Show resolved
Hide resolved
pmachapman
left a comment
There was a problem hiding this comment.
@pmachapman reviewed 2 files and all commit messages, made 1 comment, and resolved 5 discussions.
Reviewable status:complete! all files reviewed, all discussions resolved (waiting on RaymondLuong3).
f8b3ef4 to
9085851
Compare
pmachapman
left a comment
There was a problem hiding this comment.
@pmachapman reviewed 3 files and all commit messages.
Reviewable status:complete! all files reviewed, all discussions resolved (waiting on RaymondLuong3).
There was a problem hiding this comment.
🚩 loadingStarted() may not have a corresponding loadingFinished() for draft-only books
At editor.component.ts:782, loadingStarted() is called. For draft-only books where this.text == null, changeText() early-returns at line 1787-1790 without the flow reaching any code that emits the loaded event from the text component or calls loadingFinished(). The loaded.emit() added at text.component.ts:1170 handles the case where projectHasText() returns false, but when target.id is set to undefined, bindQuill() returns early at text.component.ts:1165 without emitting loaded. This could leave the DataLoadingComponent's loading indicator active indefinitely for draft-only books. The new test at line 344-383 only checks bookName and chapters.length without verifying the loading state.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
🚩 hasSource getter returns false for draft-only books for non-Paratext users, hiding source panel
The hasSource getter at editor.component.ts:659-672 returns false when this.text == null && !this.isParatextUserRole. This means for non-Paratext-role users navigating to a draft-only book, the source panel is hidden, even though the source project likely has content for that book (since it's in the drafting range). This may be an unintended UX regression — the user navigated to a draft-only book expecting to see the draft, but both source and target panels show no content. The hasSource logic may need updating to consider this._bookNum and draft configuration rather than relying solely on this.text.
(Refers to lines 659-672)
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
🚩 Removed tests for navigation guards that no longer exist
Two tests were removed from editor.component.spec.ts: 'navigates to alternate chapter if specified chapter does not exist' and 'should navigate to projects route if url book is not in project'. These tests validated behavior that no longer applies — the PR intentionally allows navigating to books/chapters that don't exist in the project texts (for draft viewing). The removal is consistent with the intent, but the tests should be replaced with new tests verifying the correct behavior when navigating to non-existent books/chapters (beyond the single new test at line 344).
Was this helpful? React with 👍 or 👎 to provide feedback.
| this.chapters = Array.from({ length: allChapters }, (_, i) => i + 1); | ||
|
|
||
| this.updateVerseNumber(); | ||
|
|
||
| // Set chapter from route if provided |
There was a problem hiding this comment.
🚩 The chapters array is now generated from 1 to max, which may include non-existent chapters
At editor.component.ts:846-850, this.chapters is now generated as a contiguous range [1, 2, ..., allChapters] using Math.max between the project's actual last chapter and the expected chapter count from expectedBookChapters(). Previously, this.chapters was derived from this.text.chapters.map(c => c.number), which only included chapters that actually exist in the project. The new approach means that if a project has chapters 1 and 3 but not 2, the chapters array will now include chapter 2. This is a behavioral change — the user could navigate to a chapter that doesn't exist in the project. This appears intentional for the draft navigation feature (allowing navigation to chapters that may have drafts), but it represents a significant change in how chapters are enumerated.
Was this helpful? React with 👍 or 👎 to provide feedback.
| if (textDoc.isLoaded) | ||
| return textDoc.changes$.pipe( | ||
| startWith(undefined), | ||
| throttleTime(2000, asyncScheduler, { leading: true, trailing: true }), | ||
| map(() => textDoc.data?.ops), | ||
| filterNullish() | ||
| ); | ||
| else return of([] as DeltaOperation[]); |
There was a problem hiding this comment.
🚩 editor-draft returns empty ops array for unloaded text docs
At editor-draft.component.ts:437, when textDoc.isLoaded is false, the observable returns of([] as DeltaOperation[]). This empty array flows into the targetOps$ stream. Downstream code at editor-draft.component.ts:356 uses this.hasContent(this.targetDelta?.ops) to determine if there's existing content to warn about overwriting. An empty array means hasContent returns false, so the overwrite warning won't be shown for unloaded docs. This is probably the desired behavior — if the text doc doesn't exist (e.g., a chapter only available as a draft), there's nothing to overwrite. However, if the doc exists but is temporarily unloaded (e.g., offline), this could silently allow overwriting without warning.
Was this helpful? React with 👍 or 👎 to provide feedback.
| this.projectDoc.data.translateConfig.draftConfig?.draftedScriptureRange | ||
| ); | ||
| this.books = Array.from(new Set([...projectTexts, ...draftedBooks])).sort((a, b) => a - b); | ||
| this.text = this.projectDoc.data.texts.find(t => t.bookNum === bookNum); |
There was a problem hiding this comment.
🚩 this.text being undefined has cascading effects on several getters
With the removal of the redirect when a book isn't in the project (editor.component.ts:836 old lines removed), this.text can now be undefined for draft-only books. Several getters use this.text and will behave differently:
hasChapterEditPermissionat line 513 callshasChapterEditPermissionForText(this.text, this.chapter)which returnsundefinedwhen text is undefined (meaning permission is unknown)hasEditRightat line 501 checkshasChapterEditPermission === true, so it returnsfalsewhen text is undefined — this is probably fine for draft-only bookssetSegment()at line 1836 returns early whenthis.text == nullonTargetUpdatedat line 988 checksthis.text != nullbefore writing config
These all seem to degrade gracefully, but this represents a semantic shift where this.text being undefined is now a normal operational state rather than an error state.
Was this helpful? React with 👍 or 👎 to provide feedback.
src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.ts
Show resolved
Hide resolved
| if (this.projectDoc == null || this.text == null || this.chapter == null) { | ||
| this.source!.id = undefined; | ||
| this.target!.id = undefined; | ||
| if (this.source != null) this.source.id = undefined; | ||
| if (this.target != null) this.target.id = undefined; | ||
| return; |
There was a problem hiding this comment.
🚩 Draft-only books show 'error' placeholder in target editor
When a user navigates to a book that only exists in draftedScriptureRange (not in the project's texts array), this.text is undefined at editor.component.ts:836. This causes changeText() at line 1787 to early-return and set target.id = undefined. In the text component, bindQuill() sees _id == null and sets loadingState = 'no-content' without calling projectHasText(), so this.project remains whatever it was previously (or undefined). The placeholder getter at text.component.ts:353 then returns 'text.error' ("An error occurred while loading the text") because this.id == null. For a draft-only book, this message is technically misleading — the user intentionally navigated there. However, the draft tab is likely the primary UI surface for these books, and the main editor area is secondary. This may be acceptable UX but is worth the reviewer verifying the intended experience.
Was this helpful? React with 👍 or 👎 to provide feedback.
This feature allows a user to navigate to any drafted book on the project. It adds a layer to estimate the number of chapters expected for each book based on the english versifications. This will be improve further in the future to allow navigating to any chapter that exists in a draft/resource in view.
Missing book

Chapter does not exist

This change is