Skip to content

Ram usage tests#155

Merged
XeIris merged 4 commits intomasterfrom
RamUsageTests
May 5, 2026
Merged

Ram usage tests#155
XeIris merged 4 commits intomasterfrom
RamUsageTests

Conversation

@XeIris
Copy link
Copy Markdown
Collaborator

@XeIris XeIris commented May 5, 2026

Summary by CodeRabbit

  • New Features

    • Expanded Genshin and HSR profile datasets with many additional avatars and namecards.
  • Improvements

    • Switched multiple commands to lazy/dynamic data loading for faster startup and lower memory use.
    • Updated media URL construction to use enka.network.
    • Updated Formula 1 standings parsing for improved reliability.
    • Simplified AI token estimation to a lightweight heuristic.
  • Bug Fixes

    • Fixed an execution flow issue to prevent continuing after invalid-year replies.
  • Chores

    • Removed deprecated local character dataset.

@XeIris XeIris self-assigned this May 5, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Warning

Rate limit exceeded

@XeIris has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 49 minutes and 22 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dc83c5c4-f4b7-4958-b21d-56bb7ca7e52a

📥 Commits

Reviewing files that changed from the base of the PR and between 9353b0e and 74a6e8b.

📒 Files selected for processing (3)
  • commands/gacha.ts
  • commands/genshinProfile.ts
  • commands/hsrProfile.ts
📝 Walkthrough

Walkthrough

Multiple command modules switch from static JSON imports to lazy runtime loading via Bun.file().json() with module-level caching. The f1Standings parser is rewritten to use cheerio instead of jsdom. Token counting is simplified to a character-length heuristic. Several Genshin/HSR data files had schema and content updates.

Changes

Runtime data loading, parser migration, and tokenizer simplification

