Skip to content

refactor(data): replace setVersion with loadData and establish fetch boundary#153

Merged
ushkinaz merged 12 commits intonextfrom
refactor/data-load
Apr 6, 2026
Merged

refactor(data): replace setVersion with loadData and establish fetch boundary#153
ushkinaz merged 12 commits intonextfrom
refactor/data-load

Conversation

@ushkinaz
Copy link
Copy Markdown
Owner

@ushkinaz ushkinaz commented Apr 6, 2026

Summary

This PR refactors game-data loading around a clearer architectural boundary.

The core change is the one described in ADR-007: src/data-loader.ts now owns raw transport and fetch orchestration, while src/data.ts keeps domain assembly, locale fallback, mod parsing/merging, cancellation, and store publication.

Along the way, this branch also:

  • replaces data.setVersion() with data.loadData()
  • eagerly loads complete mod metadata during initialization
  • renames the shared runtime gettext layer to src/i18n/game-locale.ts
  • updates tests and architecture/development docs to match the new shape

Why

Before this refactor, src/data.ts was carrying too many responsibilities at once:

  • HTTP transport and retry behavior
  • orchestration of all.json, locale, pinyin, and mod-catalog fetches
  • domain assembly into CBNData
  • Svelte store lifecycle and stale-load protection

That made the main loading path harder to reason about than the problem really is.

ADR-007 captures the simpler truth:

  • all.json is the only required asset
  • locale, pinyin, and all_mods.json are optional enrichments
  • static CDN-hosted JSON gains little from retry loops
  • progress only really needs to reflect the dominant all.json download

What Changed

Data-loading architecture

  • Replaced data.setVersion(...) with data.loadData(version, locale, activeMods).
  • Added loadRawDataset(...) as the single public entry point in src/data-loader.ts.
  • loadRawDataset(...) now fetches all.json, locale JSON, zh pinyin JSON, and all_mods.json in parallel via Promise.allSettled.
  • src/data.ts now consumes the settled raw asset bundle and remains responsible for:
    • locale fallback to English
    • parsing/validating all_mods.json
    • filtering and merging active mods
    • generation-token cancellation
    • publishing the final immutable CBNData

Loading behavior

  • Removed retry logic from the game-data loading path.
  • Simplified progress reporting to track only all.json.
  • Swallowed fetch failures for optional assets in the loader by degrading them to undefined.

Mod metadata flow

  • Mod metadata is now loaded during dataset initialization rather than through a later lazy path.
  • Removed the old lazy ensureModsLoaded() path.
  • ModSelector now derives its catalog directly from rawModsJson and no longer has a separate loading/error state.

Locale/runtime cleanup

  • Renamed src/i18n/gettext.ts to src/i18n/game-locale.ts to make its role explicit.
  • Centralized shared locale defaults in src/constants.ts.
  • Repaired Node/script TypeScript config and imports so shared runtime translation code remains safe to use from scripts.

Docs and tests

  • Added loader-focused tests in src/data-loader.test.ts.
  • Updated data/mod tests to match the new contracts.
  • Added ADR-007 and updated docs/architecture.md and DEVELOPMENT.md.

What's Improved

  • The data-loading boundary is clearer: transport lives in one place, domain logic in another.
  • loadData() is materially shorter and easier to reason about.
  • Optional asset degradation is now consistent across locale, pinyin, and mod metadata fetches.
  • The mod selector has its metadata ready as soon as the dataset is ready, instead of depending on a second loading path.
  • Locale resolution is more explicit and easier to test.
  • The docs now reflect the actual architecture rather than an older shape of the code.

Compromises / Trade-offs

  • Every dataset load now fetches all_mods.json, even if the user never opens the mod selector or activates mods.
  • Optional mod-catalog fetch failures now degrade quietly to an empty catalog instead of surfacing most transport errors.
  • Promise.allSettled can start optional requests that later turn out not to matter if all.json fails.
  • The progress indicator is less "complete" in theory because it no longer aggregates all sub-requests, but more honest in practice because all.json dominates perceived load time.

Behavior Changes

User-visible

  • The app now has mod metadata available immediately after the main dataset load completes.
  • The mod selector no longer shows its own loading/error state; if the optional mod catalog could not be fetched, it simply opens with no available mods.
  • Locale selection is normalized through available build languages, so requests like uk_UA can resolve to uk when that is the real available locale.
  • If all.json fails, the app fails immediately instead of waiting through retry attempts.

