Skip to content

Improve types for annotations and scripture view#998

Open
FyreByrd wants to merge 7 commits into
mainfrom
refactor/improve-types
Open

Improve types for annotations and scripture view#998
FyreByrd wants to merge 7 commits into
mainfrom
refactor/improve-types

Conversation

@FyreByrd

@FyreByrd FyreByrd commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

I recommend rebase and merge, since these commits are not super closely related.

Summary by CodeRabbit

  • Improvements
    • Enhanced scripture view reliability with corrected plan section visibility and more dependable in-text navigation (including footnote/link handling and media placement)
    • More robust verse/phrase selection syncing to the current selection state, with safer click targeting
    • Stronger sorting consistency across bookmarks, highlights, and notes using a unified sort mode
  • Bug Fixes
    • Improved image cropping stability with added guards for missing images and null-safe crop results
  • Maintenance
    • Improved type safety across the affected UI features and stores for greater runtime confidence

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR replaces legacy sort constants with a typed Sort API, tightens shared utility and store types, and refactors ScriptureViewSofria around typed stores, DOM helpers, and interaction handlers. The image upload crop flow adds guards for missing image instances.

Changes

TypeScript typing and render pipeline updates

Layer / File(s) Summary
Annotation sort enum and consumer route pages
src/lib/data/annotation-sort.ts, src/routes/bookmarks/+page.svelte, src/routes/highlights/+page.svelte, src/routes/notes/+page.svelte
Replaces SORT_DATE, SORT_REFERENCE, and SORT_COLOR with a typed Sort object and union type, updates comparator signatures, and switches bookmark, highlight, and note sort state to typed Sort values.
Supporting utility and store type improvements
src/lib/scripts/stringUtils.ts, src/lib/scripts/verseSelectUtil.ts, src/lib/data/stores/plan.ts, src/lib/data/stores/scripture.ts, src/lib/data/stores/reference.ts, src/lib/video/index.ts
isDefined becomes a generic type guard; verse-selection helpers read from selectedVerses directly; defaultPlanStore is exported, plan is typed, and updatePlanState guards the write; scripture store adds SelectedVersesStore and Block.text; ReferenceStore is exported; video factory return types narrow to HTMLDivElement.
ScriptureViewSofria props and query inputs
src/lib/components/ScriptureViewSofria.svelte, src/routes/text/+page.svelte
Props narrows maxSelections to number and references to ReferenceStore while removing several any-typed UI fields. The component adds typed local render models, types its store-backed inputs, and updates query(...) to use typed core parameters and media inputs.
ScriptureViewSofria rendering and DOM helpers
src/lib/components/ScriptureViewSofria.svelte
Phrase, text, footnote, verse label, figure, plan, table, and milestone rendering paths are retyped. The observer lifecycle uses defaultPlanStore and safer cleanup; intro graft ids use the workspace index; typed DOM helpers replace untyped graft and span handling.
ScriptureViewSofria click handling and action pipeline
src/lib/components/ScriptureViewSofria.svelte
Typed click handlers cover verse selection, glossary lookup, header navigation, footnote superscripts, and remote audio playback. Sofria action callbacks are updated to typed action destructuring and guarded DOM insertion paths.

Image upload null safety

Layer / File(s) Summary
Image upload null safety
src/routes/image/upload/+page.svelte
The crop state and handlers now allow image to be missing, add optional cleanup for the load listener, guard crop initialization and drag/resize paths, and return null from getCroppedImage() when no image is available.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested reviewers: chrisvire, TheNonPirate

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately describes the main type-focused changes to annotations and scripture view.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/improve-types

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/data/annotation-sort.ts (1)

50-60: ⚠️ Potential issue | 🟠 Major

Type safety gap: toSorted signature permits invalid Sort.Color usage with non-Highlight arrays.

The generic signature toSorted<T extends Annotation> allows callers to pass BookmarkItem[] or NoteItem[] with sortType === Sort.Color, but compareColor (line 38) requires HighlightItem with a penColor property. The ts-expect-error suppresses the compile error at the call site but doesn't prevent runtime failures if a developer mistakenly calls toSorted(bookmarks, Sort.Color).