Layer / File(s) Summary
Dependency & manifest
package.json
Added cheerio; removed jsdom, gpt-tokenizer, xml2js and related type packages.
Data Shape / Dataset updates
data/genshinPfps.json, data/genshinNamecards.json, data/hsrAvartars.json, data/characters.json
Genshin namecard/profile schemas renamed (iconIcon, iconPathIconPath) and icon values reformatted to /ui/... paths; many new entries appended; hsrAvartars.json extended; data/characters.json content removed.
Tokenizer heuristic
utils/tokenizer.ts, utils/ai.ts
Token estimation replaced with a char-based heuristic (Math.ceil(text.length/4)); ai token-count callsite updated to use countTokensOpenRouterMessages.
Parser migration (HTML parsing)
commands/f1Standings.ts
extractStandings rewritten to use cheerio.load and CSS selectors (#results-table tbody tr / td); added safeParseInt; run now early-returns after editReply on invalid year.
Runtime JSON loading helpers
commands/gacha.ts, commands/genshinProfile.ts, commands/hsrProfile.ts, commands/nword.ts, commands/spotifyPlaylist.ts
Replaced static JSON imports with async module-level loaders (e.g., getPools(), loadGenshinData(), loadHsrData(), getNwords(), getMusicLinks()) that use Bun.file(...).json() and cache results; constructors/run methods updated to await and populate instance fields.
Command-level wiring & minor logic
commands/gacha.ts, commands/genshinProfile.ts, commands/hsrProfile.ts, commands/nword.ts, commands/spotifyPlaylist.ts, commands/f1Standings.ts
Image URL construction updated to https://enka.network${path}; gacha item type now uses rollResult?.AvatarName; component customId checks updated (nextRoll / skipResults); commands now await cached loaders before use.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hopped through JSON, left imports behind,
Cheerio nibble—rows parsed, neat and kind,
Tokens by characters, a simple delight,
Cached files snug in memory all night,
Hooray for small hops that make code light!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title 'Ram usage tests' does not accurately reflect the actual changes. The PR makes extensive modifications to data loading patterns (switching from static imports to runtime lazy loading), replaces dependencies (jsdom/xml2js with cheerio), updates data schemas in JSON files, and refactors token estimation logic. These changes have no clear connection to RAM usage testing. Update the title to accurately describe the main changes, such as 'Refactor to lazy-load JSON data and replace jsdom with cheerio' or 'Optimize data loading with runtime caching and dependency updates'.
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 (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch RamUsageTests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@XeIris XeIris linked an issue May 5, 2026 that may be closed by this pull request
Copy link
Copy Markdown

@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

Caution

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

⚠️ Outside diff range comments (3)
commands/gacha.ts (2)

73-80: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Early return makes the entire roll flow — including the new lazy-load — unreachable.

run() always replies with "gacha is not ready yet" and returns at line 75, so getPools() (line 77) and everything below it (DB reads/writes, embed/button rendering, collector) is dead code, suppressed only by /* eslint-disable no-unreachable */ at line 1. Two consequences:

  1. The lazy-loading change in this PR is not actually exercised for gacha — it does not save RAM here because it never runs, and it also never gets validated.
  2. When the early return is eventually removed, several latent bugs become live (see follow-up comment about customId mismatch, and this.characterPool.some((c) => c.name === ...) at line 129 which references a non-existent field on the pool entries — AvatarName.Hash is what they expose).

If gacha is intentionally disabled, consider either gating it at the command-registration level or short-circuiting before reaching code that you intend to keep dormant, and dropping the eslint-disable so future regressions are caught.

🤖 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 `@commands/gacha.ts` around lines 73 - 80, The run() method early-returns
before the new lazy-load and entire roll flow run; remove or relocate the "gacha
is not ready yet" early return so getPools() executes (or gate-disable the
command at registration time instead of returning inside run()), and drop the
top-level "eslint-disable no-unreachable" so unreachable code is detected;
ensure the lazy-load populates this.namesData, this.characterPool, and
this.lightconePool by calling getPools() before any DB/read/write or collector
logic, and fix all field accesses that assume character entries have a "name"
property (e.g., code using this.characterPool.some(c => c.name === ...)) to use
the actual AvatarName.Hash (or the correct property exposed by getPools()), and
verify any customId comparisons match the IDs generated elsewhere.

149-194: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

customId mismatch — buttons will be inert once gacha is enabled.

Buttons are registered with nextRoll (lines 149, 172) and skipResults (lines 154, 176), but the collector at lines 186 and 191 compares against next_roll and skip_results. Neither branch will ever fire, so "Next" and "Skip to Results" do nothing and the collector only ends on timeout.

🛠 Proposed fix
-          new ButtonBuilder()
-            .setCustomId('nextRoll')
+          new ButtonBuilder()
+            .setCustomId('next_roll')
             .setLabel('➡️ Next')
             .setStyle(ButtonStyle.Primary)
             .setDisabled(currentIndex === results.length - 1),
           new ButtonBuilder()
-            .setCustomId('skipResults')
+            .setCustomId('skip_results')
             .setLabel('Skip to Results')
             .setStyle(ButtonStyle.Danger),

Apply the same rename to the initialRow builders at lines 172 and 176.

🤖 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 `@commands/gacha.ts` around lines 149 - 194, The collector's i.customId checks
use snake_case ('next_roll', 'skip_results') but the buttons are created with
camelCase IDs ('nextRoll', 'skipResults'), so the branches never run; fix by
making the IDs consistent — either change the setCustomId calls on the
ButtonBuilder instances (currently 'nextRoll' and 'skipResults') to 'next_roll'
and 'skip_results' or update the collector logic to check for 'nextRoll' and
'skipResults' (references: the ButtonBuilder.setCustomId entries and the
collector.on('collect', async (i) => { if (i.customId === 'next_roll' ...) } )).
Ensure both the initialRow and updateMessage button builders use the exact same
IDs the collector expects.
commands/f1Standings.ts (1)

112-117: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing return after the invalid-year reply causes a double editReply.

After calling interaction.editReply(...) with the validation error, execution falls through to the fetch call at line 123. If the request succeeds, interaction.editReply is called a second time at line 128. Discord.js throws on the second editReply, producing an unhandled rejection and a broken UX.

🐛 Proposed fix
     if (year > currentYear || year < minYear) {
-      interaction.editReply({
+      await interaction.editReply({
         content: `Invalid year for ${type} standings. Must be between ${minYear} and ${currentYear}.`,
         ephemeral: true,
       });
+      return;
     }
🤖 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 `@commands/f1Standings.ts` around lines 112 - 117, The invalid-year branch in
the interaction handler calls interaction.editReply(...) but does not stop
execution, allowing fetch and a second interaction.editReply(...) later; update
the validation in the handler (the block that checks "if (year > currentYear ||
year < minYear)" using variables year, currentYear, minYear, and type) to return
immediately after sending the ephemeral error reply so the subsequent fetch and
the later interaction.editReply are not executed.
🧹 Nitpick comments (3)
commands/hsrProfile.ts (1)

46-48: 💤 Low value

Optional: move await loadHsrData() inside the try block.

If any of the four JSON reads ever fail (corruption, missing file, permission), the rejection will bypass the existing catch at line 133-136 and surface as an unhandled rejection on the interaction. Moving the call inside try would route the failure through the same user-facing error reply path used for fetch failures.

🤖 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 `@commands/hsrProfile.ts` around lines 46 - 48, Move the await loadHsrData()
call inside the existing try block so any rejection from reading avatarData,
characterData, namesData, or lightconeData is handled by the existing catch that
sends the user-facing error reply; specifically, replace the top-level const {
avatarData, characterData, namesData, lightconeData } = await loadHsrData() with
the same await call executed inside the try that surrounds the fetch/processing
logic so failures from loadHsrData() flow into the same catch path.
commands/genshinProfile.ts (1)

37-37: 💤 Low value

Optional: move await loadGenshinData() inside the try block.

If Bun.file(...).json() rejects (file missing/corrupt), the existing catch at line 87-90 is bypassed and the user gets no reply. Moving line 37 below line 39 routes load failures through the same user-facing fallback path used for upstream API errors.

🤖 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 `@commands/genshinProfile.ts` at line 37, The call to loadGenshinData() is
currently awaited outside the try/catch so filesystem errors escape the catch;
move the await into the existing try block so failures are handled by the same
user-facing fallback. Concretely, declare variables for profilePictures and
namecards (e.g. let profilePictures, namecards;) before the try, then perform
const { profilePictures: pp, namecards: nc } = await loadGenshinData() or assign
directly inside the try (or use const inside the try) and use those variables
afterward; update any renamed bindings accordingly so scope/usage remains
correct and no unhandled rejections occur. Ensure the change references the
loadGenshinData() call and the surrounding try/catch that currently handles
upstream API errors.
utils/ai.ts (1)

129-133: ⚡ Quick win

Inline estimation duplicates countTokensOpenRouterMessages from tokenizer.ts.

The loop on lines 130–132 is byte-for-byte equivalent to the exported countTokensOpenRouterMessages(requestMessages). If the formula is updated in tokenizer.ts again (as it was in this PR), this copy in ai.ts will silently diverge.

♻️ Suggested refactor

Add the import at the top of ai.ts:

+import { countTokensOpenRouterMessages } from './tokenizer';

Then replace the estimation loop:

-      let estimated = 0;
-      for (const m of requestMessages) {
-        estimated += Math.ceil(m.content.length / 4) + 4;
-      }
+      const estimated = countTokensOpenRouterMessages(requestMessages);
       recordUsage(model, estimated, actualPromptTokens);
🤖 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 `@utils/ai.ts` around lines 129 - 133, The token-estimation loop in ai.ts
duplicates logic from tokenizer.ts (countTokensOpenRouterMessages); import
countTokensOpenRouterMessages into ai.ts and replace the manual loop (the
estimated calculation using Math.ceil over requestMessages) with a call like
const estimated = countTokensOpenRouterMessages(requestMessages), then pass that
estimated value into recordUsage(model, estimated, actualPromptTokens); remove
the now-unused manual loop/variable to avoid divergence when the token formula
changes.
🤖 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 `@commands/f1Standings.ts`:
- Around line 44-48: The parsing of numeric cells uses parseInt which can return
NaN for non-numeric text (e.g. "–"), so update the parsing logic in
commands/f1Standings.ts (the lines setting position and points and the analogous
team parsing at lines referenced in the review) to coerce NaN to 0;
specifically, replace direct parseInt(...) uses with a safe parse that checks
Number.isNaN(result) and falls back to 0 (apply the same pattern for position,
points and the team parsing block), so buildEmbed never receives NaN values.

---

Outside diff comments:
In `@commands/f1Standings.ts`:
- Around line 112-117: The invalid-year branch in the interaction handler calls
interaction.editReply(...) but does not stop execution, allowing fetch and a
second interaction.editReply(...) later; update the validation in the handler
(the block that checks "if (year > currentYear || year < minYear)" using
variables year, currentYear, minYear, and type) to return immediately after
sending the ephemeral error reply so the subsequent fetch and the later
interaction.editReply are not executed.

In `@commands/gacha.ts`:
- Around line 73-80: The run() method early-returns before the new lazy-load and
entire roll flow run; remove or relocate the "gacha is not ready yet" early
return so getPools() executes (or gate-disable the command at registration time
instead of returning inside run()), and drop the top-level "eslint-disable
no-unreachable" so unreachable code is detected; ensure the lazy-load populates
this.namesData, this.characterPool, and this.lightconePool by calling getPools()
before any DB/read/write or collector logic, and fix all field accesses that
assume character entries have a "name" property (e.g., code using
this.characterPool.some(c => c.name === ...)) to use the actual AvatarName.Hash
(or the correct property exposed by getPools()), and verify any customId
comparisons match the IDs generated elsewhere.
- Around line 149-194: The collector's i.customId checks use snake_case
('next_roll', 'skip_results') but the buttons are created with camelCase IDs
('nextRoll', 'skipResults'), so the branches never run; fix by making the IDs
consistent — either change the setCustomId calls on the ButtonBuilder instances
(currently 'nextRoll' and 'skipResults') to 'next_roll' and 'skip_results' or
update the collector logic to check for 'nextRoll' and 'skipResults'
(references: the ButtonBuilder.setCustomId entries and the
collector.on('collect', async (i) => { if (i.customId === 'next_roll' ...) } )).
Ensure both the initialRow and updateMessage button builders use the exact same
IDs the collector expects.

---

Nitpick comments:
In `@commands/genshinProfile.ts`:
- Line 37: The call to loadGenshinData() is currently awaited outside the
try/catch so filesystem errors escape the catch; move the await into the
existing try block so failures are handled by the same user-facing fallback.
Concretely, declare variables for profilePictures and namecards (e.g. let
profilePictures, namecards;) before the try, then perform const {
profilePictures: pp, namecards: nc } = await loadGenshinData() or assign
directly inside the try (or use const inside the try) and use those variables
afterward; update any renamed bindings accordingly so scope/usage remains
correct and no unhandled rejections occur. Ensure the change references the
loadGenshinData() call and the surrounding try/catch that currently handles
upstream API errors.

In `@commands/hsrProfile.ts`:
- Around line 46-48: Move the await loadHsrData() call inside the existing try
block so any rejection from reading avatarData, characterData, namesData, or
lightconeData is handled by the existing catch that sends the user-facing error
reply; specifically, replace the top-level const { avatarData, characterData,
namesData, lightconeData } = await loadHsrData() with the same await call
executed inside the try that surrounds the fetch/processing logic so failures
from loadHsrData() flow into the same catch path.

In `@utils/ai.ts`:
- Around line 129-133: The token-estimation loop in ai.ts duplicates logic from
tokenizer.ts (countTokensOpenRouterMessages); import
countTokensOpenRouterMessages into ai.ts and replace the manual loop (the
estimated calculation using Math.ceil over requestMessages) with a call like
const estimated = countTokensOpenRouterMessages(requestMessages), then pass that
estimated value into recordUsage(model, estimated, actualPromptTokens); remove
the now-unused manual loop/variable to avoid divergence when the token formula
changes.
🪄 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: CHILL

Plan: Pro

Run ID: 06b0557e-f3d6-483a-9a04-8c9fdf90772c

📥 Commits

Reviewing files that changed from the base of the PR and between d2d4b3a and ed51b33.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • commands/f1Standings.ts
  • commands/gacha.ts
  • commands/genshinProfile.ts
  • commands/hsrProfile.ts
  • commands/nword.ts
  • commands/spotifyPlaylist.ts
  • data/characters.json
  • data/genshinNamecards.json
  • data/genshinPfps.json
  • data/hsr.json
  • data/hsrAvartars.json
  • data/hsrCharacters.json
  • data/hsrLC.json
  • data/nwords.json
  • data/words_dictionary.json
  • package.json
  • utils/ai.ts
  • utils/tokenizer.ts
💤 Files with no reviewable changes (1)
  • data/characters.json

Comment thread commands/f1Standings.ts Outdated
f1Standings.ts: added safeParseInt helper, replaced parseInt calls in driver/team rows, added await + return to invalid-year branch.
gacha.ts: removed eslint-disable no-unreachable and the early-return so getPools() populates fields; replaced the broken c.name check with rollResult?.AvatarName discriminator; aligned collector customIds (nextRoll/skipResults) with the button IDs.
genshinProfile.ts & hsrProfile.ts: moved loadGenshinData()/loadHsrData() calls inside the existing try blocks so file-read failures hit the existing user-facing fallback.
ai.ts: imported countTokensOpenRouterMessages from tokenizer.ts and replaced the duplicated manual loop with a single call.
Copy link
Copy Markdown

@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.

Caution

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

⚠️ Outside diff range comments (1)
commands/gacha.ts (1)

57-69: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

imagePath key in the early return should be imageUrl after the rename.

Line 67 was updated to return imageUrl, but the early-return guard on line 58 still returns imagePath: null. Any caller that does item.imageUrl (lines 138, 161) will receive undefined instead of null when getItemDetails is called with a falsy item (e.g., an empty pool).

🐛 Proposed fix
-    if (!item) return { name: 'Unknown', imagePath: null, rarity: 'Unknown' };
+    if (!item) return { name: 'Unknown', imageUrl: null, rarity: 'Unknown' };
🤖 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 `@commands/gacha.ts` around lines 57 - 69, In getItemDetails, the early-return
object still uses the old key imagePath; change that to imageUrl so it matches
the rest of the function and callers (e.g., where item.imageUrl is accessed).
Update the guard return from { name: 'Unknown', imagePath: null, rarity:
'Unknown' } to use imageUrl: null while keeping name and rarity the same so
callers receive a consistent shape.
🧹 Nitpick comments (1)
commands/hsrProfile.ts (1)

12-25: ⚡ Quick win

Cache the in-flight Promise to prevent concurrent duplicate file reads.

Because loadHsrData stores only the resolved value, two concurrent calls can both pass if (!dataCache) before either Promise.all resolves, causing the four JSON files to be read twice. Caching the promise itself closes the race:

♻️ Proposed refactor
-let dataCache: {
-  avatarData: any;
-  characterData: any;
-  namesData: any;
-  lightconeData: any;
-} | null = null;
-
-async function loadHsrData() {
-  if (!dataCache) {
-    const [avatarData, characterData, namesData, lightconeData] = await Promise.all([
-      Bun.file(path.join(__dirname, '../data/hsrAvartars.json')).json(),
-      Bun.file(path.join(__dirname, '../data/hsrCharacters.json')).json(),
-      Bun.file(path.join(__dirname, '../data/hsr.json')).json(),
-      Bun.file(path.join(__dirname, '../data/hsrLC.json')).json(),
-    ]);
-    dataCache = {
-      avatarData, characterData, namesData, lightconeData,
-    };
-  }
-  return dataCache!;
-}
+let dataCachePromise: Promise<{
+  avatarData: any;
+  characterData: any;
+  namesData: any;
+  lightconeData: any;
+}> | null = null;
+
+function loadHsrData() {
+  if (!dataCachePromise) {
+    dataCachePromise = Promise.all([
+      Bun.file(path.join(__dirname, '../data/hsrAvartars.json')).json(),
+      Bun.file(path.join(__dirname, '../data/hsrCharacters.json')).json(),
+      Bun.file(path.join(__dirname, '../data/hsr.json')).json(),
+      Bun.file(path.join(__dirname, '../data/hsrLC.json')).json(),
+    ]).then(([avatarData, characterData, namesData, lightconeData]) => ({
+      avatarData, characterData, namesData, lightconeData,
+    }));
+  }
+  return dataCachePromise;
+}

The same pattern exists in genshinProfile.ts (loadGenshinData) and gacha.ts (getPools) and should receive the same fix.

🤖 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 `@commands/hsrProfile.ts` around lines 12 - 25, loadHsrData currently only
caches the resolved value so concurrent calls race and duplicate file reads;
change it to cache the in-flight Promise (e.g., add a module-level
dataCachePromise alongside dataCache), assign dataCachePromise =
Promise.all([...]).then(results => { set dataCache from results;
dataCachePromise = null; return dataCache; }) before awaiting, and return await
dataCachePromise; apply the same pattern to loadGenshinData and getPools so
simultaneous callers share the same Promise and only one set of file reads
occurs.
🤖 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 `@commands/gacha.ts`:
- Around line 57-69: In getItemDetails, the early-return object still uses the
old key imagePath; change that to imageUrl so it matches the rest of the
function and callers (e.g., where item.imageUrl is accessed). Update the guard
return from { name: 'Unknown', imagePath: null, rarity: 'Unknown' } to use
imageUrl: null while keeping name and rarity the same so callers receive a
consistent shape.

---

Nitpick comments:
In `@commands/hsrProfile.ts`:
- Around line 12-25: loadHsrData currently only caches the resolved value so
concurrent calls race and duplicate file reads; change it to cache the in-flight
Promise (e.g., add a module-level dataCachePromise alongside dataCache), assign
dataCachePromise = Promise.all([...]).then(results => { set dataCache from
results; dataCachePromise = null; return dataCache; }) before awaiting, and
return await dataCachePromise; apply the same pattern to loadGenshinData and
getPools so simultaneous callers share the same Promise and only one set of file
reads occurs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 856898d1-4d6a-401c-9bc4-3d0b5249204c

📥 Commits

Reviewing files that changed from the base of the PR and between ed51b33 and 9353b0e.

📒 Files selected for processing (5)
  • commands/f1Standings.ts
  • commands/gacha.ts
  • commands/genshinProfile.ts
  • commands/hsrProfile.ts
  • utils/ai.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • commands/f1Standings.ts

commands/gacha.ts:58 — guard return now uses imageUrl: null to match the success-path shape.
commands/hsrProfile.ts:12-29, commands/genshinProfile.ts:5-21, commands/gacha.ts:7-28 — each loader now caches the in-flight Promise so concurrent callers share one set of file reads, clearing the promise once the cache is populated.
@XeIris XeIris merged commit 0dd636c into master May 5, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

silverwolf leaking 🥵 🥵 🥵

1 participant