Internal / reviewer-relevant

  • data.setVersion() was removed in favor of data.loadData().
  • data.ensureModsLoaded() was removed; lazy mod metadata loading no longer exists.
  • src/i18n/gettext.ts was renamed to src/i18n/game-locale.ts.
  • CBNData now consistently carries activeMods / rawModsJSON data from initial load rather than from a later enrichment step.

Reviewer Notes / Potential Triggers

Removed lazy mod loading

This is intentional.

Previously, mod metadata could arrive later through ensureModsLoaded(), which created a second loading model for the same screen and forced ModSelector to carry loading/error UI states. This branch pays a small extra request up front to make the data model simpler and the selector deterministic.

Removed retries from game-data loading

This is also intentional and follows ADR-007.

The relevant assets are static CDN-hosted JSON files. Retrying them in-app mostly adds delay and duplicate traffic rather than meaningful resilience. The new contract is:

  • required asset (all.json) fails fast
  • optional assets degrade gracefully

Optional mod fetch errors are now quieter

This is the main semantic compromise in the branch.

Fetch failures for all_mods.json no longer block the main dataset load, even for non-404 transport errors. A successful but invalid all_mods.json payload still throws during parsing, so we are not masking schema corruption after a successful response.

Critical Path For Manual Testing

  1. Load the app in English on a normal build and confirm the dataset loads, the app renders, and the progress UI behaves sensibly.
  2. Open the mod selector immediately after load and confirm the catalog is present without an in-dialog loading spinner.
  3. Use Default, manual selection, Reset, and Apply in the mod selector and confirm the URL and selected mod set stay consistent after reload.
  4. Switch to a non-English locale such as uk and confirm translations apply.
  5. Verify locale normalization with a regional locale that should resolve to a base language when only the base is available.
  6. Switch to zh_CN and confirm the pinyin supplement path does not break loading.
  7. Activate a content mod and confirm modded objects are present in search/detail pages.
  8. Change version / locale / mods quickly during loading and confirm a stale request does not replace the newer dataset.
  9. If possible via devtools or a stubbed environment, force all_mods.json to fail and confirm the main app still loads while mod selection gracefully degrades.

Verification

  • Added dedicated loader tests for parallel fetch orchestration and optional-asset degradation.
  • Updated data/mod tests to reflect the new contracts.
  • Documentation updated to match the implemented architecture.
  • I did not run checks as part of this PR creation pass.

ushkinaz added 5 commits April 6, 2026 16:34
Move DEFAULT_LOCALE into shared constants and update app modules to
import it from one place

Adjust fetch-icons, gen-sitemap, and bench-node to use the current
CBNData constructor
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 387aeaea-08d0-4fa0-8115-2573dc46424b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/data-load

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.

@ushkinaz ushkinaz marked this pull request as ready for review April 6, 2026 18:09
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: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/adr/ADR-007_data_loading_orchestration.md`:
- Line 85: The ADR's claim that the public data.loadData() API does not change
is incorrect; update the document to state that data.setVersion(...) was
replaced by a new signature data.loadData(version, locale, activeMods), and
describe the new parameters and caller responsibilities (version, locale,
activeMods) and any behavioral differences from the old setVersion flow;
reference the symbols data.setVersion and data.loadData in the correction so
readers can map the change.

In `@src/data-loader.ts`:
- Around line 163-170: The returned dataJson is being asserted to
LoadedRawDataset["dataJson"] without runtime validation, which can mask missing
fields (e.g., raw.dataJson.build_number) and cause downstream failures; update
the fetch completion code (use dataResult/dataJson in src/data-loader.ts) to
perform explicit runtime schema validation (for example using zod/AJV or a
custom validator) on dataResult.value and only then assign/return it as
LoadedRawDataset["dataJson"]; alternatively, if you prefer not to add a schema
library, add explicit existence/type guards for required fields (at least data
and build_number) before the type assertion and document that callers rely on
validated data so accesses like raw.dataJson.build_number are safe.

In `@src/data.mods.test.ts`:
- Line 38: The test uses await expect(data.loadData(...)).resolves.not.toThrow()
which misapplies a function-execution matcher to an already-unwrapped promise
result; replace each occurrence of .resolves.not.toThrow() for loadData(...)
with an explicit assertion such as await expect(data.loadData("latest", "en",
[])).resolves.toBe(true) (or simply await data.loadData("latest", "en", []) if
you only want to ensure it doesn't reject) and update the other three instances
(the same pattern at the other failing assertions) so they assert the resolved
boolean value rather than using not.toThrow().

In `@src/data.ts`:
- Around line 2449-2500: Wrap the existing try/finally block with a
try/catch/finally (or insert a catch between the existing try and finally) that
captures any thrown errors from loadRawDataset, parseModsJson, the CBNData
constructor, etc., by calling Sentry.captureException(err) before re-throwing
the error; keep the existing finally behavior (the generationToken check and
loadProgressStore.set(null)) unchanged and re-throw the caught error so original
fail-fast behavior is preserved.
- Around line 303-312: The catch block in the CBNData constructor that handles
failures from applyLocaleJson currently logs to console and falls back to
resetI18n("en") but does not report the error to telemetry; update the catch to
call Sentry.captureException(e) (or the project's Sentry wrapper) in addition to
the console.warn and fallback so the failure is recorded, keeping the existing
resetI18n("en") and this.locale = "en" behavior; locate the code around the
CBNData constructor where applyLocaleJson(...) is invoked and add the
Sentry.captureException call inside that catch clause.

In `@src/i18n/game-locale.ts`:
- Around line 85-88: The function signature for applyLocaleJson has a redundant
union type for pinyinJSON; change the parameter type from "unknown | null" to
just "unknown" in the applyLocaleJson declaration so pinyinJSON uses a single
unknown type (update any matching overloads or callers if necessary), keeping
the function name applyLocaleJson as the locator for the change.

In `@src/ModSelector.svelte`:
- Around line 50-55: The current assignment to mods uses an unsafe cast `as
ModInfo[]` which can hide null/undefined if a rawModsJson entry has a missing or
malformed info; remove the cast and instead add a runtime type guard that
filters out null/undefined and narrows the type to ModInfo (e.g. change the
pipeline that reads Object.values(rawModsJson).map(({ info }) => info) so you
call .filter((info): info is ModInfo => info != null && typeof info ===
'object') and then apply the existing `.filter((modInfo) => !modInfo.core)` or
combine both checks; ensure the variable `mods` (and any use of `$derived`) is
typed from that filtered result rather than using `as ModInfo[]`.

In `@src/tile-data.test.ts`:
- Around line 56-57: The test fixture `fakeData` still uses old snake_case keys
(e.g., active_mods, raw_mods_json) causing invalid CBNData defaults after you
renamed fields; update the fixture and all other occurrences (e.g., the
instances around the other reported spots) to use the new camelCase keys
`activeMods` and `rawModsJSON` so the baseline data matches the renamed model
and tests exercise real behavior. Locate and replace the legacy keys inside the
`fakeData` object and any other test fixtures or inline objects in this file
(and the other listed occurrences) to ensure the test data shape matches the
`CBNData`/model fields. Ensure no other snake_case variants remain in this
file's fixtures so overrides using `activeMods`/`rawModsJSON` are trusted.

In `@tsconfig.node.json`:
- Line 4: Remove the redundant "noEmit": true setting from tsconfig.node.json
since it is already inherited from the base tsconfig; edit tsconfig.node.json to
delete the "noEmit" option so the file relies on the base configuration and
avoids duplicate compiler option declarations.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3aeadf1c-647f-45b7-8e46-ac8bc5d27113

📥 Commits

Reviewing files that changed from the base of the PR and between e96dfeb and 438fc06.

📒 Files selected for processing (102)
  • .github/workflows/ci.yml
  • AGENTS.md
  • DEVELOPMENT.md
  • docs/adr/ADR-007_data_loading_orchestration.md
  • docs/adr/README.md
  • docs/architecture.md
  • scripts/bench-node.ts
  • scripts/fetch-icons.ts
  • scripts/gen-sitemap.ts
  • src/App.svelte
  • src/Catalog.svelte
  • src/ModSelector.svelte
  • src/ModSelector.test.ts
  • src/PageMeta.svelte
  • src/PageMeta.test.ts
  • src/RenderErrorFallback.svelte
  • src/Thing.svelte
  • src/constants.ts
  • src/data-loader.test.ts
  • src/data-loader.ts
  • src/data.flatten.test.ts
  • src/data.mods.test.ts
  • src/data.test-helpers.ts
  • src/data.test.ts
  • src/data.ts
  • src/i18n/game-locale.ts
  • src/i18n/transifex-static.ts
  • src/i18n/ui-locale.ts
  • src/mod-provenance.test.ts
  • src/mods.test.ts
  • src/monster-policy.test.ts
  • src/navigation.svelte.ts
  • src/routing.svelte.ts
  • src/routing.test.ts
  • src/schema.test.ts
  • src/search-engine.ts
  • src/search-state.test.ts
  • src/search.test.ts
  • src/testRender.ts
  • src/testRenderMods.ts
  • src/tile-data.test.ts
  • src/tile-data.ts
  • src/types/Achievement.svelte
  • src/types/AmmunitionType.svelte
  • src/types/Bionic.svelte
  • src/types/BonusContainer.svelte
  • src/types/Construction.svelte
  • src/types/Construction.test.ts
  • src/types/ConstructionGroup.svelte
  • src/types/Fault.svelte
  • src/types/Flag.svelte
  • src/types/Furniture.svelte
  • src/types/Furniture.test.ts
  • src/types/Item.svelte
  • src/types/ItemAction.svelte
  • src/types/ItemLink.svelte
  • src/types/MartialArt.svelte
  • src/types/MartialArtRequirements.svelte
  • src/types/Material.svelte
  • src/types/Monster.svelte
  • src/types/Mutation.svelte
  • src/types/MutationCategory.svelte
  • src/types/MutationList.svelte
  • src/types/MutationType.svelte
  • src/types/OvermapSpecial.svelte
  • src/types/Recipe.svelte
  • src/types/Skill.svelte
  • src/types/Technique.svelte
  • src/types/Terrain.svelte
  • src/types/Terrain.test.ts
  • src/types/ToolQuality.svelte
  • src/types/Trap.test.ts
  • src/types/UsageDescription.svelte
  • src/types/Vehicle.svelte
  • src/types/VehiclePart.svelte
  • src/types/Vitamin.svelte
  • src/types/WeaponCategory.svelte
  • src/types/item/AmmoInfo.svelte
  • src/types/item/ArmorInfo.svelte
  • src/types/item/CompatibleItems.svelte
  • src/types/item/ComponentOf.svelte
  • src/types/item/ConstructionByproduct.svelte
  • src/types/item/DisarmTrap.test.ts
  • src/types/item/Disassembly.svelte
  • src/types/item/DissectedFrom.svelte
  • src/types/item/DissectedFrom.test.ts
  • src/types/item/GunAmmoInfo.svelte
  • src/types/item/GunInfo.svelte
  • src/types/item/HarvestedFrom.svelte
  • src/types/item/HarvestedTo.svelte
  • src/types/item/MagazineInfo.svelte
  • src/types/item/MeleeInfo.svelte
  • src/types/item/MilledFrom.svelte
  • src/types/item/OvermapAppearance.svelte
  • src/types/item/Recipes.svelte
  • src/types/item/RequirementDataTools.svelte
  • src/types/item/Salvaged.svelte
  • src/types/item/SmokedFrom.svelte
  • src/types/item/SpawnedIn.test.ts
  • src/types/item/spawnLocations.test.ts
  • tsconfig.json
  • tsconfig.node.json
💤 Files with no reviewable changes (2)
  • src/routing.test.ts
  • src/mod-provenance.test.ts

Comment thread docs/adr/ADR-007_data_loading_orchestration.md Outdated
Comment thread src/data-loader.ts
Comment thread src/data.mods.test.ts Outdated
Comment thread src/data.ts
Comment thread src/data.ts
Comment thread src/i18n/game-locale.ts Outdated
Comment thread src/ModSelector.svelte
Comment thread src/tile-data.test.ts
Comment thread tsconfig.node.json Outdated
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/data-loader.ts`:
- Around line 65-74: The onload handler's truthy check for xhr.response can
incorrectly reject valid falsy primitives (0, false, ""), so update the check in
the xhr.onload callback to accept any non-null/undefined response (e.g., use
xhr.response !== null && xhr.response !== undefined) before calling
resolve(xhr.response); keep the existing 404 and status-range logic and the
handleError("Status") path unchanged so only the falsy-value handling around
xhr.response is fixed.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5b6845a6-6427-4d0e-bedf-2ed8fc1d496b

📥 Commits

Reviewing files that changed from the base of the PR and between 438fc06 and 88001e6.

📒 Files selected for processing (12)
  • docs/adr/ADR-007_data_loading_orchestration.md
  • scripts/fetch-fixtures.ts
  • scripts/fetch-icons.ts
  • scripts/gen-sitemap.ts
  • src/constants.ts
  • src/data-loader.test.ts
  • src/data-loader.ts
  • src/data.mods.test.ts
  • src/data.ts
  • src/i18n/game-locale.ts
  • src/tile-data.test.ts
  • tsconfig.node.json

Comment thread src/data-loader.ts
@ushkinaz ushkinaz merged commit f9ff880 into next Apr 6, 2026
2 checks passed
@ushkinaz ushkinaz deleted the refactor/data-load branch April 6, 2026 20:45
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.

1 participant