In practice, only the highlights page uses Sort.Color (bookmarks and notes pages never set this sort order), but the type system doesn't enforce the constraint.

🔧 Proposed fix using function overloads

Replace the current generic signature with overloads that constrain valid combinations:

-export function toSorted<T extends Annotation>(items: T[], sortType: Sort) {
+export function toSorted(items: HighlightItem[], sortType: typeof Sort.Color): HighlightItem[];
+export function toSorted<T extends Annotation>(items: T[], sortType: Exclude<Sort, typeof Sort.Color>): T[];
+export function toSorted<T extends Annotation>(items: T[], sortType: Sort): T[] {
     if (sortType === Sort.Reference) {
         return items.toSorted(compareReference);
     } else if (sortType === Sort.Date) {
         return items.toSorted(compareDate);
     } else if (sortType === Sort.Color) {
-        //@ts-expect-error I don't know how to specify that this only be for Highlights - Aidan
-        return items.toSorted(compareColor);
+        return (items as HighlightItem[]).toSorted(compareColor);
     }
     return items;
 }

This enforces at compile time that Sort.Color can only be used with HighlightItem[], while preserving type refinement for the other branches.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/data/annotation-sort.ts` around lines 50 - 60, The toSorted function
has a generic signature that allows any Annotation type with any Sort type, but
compareColor only works with HighlightItem because it requires the penColor
property. The `@ts-expect-error` suppresses the compile error but doesn't prevent
runtime failures if Sort.Color is used with BookmarkItem or NoteItem. Replace
the current single generic signature with function overloads that enforce
compile-time constraints: create specific overload signatures that restrict
Sort.Color to only HighlightItem arrays, while allowing Sort.Reference and
Sort.Date for any Annotation type. Remove the `@ts-expect-error` comment once the
overloads prevent invalid combinations from compiling.
🧹 Nitpick comments (2)
src/lib/data/annotation-sort.ts (2)

14-28: 💤 Low value

Optional: compareReference returns -1 for equal items instead of 0.

When two annotations share the same bookIndex, chapter, and verse, the comparator returns -1 (line 26) instead of 0. This violates the comparator contract and can cause unstable sorting behavior. A proper tie-breaker (e.g., secondary sort by date) or returning 0 would make the sort stable and predictable.

♻️ Proposed fix
     } else if (parseInt(a.verse) > parseInt(b.verse)) {
         return 1;
+    } else if (parseInt(a.verse) < parseInt(b.verse)) {
+        return -1;
     } else {
-        return -1;
+        return 0;  // Equal references
     }

Or add a tie-breaker:

     } else if (parseInt(a.verse) > parseInt(b.verse)) {
         return 1;
+    } else if (parseInt(a.verse) < parseInt(b.verse)) {
+        return -1;
     } else {
-        return -1;
+        // Tie-breaker: sort by date descending
+        return a.date < b.date ? 1 : a.date > b.date ? -1 : 0;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/data/annotation-sort.ts` around lines 14 - 28, The `compareReference`
function violates the comparator contract by returning `-1` when two annotations
have equal `bookIndex`, `chapter`, and `verse` values. The final else clause
should return `0` for equal items instead of always returning `-1`. Either
replace the final else block to return `0` when all primary sorting criteria are
equal, or add a tie-breaker comparison (such as comparing by a date property) to
ensure stable and predictable sort behavior when annotations share the same
reference point.

30-36: 💤 Low value

Optional: compareDate returns -1 for equal dates instead of 0.

When a.date === b.date, the comparator returns -1 (line 34) instead of 0, violating the comparator contract and causing unstable sort order for annotations created at the same timestamp.

♻️ Proposed fix
 export function compareDate(a: Annotation, b: Annotation) {
     if (a.date < b.date) {
         return 1;
+    } else if (a.date > b.date) {
+        return -1;
     } else {
-        return -1;
+        return 0;  // Equal dates
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/data/annotation-sort.ts` around lines 30 - 36, The compareDate
function in annotation-sort.ts does not properly handle the case when a.date ===
b.date, as it returns -1 instead of 0. Restructure the function to explicitly
check all three comparison cases: if a.date < b.date return 1, else if a.date >
b.date return -1, and else (when they are equal) return 0. This ensures the
comparator follows the standard contract required for stable sorting.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/components/ScriptureViewSofria.svelte`:
- Around line 2552-2557: The loop in the ScriptureViewSofria.svelte file is
meant to clear lower-level list counters by iterating through increasing index
values with i++, but the delete statement inside the loop uses matchNum instead
of the loop variable i. This causes the same level counter to be deleted
repeatedly instead of clearing each subsequent level. Replace the matchNum
reference in the delete workspace statement with i so that each iteration
properly removes the next deeper level's list counter
(workspace[`level${i}ListNum`]).

In `@src/lib/data/stores/plan.ts`:
- Around line 60-62: The currentPlanState store is only updated when result is
truthy, which means if result is falsy (indicating no persisted state), the
previous plan state remains cached in the store. This stale state (such as
"completed" status) can affect downstream plan navigation decisions. Fix this by
adding an else clause to reset currentPlanState to an appropriate default or
empty value when result is falsy, ensuring that switching to a plan with no
saved state properly clears any previously cached state.

---

Outside diff comments:
In `@src/lib/data/annotation-sort.ts`:
- Around line 50-60: The toSorted function has a generic signature that allows
any Annotation type with any Sort type, but compareColor only works with
HighlightItem because it requires the penColor property. The `@ts-expect-error`
suppresses the compile error but doesn't prevent runtime failures if Sort.Color
is used with BookmarkItem or NoteItem. Replace the current single generic
signature with function overloads that enforce compile-time constraints: create
specific overload signatures that restrict Sort.Color to only HighlightItem
arrays, while allowing Sort.Reference and Sort.Date for any Annotation type.
Remove the `@ts-expect-error` comment once the overloads prevent invalid
combinations from compiling.

---

Nitpick comments:
In `@src/lib/data/annotation-sort.ts`:
- Around line 14-28: The `compareReference` function violates the comparator
contract by returning `-1` when two annotations have equal `bookIndex`,
`chapter`, and `verse` values. The final else clause should return `0` for equal
items instead of always returning `-1`. Either replace the final else block to
return `0` when all primary sorting criteria are equal, or add a tie-breaker
comparison (such as comparing by a date property) to ensure stable and
predictable sort behavior when annotations share the same reference point.
- Around line 30-36: The compareDate function in annotation-sort.ts does not
properly handle the case when a.date === b.date, as it returns -1 instead of 0.
Restructure the function to explicitly check all three comparison cases: if
a.date < b.date return 1, else if a.date > b.date return -1, and else (when they
are equal) return 0. This ensures the comparator follows the standard contract
required for stable sorting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 571f7433-ff8b-4107-a8d0-7109ecc2254b

📥 Commits

Reviewing files that changed from the base of the PR and between 8ed25eb and 907425d.

📒 Files selected for processing (12)
  • src/lib/components/ScriptureViewSofria.svelte
  • src/lib/data/annotation-sort.ts
  • src/lib/data/stores/plan.ts
  • src/lib/data/stores/scripture.ts
  • src/lib/scripts/stringUtils.ts
  • src/lib/scripts/verseSelectUtil.ts
  • src/lib/video/index.ts
  • src/routes/bookmarks/+page.svelte
  • src/routes/highlights/+page.svelte
  • src/routes/image/upload/+page.svelte
  • src/routes/notes/+page.svelte
  • src/routes/text/+page.svelte

Comment thread src/lib/components/ScriptureViewSofria.svelte Outdated
Comment thread src/lib/data/stores/plan.ts
@FyreByrd FyreByrd force-pushed the refactor/improve-types branch from 907425d to 6341d78 Compare June 16, 2026 14:58
@FyreByrd FyreByrd requested a review from chrisvire June 16, 2026 14:58
@FyreByrd FyreByrd force-pushed the refactor/improve-types branch from 6341d78 to 09e91f3 Compare June 16, 2026 15:09

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/components/ScriptureViewSofria.svelte (1)

1792-1876: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Duplicate code in endParagraph causes main paragraph handling to run twice.

The switch statement at line 1793 handles case 'main' (lines 1794-1821), then immediately after the switch, there's an identical if (sequenceType == 'main') block (lines 1824-1850) with the exact same code. When sequenceType === 'main', both blocks execute sequentially.

This causes:

  • appendPhrase potentially called twice
  • workspace.paragraphDiv appended to root, then the now-empty div appended again
  • Duplicate/empty DOM elements in the rendered output

The if-else chain after the switch (lines 1824-1876) appears to be legacy code that should have been removed when the switch was introduced.

🐛 Proposed fix: Remove duplicate switch case
                             switch (sequenceType) {
                                 case 'main': {
-                                    if (scriptureLogs?.paragraph) {
-                                        console.log('End main paragraph');
-                                    }
-                                    if (
-                                        workspace.phraseDiv != null &&
-                                        workspace.phraseDiv.innerText !== ''
-                                    ) {
-                                        appendPhrase(workspace);
-                                        workspace.phraseDiv = null;
-                                    }
-                                    if (versePerLine) {
-                                        if (workspace.verseDiv !== null) {
-                                            workspace.paragraphDiv.appendChild(
-                                                workspace.verseDiv.cloneNode(true)
-                                            );
-                                            workspace.verseDiv = document.createElement('div');
-                                        }
-                                    }
-                                    // Build div
-                                    if (workspace.paragraphDiv.innerHTML !== '') {
-                                        workspace.root.appendChild(workspace.paragraphDiv);
-                                    }
-                                    if (workspace.videoDiv) {
-                                        workspace.root.appendChild(workspace.videoDiv);
-                                        workspace.videoDiv = null;
-                                    }
                                     break;
                                 }
                             }
                             if (sequenceType == 'main') {

Alternatively, keep the switch case and remove the if-else block for 'main'.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/ScriptureViewSofria.svelte` around lines 1792 - 1876, The
endParagraph block contains duplicate handling for sequenceType 'main': the
switch statement's case 'main' block (lines 1794-1821) contains identical code
to the subsequent if (sequenceType == 'main') block (lines 1824-1850), causing
appendPhrase and workspace operations to execute twice. Remove the redundant if
(sequenceType == 'main') condition and its code block, but preserve the else-if
conditions for 'title', 'heading', and 'introduction' that follow it. This
leaves the switch statement as the sole handler for the 'main' case while
maintaining the if-else chain for the other sequence types.
🧹 Nitpick comments (2)
src/lib/components/ScriptureViewSofria.svelte (2)

1505-1510: 💤 Low value

Type definition for Element.atts may not match actual structure.

The atts property is typed as Record<string, string>, but usage patterns suggest values are arrays:

  • Line 2534: element.atts['start'][0] — array indexing
  • Line 584: element.atts['number'] — passed where string expected

Consider Record<string, string | string[]> or investigating the actual Proskomma schema.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/ScriptureViewSofria.svelte` around lines 1505 - 1510, The
`atts` property in the `Element` type definition is typed as `Record<string,
string>`, but the actual code usage shows values can be arrays (accessed with
index notation like `element.atts['start'][0]`). Update the type definition of
the `atts` property to `Record<string, string | string[]>` to accurately reflect
that the values can be either strings or arrays of strings, accommodating both
usage patterns throughout the codebase.

1161-1198: 💤 Low value

Style element appended without cleanup causes duplication on remount.

The fullscreen popup styles are appended to document.head every time this component mounts, but never removed in onDestroy. Repeated navigation could accumulate duplicate style blocks.

Consider either:

  1. Moving styles to a global CSS file, or
  2. Tracking and removing the style element in onDestroy
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/ScriptureViewSofria.svelte` around lines 1161 - 1198, The
style element containing the fullscreen popup styles is appended to
document.head during component initialization but is never removed when the
component unmounts, causing duplicate style blocks to accumulate on remount.
Store a reference to the created style element so it can be properly cleaned up
in the onDestroy lifecycle hook by removing it from document.head.
Alternatively, extract these styles to a global CSS file to avoid appending
styles dynamically in the component at all.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/lib/components/ScriptureViewSofria.svelte`:
- Around line 1792-1876: The endParagraph block contains duplicate handling for
sequenceType 'main': the switch statement's case 'main' block (lines 1794-1821)
contains identical code to the subsequent if (sequenceType == 'main') block
(lines 1824-1850), causing appendPhrase and workspace operations to execute
twice. Remove the redundant if (sequenceType == 'main') condition and its code
block, but preserve the else-if conditions for 'title', 'heading', and
'introduction' that follow it. This leaves the switch statement as the sole
handler for the 'main' case while maintaining the if-else chain for the other
sequence types.

---

Nitpick comments:
In `@src/lib/components/ScriptureViewSofria.svelte`:
- Around line 1505-1510: The `atts` property in the `Element` type definition is
typed as `Record<string, string>`, but the actual code usage shows values can be
arrays (accessed with index notation like `element.atts['start'][0]`). Update
the type definition of the `atts` property to `Record<string, string |
string[]>` to accurately reflect that the values can be either strings or arrays
of strings, accommodating both usage patterns throughout the codebase.
- Around line 1161-1198: The style element containing the fullscreen popup
styles is appended to document.head during component initialization but is never
removed when the component unmounts, causing duplicate style blocks to
accumulate on remount. Store a reference to the created style element so it can
be properly cleaned up in the onDestroy lifecycle hook by removing it from
document.head. Alternatively, extract these styles to a global CSS file to avoid
appending styles dynamically in the component at all.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 813b0620-4179-403d-897c-8b2d073252c6

📥 Commits

Reviewing files that changed from the base of the PR and between 907425d and 6341d78.

📒 Files selected for processing (2)
  • src/lib/components/ScriptureViewSofria.svelte
  • src/routes/image/upload/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/routes/image/upload/+page.svelte

@TheNonPirate TheNonPirate left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any problems with this.

@FyreByrd FyreByrd force-pushed the refactor/improve-types branch from 09e91f3 to 0c902c7 Compare June 29, 2026 21:40
@FyreByrd

Copy link
Copy Markdown
Collaborator Author

Resolved merge conflicts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/lib/components/ScriptureViewSofria.svelte (3)

1480-1484: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Return after handling header-reference clicks.

After headerLinkClickReference(e, target), execution continues into onClickText(e, maxSelections), so clicking a header reference can also toggle text selection.

Proposed fix
                 if (target.classList.contains('header-ref')) {
-                    headerLinkClickReference(e, target);
+                    void headerLinkClickReference(e, target);
+                    break;
                 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/ScriptureViewSofria.svelte` around lines 1480 - 1484,
After handling a header reference in ScriptureViewSofria.svelte, stop further
click processing so the same event does not also reach onClickText(e,
maxSelections). Update the click handler around headerLinkClickReference and the
subsequent onClickText call to return immediately when
target.classList.contains('header-ref') is true, preserving the existing
header-ref behavior while preventing unintended text selection toggles.

2550-2553: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard figureDiv before appending it.

figureSource() can return undefined, causing addFigureDiv() to be skipped, but endWrapper still appends workspace.figureDiv! when images are enabled. A malformed/unsupported fig wrapper can crash rendering.

Proposed fix
                                     if (usfmType === 'fig') {
-                                        if (showImage()) {
+                                        if (showImage() && workspace.figureDiv) {
                                             workspace.paragraphDiv.appendChild(
-                                                workspace.figureDiv!
+                                                workspace.figureDiv
                                             );
                                         }
+                                        workspace.figureDiv = null;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/ScriptureViewSofria.svelte` around lines 2550 - 2553, The
image rendering path in ScriptureViewSofria.svelte can append a missing figure
container, causing a crash when figureSource() is undefined and addFigureDiv()
never ran. Update the endWrapper logic around showImage() to guard
workspace.figureDiv before appending it, and only append when the figure div has
actually been created. Use the existing addFigureDiv(), figureSource(), and
endWrapper flow to keep the check localized and prevent malformed fig wrappers
from reaching the appendChild call.

1526-1531: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Align Element.atts with the Sofria payload shape.

The payload mixes scalar attrs (number) with collected attrs (src, href, title, lemma, id, link, start) that are arrays. Record<string, string> hides that split and lets [0] compile against plain strings, so bad scalar/array access slips through. Use a union/dedicated type or small access helpers here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/ScriptureViewSofria.svelte` around lines 1526 - 1531,
`Element.atts` in `ScriptureViewSofria.svelte` is typed too narrowly as
`Record<string, string>`, which hides the Sofria payload’s mix of scalar fields
and array-valued collected attrs. Update the `Element` type to use a dedicated
shape or union that distinguishes scalar attrs like `number` from array attrs
like `src`, `href`, `title`, `lemma`, `id`, `link`, and `start`, and then adjust
any `Element.atts` access in `ScriptureViewSofria` to use the correct typed
access/helpers instead of assuming plain strings.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/lib/components/ScriptureViewSofria.svelte`:
- Around line 1480-1484: After handling a header reference in
ScriptureViewSofria.svelte, stop further click processing so the same event does
not also reach onClickText(e, maxSelections). Update the click handler around
headerLinkClickReference and the subsequent onClickText call to return
immediately when target.classList.contains('header-ref') is true, preserving the
existing header-ref behavior while preventing unintended text selection toggles.
- Around line 2550-2553: The image rendering path in ScriptureViewSofria.svelte
can append a missing figure container, causing a crash when figureSource() is
undefined and addFigureDiv() never ran. Update the endWrapper logic around
showImage() to guard workspace.figureDiv before appending it, and only append
when the figure div has actually been created. Use the existing addFigureDiv(),
figureSource(), and endWrapper flow to keep the check localized and prevent
malformed fig wrappers from reaching the appendChild call.
- Around line 1526-1531: `Element.atts` in `ScriptureViewSofria.svelte` is typed
too narrowly as `Record<string, string>`, which hides the Sofria payload’s mix
of scalar fields and array-valued collected attrs. Update the `Element` type to
use a dedicated shape or union that distinguishes scalar attrs like `number`
from array attrs like `src`, `href`, `title`, `lemma`, `id`, `link`, and
`start`, and then adjust any `Element.atts` access in `ScriptureViewSofria` to
use the correct typed access/helpers instead of assuming plain strings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 60c1b57e-e4b3-4029-a30b-20d5bfd75e9c

📥 Commits

Reviewing files that changed from the base of the PR and between 3cc3fea and 905739a.

📒 Files selected for processing (3)
  • src/lib/components/ScriptureViewSofria.svelte
  • src/lib/data/stores/scripture.ts
  • src/routes/text/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/routes/text/+page.svelte
  • src/lib/data/stores/scripture.ts

FyreByrd added 6 commits July 1, 2026 13:44
This is necessary for upcoming changes...
ScriptureViewSofria still directly accesses many stores. This just restores the state of affairs to what it was before. We will need to do further refactoring at a later time in order to support testing.
@FyreByrd FyreByrd force-pushed the refactor/improve-types branch from 905739a to 4418642 Compare July 1, 2026 18:49
@FyreByrd

FyreByrd commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

Resolved merge conflicts

workspace.phraseDiv = div.cloneNode(true);
workspace.phraseDiv = div.cloneNode(true) as HTMLDivElement;
}
if (workspace.encloseInSpanTag) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more than just changing types. Did you find a bug?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants