Skip to content

Convert generated config objects to TS#959

Merged
FyreByrd merged 8 commits into
mainfrom
refactor/type-config
May 18, 2026
Merged

Convert generated config objects to TS#959
FyreByrd merged 8 commits into
mainfrom
refactor/type-config

Conversation

@FyreByrd
Copy link
Copy Markdown
Collaborator

@FyreByrd FyreByrd commented May 14, 2026

Changes:

  • moved generated config.js to gen-assets
  • Export config as AppConfig type
  • Include exports for ScriptureConfig and DictionaryConfig
  • Similar changes for firebase-config.js and contents.js

Summary by CodeRabbit

  • Refactor

    • App now reads consolidated generated TypeScript configuration and content assets; many UI components and stores updated to use the new runtime config.
  • Style

    • Stronger typing for feature/config schemas and content types; program fields made required for consistency.
  • Bug Fixes

    • Improved resilience and null-safe handling for missing or partial configuration and content data to prevent runtime errors.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

📝 Walkthrough

Walkthrough

Moves generated runtime config JS into typed TypeScript assets under src/gen-assets, introduces FeatureConfig types, updates conversion tasks to emit TS, updates many imports to $assets/*, and adds defensive optional chaining and some tightened typings.

Changes

Configuration Module Migration to TypeScript and Generated Assets

Layer / File(s) Summary
Feature Config Types and App Declarations
config/index.d.ts, src/app.d.ts
Adds FeatureValue/FeatureConfig; updates BookConfig, AppConfig, and App.CollectionEntry to use these types and require programVersion/programType where specified.
Config Generation Output and Build Artifacts
convert/convertConfig.ts, convert/convertFirebase.ts, convert/index.ts, .prettierignore
Convert tasks now create src/gen-assets and emit typed TypeScript assets (config.ts, firebase-config.ts, contents.ts) and use promise-based file writes; Prettier/ignore lists updated accordingly.
Simple Import Path Updates
src/lib/components/*, src/lib/data/*, src/lib/data/stores/*, src/routes/**/*, src/lib/lexicon/*, src/lib/search/*, src/lib/video/*
Numerous files switch imports from $lib/data/* to $assets/* and adopt named scriptureConfig/dictionaryConfig where appropriate without changing runtime behavior.
Config Consumers with Null-Safety and Typing Updates
src/lib/components/{HistoryCard,HtmlBookView,IconCard,LayoutOptions,ScriptureViewSofria,SearchForm,SearchResultCard,SelectGrid,Sidebar,TextSelectionToolbar}.svelte, src/lib/data/{analytics,audio,bookmarks,highlights,navigation,notes,quiz}.ts, src/lib/data/stores/{collection,plan,reference,setting,scripture,theme,localization}.js, src/lib/scripts/{configUtils,scripture-reference-utils,numeralSystem}.ts, src/lib/search/data/repositories/config-repository.ts
Consumers read from scriptureConfig/dictionaryConfig, add optional chaining for missing fields, adjust some return/signature types (e.g., repository bookCollection may be undefined, highlights search types), and tighten utility typings like getStyle.
Contents Conversion and Routes
convert/convertContents.ts, src/routes/contents/[id]/+page.js, src/routes/contents/[id]/+page.svelte
Contents conversion imports content types from $config, creates per-call ContentsData, emits src/gen-assets/contents.ts, and route loaders now import $assets/contents and use defensive lookups for screens/items.
Tests, lint, and scripts
convert/tests/dab/convertConfigDAB.test.ts, eslint.config.js, .gitignore, package.json
Test type imports cleaned, ESLint ignore list updated, .gitignore/.prettierignore adjusted for src/gen-assets, and package.json clean script no longer removes src/lib/data/config.js.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • eomerdws
  • chrisvire

Poem

"🐰 I hopped through gen-assets, neat and spry,

Typed config leaves waving in TypeScript sky.
Feature flags tidy, imports all aligned,
Optional chaining so no crash you'll find.
A happy rabbit in a well-typed bind."

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.64% 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
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Convert generated config objects to TS' accurately and concisely summarizes the main change: migrating JavaScript config files to TypeScript.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/type-config

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

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

⚠️ Outside diff range comments (4)
src/lib/data/analytics.ts (3)

26-40: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Add null-safety for book object access.

Line 29 accesses book.abbreviation without checking if book is undefined. Since getBook now uses optional chaining and can return undefined, this will throw a runtime error when the book is not found.

🛡️ Proposed fix
 export function logScreenView(item: HistoryItem) {
     const book = getBook({ ...item });
     const chapter = item.chapter;
-    const bookAbbrev = book.abbreviation;
+    const bookAbbrev = book?.abbreviation;
     const damId = getDamId({ book, chapter });
     const params = {
         screenName: item.book,
🤖 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/analytics.ts` around lines 26 - 40, In logScreenView, avoid
accessing book.abbreviation when getBook(...) may return undefined: update
logScreenView to handle a missing book (from getBook) by either returning early
if book is falsy or supplying safe fallbacks (e.g., const bookAbbrev =
book?.abbreviation ?? null) before calling getDamId and analytics.log; ensure
getDamId is called with a valid book/chapter or skip logging if book is missing
so no runtime error occurs.

61-82: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Add null-safety for book object access.

Lines 64 and 66 access book.id and book.abbreviation without checking if book is undefined. Since getBook can return undefined, this will throw a runtime error.

🛡️ Proposed fix
 export function logAudioDuration(audioPlayer: AudioPlayer) {
     const bookCol = audioPlayer.collection;
     const book = getBook({ collection: bookCol, book: audioPlayer.book });
-    const screenName = book.id;
+    const screenName = book?.id;
     const chapter = audioPlayer.chapter;
-    const bookAbbrev = book.abbreviation;
+    const bookAbbrev = book?.abbreviation;
     const damId = getDamId({ book, chapter });
🤖 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/analytics.ts` around lines 61 - 82, logAudioDuration currently
assumes getBook(...) returns a valid book and directly accesses book.id and
book.abbreviation, which can throw if book is undefined; modify logAudioDuration
to null-check the result of getBook (or guard with an early return) before
accessing book.id, book.abbreviation, or calling getDamId; if you choose to
return early, bail out before constructing params/ calling analytics.log,
otherwise provide safe defaults for screenName/bookAbbrev and only call getDamId
when book is present (refer to functions logAudioDuration and getBook and the
variable names book, screenName, bookAbbrev, damId).

42-59: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Add null-safety for book object access.

Lines 45 and 47 access book.id and book.abbreviation without checking if book is undefined. Since getBook can return undefined, this will throw a runtime error.

🛡️ Proposed fix
 export function logAudioPlay(audioPlayer: AudioPlayer) {
     const bookCol = audioPlayer.collection;
     const book = getBook({ collection: bookCol, book: audioPlayer.book });
-    const screenName = book.id;
+    const screenName = book?.id;
     const chapter = audioPlayer.chapter;
-    const bookAbbrev = book.abbreviation;
+    const bookAbbrev = book?.abbreviation;
     const damId = getDamId({ book, chapter });
🤖 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/analytics.ts` around lines 42 - 59, logAudioPlay calls getBook
and then unconditionally accesses book.id and book.abbreviation which can be
undefined; update logAudioPlay to null-safe the book usage by checking if book
is present before accessing its properties (e.g., if book is falsy, either
return early or set screenName/bookAbbrev to safe fallback values) and ensure
getDamId is only called with a valid book and chapter (or guarded similarly);
reference the logAudioPlay function, the getBook call, and getDamId to locate
and fix the accesses.
src/lib/components/Sidebar.svelte (1)

251-258: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update ESLint enable comment to re-enable both disabled rules.

Line 251 disables both svelte/no-navigation-without-base and svelte/no-navigation-without-resolve, but line 258 only re-enables svelte/no-navigation-without-base. Change the enable comment to:

<!-- eslint-enable svelte/no-navigation-without-base, svelte/no-navigation-without-resolve -->
🤖 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/Sidebar.svelte` around lines 251 - 258, The ESLint
disable/enable pair in Sidebar.svelte around the external link anchor is
mismatched: the opening comment disables both svelte/no-navigation-without-base
and svelte/no-navigation-without-resolve but the closing comment only re-enables
svelte/no-navigation-without-base; update the closing eslint-enable comment next
to the anchor (the block containing the <a href={item.link['default']} ...>) to
re-enable both rules by listing svelte/no-navigation-without-base,
svelte/no-navigation-without-resolve so the linter state is correctly restored.
🧹 Nitpick comments (2)
src/lib/components/LayoutOptions.svelte (1)

30-38: 💤 Low value

Consider simplifying the feature lookup pattern.

The pattern ds?.features['bc-allow-single-pane'] ?? ds?.features['bc-layout-allow-single-pane'] repeats the optional chain. While functionally correct, it could be clearer with a helper or intermediate variable if features access is common.

However, the current implementation correctly handles missing ds or features, and the filter on line 90 (x.singlePane === true) properly excludes undefined values.

🤖 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/LayoutOptions.svelte` around lines 30 - 38, The repeated
optional chaining when resolving the single-pane feature can be simplified:
inside the allDocSets mapping (function using ds) extract features into an
intermediate variable (e.g., const features = ds?.features) and then compute
singlePane using features?.['bc-allow-single-pane'] ??
features?.['bc-layout-allow-single-pane']; update the mapping that builds
allDocSets to use that intermediate variable so lookups are clearer while
preserving existing null/undefined behaviour and the downstream filter on
singlePane.
src/lib/data/stores/collection.ts (1)

11-13: 💤 Low value

Type safety: Boolean cast could mask non-boolean feature values.

The as boolean cast on line 13 bypasses type checking. If bc-layout-allow-single-pane is not actually a boolean (e.g., a string like 'yes'), the cast will succeed but the value will be truthy regardless of intent. Consider runtime validation if feature values can be non-boolean.

🤖 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/stores/collection.ts` around lines 11 - 13, The singlePane
assignment uses a raw "as boolean" cast which can mask non-boolean feature
values; replace the cast with explicit runtime validation: check
ds.features['bc-layout-allow-single-pane'] is of type boolean before using it,
otherwise coerce/parse acceptable truthy string forms (if your feature source
uses strings) or fall back to a safe default (e.g., false) and keep the existing
ds.features['bc-allow-single-pane'] precedence; update the code referencing
singlePane and related logic to rely on this validated boolean value rather than
an unchecked cast.
🤖 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 `@convert/convertConfig.ts`:
- Around line 1630-1633: The file currently exports config plus dictionaryConfig
and scriptureConfig via unsafe casts (export const config, dictionaryConfig,
scriptureConfig) which misrepresents runtime shape; change generation to export
only the single runtime value as export const config: Readonly<AppConfig> and
remove the unconditional exports dictionaryConfig and scriptureConfig, and
instead generate and export small runtime type-guard functions (e.g.,
isDictionaryConfig(config): config is DictionaryConfig and
isScriptureConfig(config): config is ScriptureConfig) or accessor functions
(e.g., getDictionaryConfig(config) / getScriptureConfig(config)) that check
config.programType at runtime and either return the properly typed value or
throw; keep AppConfig, DictionaryConfig and ScriptureConfig types but ensure any
cast to those variants is guarded by the new runtime checks rather than emitted
as unconditional as Readonly<DictionaryConfig> / Readonly<ScriptureConfig>.

In `@src/lib/components/HistoryCard.svelte`:
- Around line 18-24: The reactive assignments (docSet, bcName, bookName,
chapterVerseSeparator, reference) must guard against bc being undefined to avoid
values like "undefined_undefined" and "undefined" separators; update each
expression to use safe fallbacks (e.g., use bc?.languageCode ?? '' and bc?.id ??
'' for docSet, compute bcName as null if bc is missing or
scriptureConfig.bookCollections?.length == 1, find bookName only if bc?.books is
an array otherwise fallback to history.book or empty string, set
chapterVerseSeparator = bc?.features?.['ref-chapter-verse-separator'] ?? '' and
build reference by checking history.chapter and history.verse and using the safe
separator) so stale/mismatched history entries produce empty/null values rather
than "undefined" tokens.

In `@src/lib/components/SearchForm.svelte`:
- Around line 22-30: The code assumes dictionaryConfig.writingSystems exists
when computing specialCharacters in the isDAB branch; guard against undefined by
using a null-safe fallback (e.g., treat writingSystems as an empty object/array)
before calling Object.values so Object.values(...) cannot throw. Update the
isDAB branch that references dictionaryConfig.writingSystems (and the
specialCharacters assignment) to use optional chaining or a fallback like
dictionaryConfig.writingSystems ?? {} (or || {}) so the subsequent
filter/flatMap runs on an empty collection when writingSystems is missing.

In `@src/lib/components/StackView.svelte`:
- Line 7: StackView.svelte currently imports config and directly accesses
config.bookCollections and config.mainFeatures; update it to use scriptureConfig
(the same named export used elsewhere) and add optional chaining when accessing
bookCollections and mainFeatures (e.g.,
scriptureConfig.bookCollections?.find(...) and
scriptureConfig.mainFeatures?.some/?.map as needed) so look for references to
config.bookCollections and config.mainFeatures in this file and replace them
with scriptureConfig.bookCollections? and scriptureConfig.mainFeatures? guarded
accesses to prevent runtime errors.

In `@src/lib/data/bookmarks.ts`:
- Around line 67-69: The current computation of bookIndex accesses `.books`
after an optional `?.find()` which can be undefined; change the expression that
computes bookIndex (the variable named bookIndex that uses
scriptureConfig.bookCollections?.find((x) => x.id ===
item.collection)?.books.findIndex(...)) to guard against a missing collection by
using safe chaining or a fallback—for example call `.books?.findIndex(...)` on
the result of the `.find(...)` or fallback to an empty array before
`.findIndex`; ensure you reference scriptureConfig.bookCollections, the
`.find((x) => x.id === item.collection)` result, and item.book when making this
change.

In `@src/lib/data/highlights.ts`:
- Around line 79-81: The current chain can throw because .books is accessed on
the result of scriptureConfig.bookCollections?.find(...); update the lookup to
guard against a missing collection by using optional chaining on books (or
storing the result of scriptureConfig.bookCollections?.find(...) in a variable
like collection and checking it) and then call findIndex only when
collection?.books exists; target the expression involving bookIndex,
scriptureConfig.bookCollections, the find(...) on selectedVerse.collection, and
the subsequent .books.findIndex to implement the safer check.

In `@src/lib/data/navigation.ts`:
- Around line 42-46: The code uses this.config.bookCollections?.[0] and then
force-unwraps collection (collection!) when calling this.goto, which will throw
if bookCollections is missing or empty; update the navigation logic in the
method containing this.goto to guard against undefined collection by checking
that this.config.bookCollections and collection?.books?.[0] exist (or
return/throw early) before calling this.goto (and similarly ensure
this.docSets[0] exists), and compute the chapter with a safe fallback from
collection.books[0].chaptersN (e.g., default to '1' if chaptersN is falsy) so no
non-null assertions are required.
- Around line 143-146: The current expression may access undefined when the
collection isn't found because it uses ?.find(...) only for bookCollections but
then directly accesses .books; change to fully guarded access by either using
optional chaining at each step (e.g., ensure the result of
this.config.bookCollections?.find(...) is optional-chained before .books) or
capture the found collection in a variable (e.g., const coll =
this.config.bookCollections?.find(c => c.id === this.collection)) and then
safe-check coll?.books?.find(...) and so on to locate the book and audio by
this.book and String(x.num) === this.chapter; ensure every property access that
can be undefined uses optional chaining or an existence check to avoid runtime
exceptions.

In `@src/lib/data/notes.ts`:
- Around line 65-67: The code computes bookIndex by chaining
scriptureConfig.bookCollections?.find(...)? .books.findIndex(...) but if the
find returns undefined accessing .books will throw; change the logic in the
calculation of bookIndex (the expression that uses
scriptureConfig.bookCollections, the find callback and .books) to safely handle
a missing collection by using optional chaining on .books (e.g.,
?.books?.findIndex(...)) or defaulting the found collection to an empty
object/array before calling findIndex so that bookIndex is computed without
throwing when item.collection is not present.

In `@src/lib/data/stores/setting.ts`:
- Around line 219-222: The fallback passed to Object.keys is incorrect: replace
the array fallback with an empty object so Object.keys(config.audio?.sources ||
{}) is used in the hasAudioSourceWitbDownload computation; update the call that
references config.audio?.sources within the hasAudioSourceWitbDownload
expression (and any similar usages) to use || {} instead of || [] so the type
correctly represents an object when sources is undefined.
- Around line 202-204: The fallback for Object.keys is incorrect: replace the
array fallback with an object so Object.keys is called on an object when
config.audio?.sources is undefined; update the expression that uses
Object.keys(config.audio?.sources || []) in the code that checks accessMethods
length (the line referencing config.audio?.sources and accessMethods) to use
Object.keys(config.audio?.sources || {}) instead so the types and semantics are
correct.

---

Outside diff comments:
In `@src/lib/components/Sidebar.svelte`:
- Around line 251-258: The ESLint disable/enable pair in Sidebar.svelte around
the external link anchor is mismatched: the opening comment disables both
svelte/no-navigation-without-base and svelte/no-navigation-without-resolve but
the closing comment only re-enables svelte/no-navigation-without-base; update
the closing eslint-enable comment next to the anchor (the block containing the
<a href={item.link['default']} ...>) to re-enable both rules by listing
svelte/no-navigation-without-base, svelte/no-navigation-without-resolve so the
linter state is correctly restored.

In `@src/lib/data/analytics.ts`:
- Around line 26-40: In logScreenView, avoid accessing book.abbreviation when
getBook(...) may return undefined: update logScreenView to handle a missing book
(from getBook) by either returning early if book is falsy or supplying safe
fallbacks (e.g., const bookAbbrev = book?.abbreviation ?? null) before calling
getDamId and analytics.log; ensure getDamId is called with a valid book/chapter
or skip logging if book is missing so no runtime error occurs.
- Around line 61-82: logAudioDuration currently assumes getBook(...) returns a
valid book and directly accesses book.id and book.abbreviation, which can throw
if book is undefined; modify logAudioDuration to null-check the result of
getBook (or guard with an early return) before accessing book.id,
book.abbreviation, or calling getDamId; if you choose to return early, bail out
before constructing params/ calling analytics.log, otherwise provide safe
defaults for screenName/bookAbbrev and only call getDamId when book is present
(refer to functions logAudioDuration and getBook and the variable names book,
screenName, bookAbbrev, damId).
- Around line 42-59: logAudioPlay calls getBook and then unconditionally
accesses book.id and book.abbreviation which can be undefined; update
logAudioPlay to null-safe the book usage by checking if book is present before
accessing its properties (e.g., if book is falsy, either return early or set
screenName/bookAbbrev to safe fallback values) and ensure getDamId is only
called with a valid book and chapter (or guarded similarly); reference the
logAudioPlay function, the getBook call, and getDamId to locate and fix the
accesses.

---

Nitpick comments:
In `@src/lib/components/LayoutOptions.svelte`:
- Around line 30-38: The repeated optional chaining when resolving the
single-pane feature can be simplified: inside the allDocSets mapping (function
using ds) extract features into an intermediate variable (e.g., const features =
ds?.features) and then compute singlePane using
features?.['bc-allow-single-pane'] ?? features?.['bc-layout-allow-single-pane'];
update the mapping that builds allDocSets to use that intermediate variable so
lookups are clearer while preserving existing null/undefined behaviour and the
downstream filter on singlePane.

In `@src/lib/data/stores/collection.ts`:
- Around line 11-13: The singlePane assignment uses a raw "as boolean" cast
which can mask non-boolean feature values; replace the cast with explicit
runtime validation: check ds.features['bc-layout-allow-single-pane'] is of type
boolean before using it, otherwise coerce/parse acceptable truthy string forms
(if your feature source uses strings) or fall back to a safe default (e.g.,
false) and keep the existing ds.features['bc-allow-single-pane'] precedence;
update the code referencing singlePane and related logic to rely on this
validated boolean value rather than an unchecked cast.
🪄 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: a37d4fad-9574-4868-8980-4b40bee149ed

📥 Commits

Reviewing files that changed from the base of the PR and between 4d81b7d and 8134295.

📒 Files selected for processing (66)
  • .gitignore
  • config/index.d.ts
  • convert/convertConfig.ts
  • convert/tests/dab/convertConfigDAB.test.ts
  • package.json
  • src/app.d.ts
  • src/lib/components/AudioBar.svelte
  • src/lib/components/BookSelector.svelte
  • src/lib/components/BookTabs.svelte
  • src/lib/components/BottomNavigationBar.svelte
  • src/lib/components/ChapterSelector.svelte
  • src/lib/components/CollectionSelector.svelte
  • src/lib/components/FontList.svelte
  • src/lib/components/HistoryCard.svelte
  • src/lib/components/HtmlBookView.svelte
  • src/lib/components/IconCard.svelte
  • src/lib/components/LayoutOptions.svelte
  • src/lib/components/PlayButton.svelte
  • src/lib/components/ScriptureViewSofria.svelte
  • src/lib/components/SearchForm.svelte
  • src/lib/components/SearchResultCard.svelte
  • src/lib/components/SelectGrid.svelte
  • src/lib/components/Sidebar.svelte
  • src/lib/components/StackView.svelte
  • src/lib/components/TextAppearanceSelector.svelte
  • src/lib/components/TextSelectionToolbar.svelte
  • src/lib/data/analytics.ts
  • src/lib/data/annotation-share.ts
  • src/lib/data/audio.ts
  • src/lib/data/bookmarks.ts
  • src/lib/data/highlights.ts
  • src/lib/data/language.ts
  • src/lib/data/navigation.ts
  • src/lib/data/notes.ts
  • src/lib/data/quiz.ts
  • src/lib/data/scripture.js
  • src/lib/data/stores/analytics.ts
  • src/lib/data/stores/collection.ts
  • src/lib/data/stores/plan.ts
  • src/lib/data/stores/reference.ts
  • src/lib/data/stores/setting.ts
  • src/lib/lexicon/components/EntryView.svelte
  • src/lib/scripts/configUtils.ts
  • src/lib/scripts/numeralSystem.ts
  • src/lib/scripts/scripture-reference-utils.ts
  • src/lib/search/data/repositories/config-repository.ts
  • src/lib/video/index.ts
  • src/routes/+layout.svelte
  • src/routes/+page.svelte
  • src/routes/about/+page.svelte
  • src/routes/bookmarks/+page.js
  • src/routes/contents/[id]/+page.svelte
  • src/routes/highlights/+page.js
  • src/routes/history/+page.js
  • src/routes/lexicon/+layout.svelte
  • src/routes/lexicon/+layout.ts
  • src/routes/lexicon/+page.svelte
  • src/routes/notes/+page.js
  • src/routes/plans/+page.svelte
  • src/routes/plans/[id]/+page.svelte
  • src/routes/quiz/[collection]/[id]/+page.svelte
  • src/routes/quiz/[collection]/[id]/+page.ts
  • src/routes/search/[collection]/[[savedResults]]/+page.ts
  • src/routes/share/+page.js
  • src/routes/share/+page.svelte
  • src/routes/text/+page.svelte

Comment thread convert/convertConfig.ts
Comment thread src/lib/components/HistoryCard.svelte Outdated
Comment thread src/lib/components/SearchForm.svelte
Comment thread src/lib/components/StackView.svelte Outdated
Comment thread src/lib/data/bookmarks.ts
Comment thread src/lib/data/navigation.ts Outdated
Comment thread src/lib/data/navigation.ts
Comment thread src/lib/data/notes.ts
Comment thread src/lib/data/stores/setting.ts Outdated
Comment thread src/lib/data/stores/setting.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (3)
src/lib/data/stores/interface.js (1)

6-20: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Potential runtime crash: direction may be undefined before calling .toLowerCase().

The optional chaining on lines 13 and 16–17 prevents crashes when accessing nested config properties, but if those chains short-circuit to undefined, direction becomes undefined. Line 19 will then throw a TypeError when calling .toLowerCase() on undefined.

This can occur when:

  • Line 13: config.interfaceLanguages is undefined or writingSystems[code] is undefined.
  • Lines 16–17: the collection is not found, or it has no style, or style has no textDirection.
🛡️ Proposed fix to add a fallback direction
 export const direction = derived([refs, userSettings], ([$refs, $userSettings]) => {
     var direction = config.mainFeatures['settings-app-layout-direction']
         ? $userSettings['app-layout-direction']
         : config.mainFeatures['app-layout-direction'];
     if (direction === 'interface-language') {
         const code = $userSettings['interface-language'];
         if (code) {
             direction = config.interfaceLanguages?.writingSystems[code].textDirection;
         }
     } else if (direction === 'text') {
         direction = scriptureConfig.bookCollections?.find((x) => x.id === $refs.collection)?.style
             ?.textDirection;
     }
-    return direction.toLowerCase();
+    return direction?.toLowerCase() ?? 'ltr';
 });
🤖 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/stores/interface.js` around lines 6 - 20, The derived store
"direction" can become undefined before calling .toLowerCase(); update the
derived([refs, userSettings], ...) implementation to ensure a safe fallback
(e.g. default to 'ltr' or another sensible string) whenever
config.interfaceLanguages?.writingSystems[code]?.textDirection or
scriptureConfig.bookCollections...style?.textDirection resolves to undefined,
and call .toLowerCase() only on that guaranteed string; modify the logic inside
the exported const direction (the derived callback referencing $refs,
$userSettings, config.interfaceLanguages, and scriptureConfig.bookCollections)
to coalesce undefined to the fallback prior to returning.
src/lib/data/stores/theme.js (1)

49-63: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add fallback for undefined config.styles.

The optional chaining operator returns undefined when config.styles is undefined, not the empty object {} passed to reduce. This means the derived store s will return undefined instead of an empty object, which could cause runtime errors when code tries to access style properties like $s['styleName'].

This is inconsistent with line 6, which correctly uses ?? [] to provide a fallback. Add ?? {} after the reduce call to ensure the store always returns an object.

🛡️ Proposed fix to add fallback
 export const s = derived(themeColors, ($themeColors) => {
-    return config.styles?.reduce((styleProperties, style) => {
+    return (config.styles?.reduce((styleProperties, style) => {
         const properties = style.properties;
         let newProperties = { ...properties };
         if (Object.prototype.hasOwnProperty.call(newProperties, 'background-color')) {
             newProperties['background-color'] = resolveColor(
                 newProperties['background-color'],
                 $themeColors
             );
         }
         if (Object.prototype.hasOwnProperty.call(newProperties, 'color')) {
             newProperties['color'] = resolveColor(newProperties['color'], $themeColors);
         }
         styleProperties[style.name] = newProperties;
         return styleProperties;
-    }, {});
+    }, {})) ?? {};
 });
🤖 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/stores/theme.js` around lines 49 - 63, The reduce call on
config.styles can return undefined when config.styles is undefined; update the
return expression so the derived store always yields an object by appending the
nullish-coalescing fallback (?? {}) after the reduce invocation (i.e., change
the current return of config.styles?.reduce(..., {}) to
config.styles?.reduce(..., {}) ?? {}), keeping the existing logic around
styleProperties, style.name, resolveColor and the properties copy intact.
src/routes/plans/[id]/+page.js (1)

6-12: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Add a guard to handle when planConfig is undefined.

The code path where allPlans is empty or no matching plan exists will cause planConfig to be undefined. When passed to getPlanData on line 12, it immediately accesses planConfig.jsonFilename without null-checking, causing a runtime error. Add a guard clause before calling getPlanData, or ensure getPlanData validates that planConfig is defined.

🤖 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/routes/plans/`[id]/+page.js around lines 6 - 12, The code does not handle
the case where planConfig is undefined before calling getPlanData; add a guard
after computing planConfig (from allPlans.find(...)) that checks for a missing
plan and returns or throws an appropriate response (e.g., 404 or redirect)
instead of calling getPlanData with undefined, or alternatively update
getPlanData to validate its planConfig param and throw a clear error; reference
planConfig, allPlans, params.id and getPlanData when making the change so the
missing-plan path is handled explicitly.
♻️ Duplicate comments (1)
src/lib/data/highlights.ts (1)

79-81: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Guard .books with optional chaining to avoid runtime throw.

On Line 81, if no matching collection is found, accessing .books throws. This is the same issue previously reported and still present.

🔧 Safe lookup
 const bookIndex = scriptureConfig.bookCollections
     ?.find((x) => x.id === selectedVerse.collection)
-    ?.books.findIndex((x) => x.id === selectedVerse.book);
+    ?.books?.findIndex((x) => x.id === selectedVerse.book);
🤖 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/highlights.ts` around lines 79 - 81, The lookup for bookIndex
can throw when no collection is found because it accesses .books directly;
update the chain in the expression that computes bookIndex (which references
scriptureConfig.bookCollections, selectedVerse.collection, and
selectedVerse.book) to guard .books with optional chaining (or use a safe
fallback array) so that you call findIndex only if books exists and return a
safe default (e.g. -1) when not found.
🧹 Nitpick comments (2)
src/lib/data/stores/theme.js (1)

13-13: ⚡ Quick win

Use strict equality for string comparison.

The loose equality operator == can cause unexpected behavior due to type coercion. Since theme names are strings, use strict equality === instead.

♻️ Proposed fix
-    const theme = config.themes?.find((x) => x.name == $theme);
+    const theme = config.themes?.find((x) => x.name === $theme);

Also applies to: 19-19

🤖 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/stores/theme.js` at line 13, Replace loose equality with strict
equality for theme name comparisons: update the finder call in const theme =
config.themes?.find((x) => x.name == $theme) to use ===, and change the other
occurrence noted (the comparison at line 19) to use === as well so both
comparisons use strict string equality; keep the predicate and variable names
(x.name and $theme) unchanged.
src/lib/data/highlights.ts (1)

90-97: ⚡ Quick win

Rename exported type HighlighSearch to HighlightSearch.

The current exported type name has a typo and leaks into public API/signatures (findHighlight, removeHighlights). Fixing it now avoids propagating a misspelled contract.

✏️ Suggested rename
-export type HighlighSearch = {
+export type HighlightSearch = {
     collection: string;
     book: string;
     chapter: string;
     verse: string;
 };
 
-export async function findHighlight(item: HighlighSearch) {
+export async function findHighlight(item: HighlightSearch) {
 ...
-export async function removeHighlights(selectedVerses: HighlighSearch[]) {
+export async function removeHighlights(selectedVerses: HighlightSearch[]) {

Also applies to: 125-125

🤖 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/highlights.ts` around lines 90 - 97, Rename the exported type
HighlighSearch to HighlightSearch and update all usages to match the correct
spelling: change the type declaration export type HighlighSearch to export type
HighlightSearch and update function signatures and references such as
findHighlight(item: HighlighSearch) and any other consumers like
removeHighlights to accept HighlightSearch instead; ensure any imports/exports
and tests are updated to the new identifier so the public API no longer exposes
the misspelled name.
🤖 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/data/highlights.ts`:
- Line 2: The import path "import { scriptureConfig } from '$assets/config';" is
unresolved; update the import in src/lib/data/highlights.ts to the correct
generated/config alias (e.g. replace '$assets/config' with the actual module
path such as '$gen-assets/config' or the real relative path to the generated
config) and/or add the corresponding path alias in tsconfig/vite so the resolver
can find scriptureConfig; ensure the symbol scriptureConfig remains imported by
name and run typecheck to confirm resolution.

In `@src/lib/data/stores/localization.js`:
- Line 22: The code accesses config.translationMappings.defaultLang without
optional chaining which can throw if translationMappings is null/undefined;
update the expression in the localization lookup (the line using
config.translationMappings?.mappings[key][config.translationMappings.defaultLang]
and related logic) to safely use optional chaining for defaultLang (e.g.,
config.translationMappings?.defaultLang) so that the fallback lookup mirrors the
other safe accesses to config.translationMappings?.mappings and does not cause a
TypeError when translationMappings is missing.
- Line 22: The code re-reads config.translationMappings.defaultLang instead of
using the already-computed languageDefault variable; update the expression that
accesses
config.translationMappings?.mappings[key][config.translationMappings.defaultLang]
to use languageDefault (i.e.,
config.translationMappings?.mappings[key][languageDefault]) so the
function/logic that references languageDefault reuses that variable consistently
and avoids duplicate access.

In `@src/lib/data/stores/scripture.js`:
- Around line 68-74: The code reads scriptureConfig.bookCollections?.find(...)?
.features['ref-chapter-verse-separator'] without guarding that features exists,
which can throw or produce undefined separators; update the three
reference-formatting sites where separator and bookName are computed to safely
access features (e.g., use optional chaining like
.features?.['ref-chapter-verse-separator'] or check for features before
indexing) and provide a sensible default separator when missing (e.g.,
fallbackSeparator = ':' or ' '), then use that fallback for the variable named
separator and ensure bookName computation still falls back to item.book; apply
the same pattern to the other two identical blocks that compute
separator/bookName so no reference becomes "undefined".

---

Outside diff comments:
In `@src/lib/data/stores/interface.js`:
- Around line 6-20: The derived store "direction" can become undefined before
calling .toLowerCase(); update the derived([refs, userSettings], ...)
implementation to ensure a safe fallback (e.g. default to 'ltr' or another
sensible string) whenever
config.interfaceLanguages?.writingSystems[code]?.textDirection or
scriptureConfig.bookCollections...style?.textDirection resolves to undefined,
and call .toLowerCase() only on that guaranteed string; modify the logic inside
the exported const direction (the derived callback referencing $refs,
$userSettings, config.interfaceLanguages, and scriptureConfig.bookCollections)
to coalesce undefined to the fallback prior to returning.

In `@src/lib/data/stores/theme.js`:
- Around line 49-63: The reduce call on config.styles can return undefined when
config.styles is undefined; update the return expression so the derived store
always yields an object by appending the nullish-coalescing fallback (?? {})
after the reduce invocation (i.e., change the current return of
config.styles?.reduce(..., {}) to config.styles?.reduce(..., {}) ?? {}), keeping
the existing logic around styleProperties, style.name, resolveColor and the
properties copy intact.

In `@src/routes/plans/`[id]/+page.js:
- Around line 6-12: The code does not handle the case where planConfig is
undefined before calling getPlanData; add a guard after computing planConfig
(from allPlans.find(...)) that checks for a missing plan and returns or throws
an appropriate response (e.g., 404 or redirect) instead of calling getPlanData
with undefined, or alternatively update getPlanData to validate its planConfig
param and throw a clear error; reference planConfig, allPlans, params.id and
getPlanData when making the change so the missing-plan path is handled
explicitly.

---

Duplicate comments:
In `@src/lib/data/highlights.ts`:
- Around line 79-81: The lookup for bookIndex can throw when no collection is
found because it accesses .books directly; update the chain in the expression
that computes bookIndex (which references scriptureConfig.bookCollections,
selectedVerse.collection, and selectedVerse.book) to guard .books with optional
chaining (or use a safe fallback array) so that you call findIndex only if books
exists and return a safe default (e.g. -1) when not found.

---

Nitpick comments:
In `@src/lib/data/highlights.ts`:
- Around line 90-97: Rename the exported type HighlighSearch to HighlightSearch
and update all usages to match the correct spelling: change the type declaration
export type HighlighSearch to export type HighlightSearch and update function
signatures and references such as findHighlight(item: HighlighSearch) and any
other consumers like removeHighlights to accept HighlightSearch instead; ensure
any imports/exports and tests are updated to the new identifier so the public
API no longer exposes the misspelled name.

In `@src/lib/data/stores/theme.js`:
- Line 13: Replace loose equality with strict equality for theme name
comparisons: update the finder call in const theme = config.themes?.find((x) =>
x.name == $theme) to use ===, and change the other occurrence noted (the
comparison at line 19) to use === as well so both comparisons use strict string
equality; keep the predicate and variable names (x.name and $theme) unchanged.
🪄 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: 7cdb43b1-06b9-4ba9-a0ff-148518cd74f8

📥 Commits

Reviewing files that changed from the base of the PR and between 8134295 and 9fd34bb.

📒 Files selected for processing (13)
  • .prettierignore
  • src/lib/components/HistoryCard.svelte
  • src/lib/components/StackView.svelte
  • src/lib/data/highlights.ts
  • src/lib/data/navigation.ts
  • src/lib/data/stores/audio.js
  • src/lib/data/stores/interface.js
  • src/lib/data/stores/localization.js
  • src/lib/data/stores/scripture.js
  • src/lib/data/stores/setting.ts
  • src/lib/data/stores/theme.js
  • src/routes/plans/[id]/+page.js
  • src/routes/plans/[id]/settings/+page.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/lib/data/navigation.ts
  • src/lib/components/HistoryCard.svelte
  • src/lib/data/stores/setting.ts

Comment thread src/lib/data/highlights.ts
Comment thread src/lib/data/stores/localization.js
Comment thread src/lib/data/stores/scripture.js
@FyreByrd FyreByrd requested a review from eomerdws May 14, 2026 18:33
@FyreByrd FyreByrd changed the title Convert config to TS Convert generated config objects to TS (i.e. config.js, firebase-config.js, and contents.js) May 14, 2026
@FyreByrd FyreByrd changed the title Convert generated config objects to TS (i.e. config.js, firebase-config.js, and contents.js) Convert generated config objects to TS May 14, 2026
Copy link
Copy Markdown
Contributor

@eomerdws eomerdws left a comment

Choose a reason for hiding this comment

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

Looking through this PR it all looks straight forward. I couldn't find anything that failed building or testing several apps, nor could I find any glaring code issues that code rabbit hadn't already brought up.

Looks good.

@FyreByrd FyreByrd merged commit 42c744a into main May 18, 2026
4 checks passed
@FyreByrd FyreByrd deleted the refactor/type-config branch May 18, 2026 20:10
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.

2 participants