Skip to content

feat: calculator for candles, sc and acs"#408

Open
imnaiyar wants to merge 6 commits intomainfrom
feat/calc
Open

feat: calculator for candles, sc and acs"#408
imnaiyar wants to merge 6 commits intomainfrom
feat/calc

Conversation

@imnaiyar
Copy link
Copy Markdown
Owner

This depends on unreleased modal comps, so will have to wait for that

@vercel
Copy link
Copy Markdown

vercel bot commented Jan 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
sky-helper-docs Ignored Ignored Preview Feb 15, 2026 0:47am
website-next Ignored Ignored Preview Feb 15, 2026 0:47am

@github-actions github-actions bot added package:skyhelper Changes made to skyhelper package package:utils Changes made to utils package labels Jan 23, 2026
@github-actions github-actions bot added the package:constants Changes made to constants package label Feb 11, 2026
@imnaiyar imnaiyar marked this pull request as ready for review February 15, 2026 12:57
@imnaiyar imnaiyar requested a review from imnyr as a code owner February 15, 2026 12:57
@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Add calculator command for candles with modal interface
✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add calculator command for regular, seasonal, and ascended candles
• Implement modal-based calculator with daily/weekly reward tracking
• Add shard utility methods for date range queries
• Update planner data schema with dailies/event/season checkin fields
• Pin discord-api-types to version 0.38.38 across packages
Diagram
flowchart LR
  CMD["Calculator Command"]
  MODAL["Modal Input<br/>current/target candles"]
  CALC["Calculate Days<br/>Needed"]
  RESULT["Display Results<br/>with scenarios"]
  
  CMD -->|launch| MODAL
  MODAL -->|submit| CALC
  CALC -->|process| RESULT
  
  SHARDS["Shard Utils<br/>getNextShard"]
  DATES["Date Utils<br/>getDatesBetween"]
  
  CALC -->|query| SHARDS
  CALC -->|iterate| DATES
Loading

Grey Divider

File Changes

1. packages/skyhelper/src/bot/handlers/calculator.ts ✨ Enhancement +223/-0

New calculator handler with candle calculations

packages/skyhelper/src/bot/handlers/calculator.ts


2. packages/skyhelper/src/bot/events/INTERACTION_CREATE.ts ✨ Enhancement +6/-0

Add calculator modal interaction handler

packages/skyhelper/src/bot/events/INTERACTION_CREATE.ts


3. packages/skyhelper/src/bot/modules/inputCommands/utility/calculator.ts ✨ Enhancement +152/-0

New calculator command with modal UI

packages/skyhelper/src/bot/modules/inputCommands/utility/calculator.ts


View more (10)
4. packages/skyhelper/src/bot/modules/commands-data/utility-commands.ts ✨ Enhancement +33/-0

Add calculator command data definition

packages/skyhelper/src/bot/modules/commands-data/utility-commands.ts


5. packages/skyhelper/src/bot/handlers/planner/helpers/data.service.ts ✨ Enhancement +18/-0

Add dailies checkin tracking and utility method

packages/skyhelper/src/bot/handlers/planner/helpers/data.service.ts


6. packages/utils/src/classes/shardsUtil.ts ✨ Enhancement +30/-8

Add getShard and getShardsBetween utility methods

packages/utils/src/classes/shardsUtil.ts


7. packages/utils/src/classes/utils.ts ✨ Enhancement +45/-31

Add getDatesBetween utility function

packages/utils/src/classes/utils.ts


8. packages/skyhelper/src/bot/handlers/registerCommands.ts Miscellaneous +2/-2

Add commands.json export for debugging

packages/skyhelper/src/bot/handlers/registerCommands.ts


9. package.json Dependencies +2/-1

Pin discord-api-types to 0.38.38

package.json


10. packages/constants/package.json Dependencies +1/-1

Pin discord-api-types to 0.38.38

packages/constants/package.json


11. packages/skyhelper/package.json Dependencies +1/-1

Pin discord-api-types to 0.38.38

packages/skyhelper/package.json


12. packages/utils/package.json Dependencies +0/-0

Pin discord-api-types to 0.38.38

packages/utils/package.json


13. pnpm-lock.yaml Dependencies +49/-45

Update lock file with pinned discord-api-types

pnpm-lock.yaml


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (5) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

1. sc case leaves component undefined 📘 Rule violation ⛯ Reliability
Description
The sc branch in handleCalculatorModal() does not assign component, but the code always uses
it in editReply(), which can cause a runtime failure for Seasonal Candle calculations. This
violates the requirement to handle edge cases and failure paths gracefully.
Code

packages/skyhelper/src/bot/handlers/calculator.ts[R81-86]

+    case "sc": {
+      break;
+    }
+  }
+  await helper.editReply({ components: [component], flags: MessageFlags.IsComponentsV2 });
+}
Evidence
PR Compliance ID 3 requires edge cases and failure points to be handled; here, selecting sc can
result in component being unset and still used in the response edit.

Rule 3: Generic: Robust Error Handling and Edge Case Management
packages/skyhelper/src/bot/handlers/calculator.ts[81-86]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`handleCalculatorModal()` can hit the `sc` switch branch without assigning `component`, but still calls `editReply({ components: [component] })`, which can crash or send an invalid response.

## Issue Context
The calculator command allows selecting Seasonal Candles (`sc`), and the modal custom id can include `type=&quot;sc&quot;`. The handler must either build a valid component for `sc` or exit early with a user-safe message.

## Fix Focus Areas
- packages/skyhelper/src/bot/handlers/calculator.ts[19-86]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Modal numbers parsed without validation 📘 Rule violation ⛨ Security
Description
User-provided modal text input values are converted with Number(...) and used without validating
NaN, non-integers, or negative/boundary values. This can produce incorrect outputs (e.g., NaN,
misleading day counts) instead of a clear, safe error response.
Code

packages/skyhelper/src/bot/handlers/calculator.ts[R15-26]

+  const [_, type = "c", guid = ""] = int.data.custom_id.split(";");
+  const current = client.utils.getModalComponent(int, "input_have", ComponentType.TextInput, true).value;
+  const checkboxes = client.utils.getModalComponent(int, "checkboxes", ComponentType.CheckboxGroupAction)?.values;
+
+  let component: APIContainerComponent;
+  switch (type) {
+    case "c": {
+      const target = client.utils.getModalComponent(int, "input_need", ComponentType.TextInput, true).value;
+
+      const { daysWithBase, daysWithBaseQuests, daysWithMax, daysWithMaxQuests } =
+        calculateCandles(Number(current), Number(target)) ?? {};
+
Evidence
PR Compliance ID 6 requires validation of external/user inputs, and PR Compliance ID 3 requires
explicit handling of null/empty/boundary cases; the new code converts raw text inputs to numbers and
uses them directly in calculations without checks.

Rule 6: Generic: Security-First Input Validation and Data Handling
Rule 3: Generic: Robust Error Handling and Edge Case Management
packages/skyhelper/src/bot/handlers/calculator.ts[15-26]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Modal text input values are treated as numbers via `Number(...)` without validating that they are finite, non-negative, and within expected ranges. Invalid inputs (empty string, non-numeric, negative, very large) can yield `NaN`/nonsensical results instead of a controlled error.

## Issue Context
These values come from Discord modal text inputs (user-controlled). Compliance requires explicit input validation and edge-case handling.

## Fix Focus Areas
- packages/skyhelper/src/bot/handlers/calculator.ts[15-26]
- packages/skyhelper/src/bot/handlers/calculator.ts[42-59]
- packages/skyhelper/src/bot/handlers/calculator.ts[88-102]
- packages/skyhelper/src/bot/handlers/calculator.ts[116-223]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Checkbox type mismatch 🐞 Bug ✓ Correctness
Description
The handler reads checkbox values using ComponentType.CheckboxGroupAction, but the modal is built
with ComponentType.CheckboxGroup; getModalComponent enforces exact type matching and will throw
if these differ.
Code

packages/skyhelper/src/bot/handlers/calculator.ts[R15-17]

+  const [_, type = "c", guid = ""] = int.data.custom_id.split(";");
+  const current = client.utils.getModalComponent(int, "input_have", ComponentType.TextInput, true).value;
+  const checkboxes = client.utils.getModalComponent(int, "checkboxes", ComponentType.CheckboxGroupAction)?.values;
Evidence
Modal submit parsing uses getModalComponent(..., type) which throws on a type mismatch. The modal
definition uses CheckboxGroup, while the handler expects CheckboxGroupAction for the same custom_id.

packages/skyhelper/src/bot/handlers/calculator.ts[15-17]
packages/skyhelper/src/bot/modules/inputCommands/utility/calculator.ts[71-79]
packages/skyhelper/src/bot/utils/classes/Utils.ts[214-227]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The calculator modal defines the checkboxes component as `ComponentType.CheckboxGroup`, but the submit handler tries to read it as `ComponentType.CheckboxGroupAction`. `getModalComponent` throws on mismatched types.

### Issue Context
`Utils.getModalComponent` checks `comp.type !== type` and throws when they differ.

### Fix Focus Areas
- packages/skyhelper/src/bot/handlers/calculator.ts[15-17]
- packages/skyhelper/src/bot/modules/inputCommands/utility/calculator.ts[71-79]
- packages/skyhelper/src/bot/utils/classes/Utils.ts[214-227]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (2)
4. Shard checkbox unreachable 🐞 Bug ✓ Correctness
Description
The AC modal’s “today’s shard” checkbox never appears because it checks shard.type, but
ShardsUtil.getShard() returns { info, timings } with the type nested under info.
Code

packages/skyhelper/src/bot/modules/inputCommands/utility/calculator.ts[R134-141]

+    const shard = ShardsUtil.getShard(DateTime.now().setZone(zone));
+    if (shard && shard.type === "red") {
+      checkboxes.push({
+        label: "Have you cleared today's shard?",
+        value: "today_shard",
+        default: PlannerDataService.shardsCleared(settings.plannerData),
+      });
+    }
Evidence
getShard returns an object without a top-level type field, so shard.type === "red" is always
false and the checkbox branch is skipped.

packages/skyhelper/src/bot/modules/inputCommands/utility/calculator.ts[132-141]
packages/utils/src/classes/shardsUtil.ts[131-138]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
AC checkbox rendering checks `shard.type`, but `ShardsUtil.getShard()` returns `{ info, timings }`. This makes the “today_shard” option unreachable.

### Issue Context
`type` lives under `shard.info.type`.

### Fix Focus Areas
- packages/skyhelper/src/bot/modules/inputCommands/utility/calculator.ts[132-141]
- packages/utils/src/classes/shardsUtil.ts[131-138]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Wrong dailies checkin 🐞 Bug ✓ Correctness
Description
The dailies checkbox default uses the wrong checkin type: regular candles (no season) query
season.checkin, while seasonal candles query dailies.checkin, so defaults will be incorrect.
Code

packages/skyhelper/src/bot/modules/inputCommands/utility/calculator.ts[R110-121]

+  // eslint-disable-next-line
+  const showDailies = (activeSeason && type === "sc") || (type === "c" && !activeSeason);
+  if (showDailies) {
+    checkboxes.push({
+      label: "Did you complete your dailies today?",
+      value: "dailies",
+      default: PlannerDataService.hasDoneDailies(
+        settings.plannerData,
+        activeSeason?.guid ?? "",
+        type === "c" ? "season" : "dailies",
+      ),
+    });
Evidence
UserPlannerData explicitly distinguishes season.checkin (record keyed by season guid) vs
dailies.checkin (string when season not active). The call site inverts which key is used.

packages/skyhelper/src/bot/modules/inputCommands/utility/calculator.ts[110-121]
packages/skyhelper/src/bot/handlers/planner/helpers/data.service.ts[453-460]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The calculator dailies checkbox default reads from the wrong planner checkin key, producing incorrect default state.

### Issue Context
`UserPlannerData` defines different storage for season dailies vs non-season dailies.

### Fix Focus Areas
- packages/skyhelper/src/bot/modules/inputCommands/utility/calculator.ts[110-121]
- packages/skyhelper/src/bot/handlers/planner/helpers/data.service.ts[359-363]
- packages/skyhelper/src/bot/handlers/planner/helpers/data.service.ts[453-460]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

6. AC shard simulation wrong 🐞 Bug ✓ Correctness
Description
Ascended-candle calculations treat getNextShard(simDate,["red"]) as a daily shard reward and
increment shardCount even when there is no red shard that day, making the reported shard
counts/days inaccurate.
Code

packages/skyhelper/src/bot/handlers/calculator.ts[R163-180]

+  while ((resWeekly === null || resShard === null || resCombined === null) && daysPassed < MAX_DAYS) {
+    let dailyWeekly = 0;
+    if (daysPassed === 0) {
+      if (!weeklyDone) {
+        dailyWeekly = MAX_WEEKLY_AC;
+        edenCounts++;
+      }
+    } else if (simDate.weekday === 7) {
+      dailyWeekly = MAX_WEEKLY_AC;
+      edenCounts++;
+    }
+
+    let dailyShard = ShardsUtil.getNextShard(simDate, ["red"])?.info.ac ?? 0;
+
+    // reset to zero if already cleared for todays shard
+    if (daysPassed === 0 && shardsCleared) dailyShard = 0;
+    else shardCount++;
+
Evidence
getNextShard returns the next upcoming shard event or null (including for non-red days), but the
calculator uses its AC value as if it were a per-day reward. The displayed copy says it’s counting
“red shards”, yet shardCount increments whenever the day isn't (day0 && shardsCleared).

packages/skyhelper/src/bot/handlers/calculator.ts[163-180]
packages/utils/src/classes/shardsUtil.ts[140-166]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The AC calculator’s shard simulation uses `getNextShard` (next upcoming event) as if it were the daily shard reward, and counts shards even on non-red/no-shard days.

### Issue Context
`getNextShard` can return null for non-red shard days, and depends on time-of-day (`date &lt;= eventTiming.start`). For a per-day planning calculation, day-level evaluation should not drift based on the current time.

### Fix Focus Areas
- packages/skyhelper/src/bot/handlers/calculator.ts[124-211]
- packages/utils/src/classes/shardsUtil.ts[131-166]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

7. commands.json write risk 🐞 Bug ⛯ Reliability
Description
registerCommands.ts now always writes commands.json synchronously; this adds an avoidable
failure mode (e.g., unwritable working dir) and blocks the event loop during deploy runs.
Code

packages/skyhelper/src/bot/handlers/registerCommands.ts[R27-31]

if (!process.env.CLIENT_ID) throw new Error("Cliend ID is missing from env");
-
+fs.writeFileSync("commands.json", JSON.stringify(toRegister, null, 2));
const data = (await rest.put(Routes.applicationCommands(process.env.CLIENT_ID), {
  body: toRegister,
})) as RESTPutAPIApplicationCommandsResult;
Evidence
The script performs an unconditional synchronous filesystem write immediately before registering
commands; there is no guarding env flag or error handling.

packages/skyhelper/src/bot/handlers/registerCommands.ts[27-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Unconditional `writeFileSync` in the command registration script introduces avoidable side effects and potential failures.

### Issue Context
This script is likely used in CI/deploy contexts where filesystem permissions may vary.

### Fix Focus Areas
- packages/skyhelper/src/bot/handlers/registerCommands.ts[27-31]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +81 to +86
case "sc": {
break;
}
}
await helper.editReply({ components: [component], flags: MessageFlags.IsComponentsV2 });
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

1. sc case leaves component undefined 📘 Rule violation ⛯ Reliability

The sc branch in handleCalculatorModal() does not assign component, but the code always uses
it in editReply(), which can cause a runtime failure for Seasonal Candle calculations. This
violates the requirement to handle edge cases and failure paths gracefully.
Agent Prompt
## Issue description
`handleCalculatorModal()` can hit the `sc` switch branch without assigning `component`, but still calls `editReply({ components: [component] })`, which can crash or send an invalid response.

## Issue Context
The calculator command allows selecting Seasonal Candles (`sc`), and the modal custom id can include `type="sc"`. The handler must either build a valid component for `sc` or exit early with a user-safe message.

## Fix Focus Areas
- packages/skyhelper/src/bot/handlers/calculator.ts[19-86]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +15 to +26
const [_, type = "c", guid = ""] = int.data.custom_id.split(";");
const current = client.utils.getModalComponent(int, "input_have", ComponentType.TextInput, true).value;
const checkboxes = client.utils.getModalComponent(int, "checkboxes", ComponentType.CheckboxGroupAction)?.values;

let component: APIContainerComponent;
switch (type) {
case "c": {
const target = client.utils.getModalComponent(int, "input_need", ComponentType.TextInput, true).value;

const { daysWithBase, daysWithBaseQuests, daysWithMax, daysWithMaxQuests } =
calculateCandles(Number(current), Number(target)) ?? {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

2. Modal numbers parsed without validation 📘 Rule violation ⛨ Security

User-provided modal text input values are converted with Number(...) and used without validating
NaN, non-integers, or negative/boundary values. This can produce incorrect outputs (e.g., NaN,
misleading day counts) instead of a clear, safe error response.
Agent Prompt
## Issue description
Modal text input values are treated as numbers via `Number(...)` without validating that they are finite, non-negative, and within expected ranges. Invalid inputs (empty string, non-numeric, negative, very large) can yield `NaN`/nonsensical results instead of a controlled error.

## Issue Context
These values come from Discord modal text inputs (user-controlled). Compliance requires explicit input validation and edge-case handling.

## Fix Focus Areas
- packages/skyhelper/src/bot/handlers/calculator.ts[15-26]
- packages/skyhelper/src/bot/handlers/calculator.ts[42-59]
- packages/skyhelper/src/bot/handlers/calculator.ts[88-102]
- packages/skyhelper/src/bot/handlers/calculator.ts[116-223]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +15 to +17
const [_, type = "c", guid = ""] = int.data.custom_id.split(";");
const current = client.utils.getModalComponent(int, "input_have", ComponentType.TextInput, true).value;
const checkboxes = client.utils.getModalComponent(int, "checkboxes", ComponentType.CheckboxGroupAction)?.values;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

3. Checkbox type mismatch 🐞 Bug ✓ Correctness

The handler reads checkbox values using ComponentType.CheckboxGroupAction, but the modal is built
with ComponentType.CheckboxGroup; getModalComponent enforces exact type matching and will throw
if these differ.
Agent Prompt
### Issue description
The calculator modal defines the checkboxes component as `ComponentType.CheckboxGroup`, but the submit handler tries to read it as `ComponentType.CheckboxGroupAction`. `getModalComponent` throws on mismatched types.

### Issue Context
`Utils.getModalComponent` checks `comp.type !== type` and throws when they differ.

### Fix Focus Areas
- packages/skyhelper/src/bot/handlers/calculator.ts[15-17]
- packages/skyhelper/src/bot/modules/inputCommands/utility/calculator.ts[71-79]
- packages/skyhelper/src/bot/utils/classes/Utils.ts[214-227]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +134 to +141
const shard = ShardsUtil.getShard(DateTime.now().setZone(zone));
if (shard && shard.type === "red") {
checkboxes.push({
label: "Have you cleared today's shard?",
value: "today_shard",
default: PlannerDataService.shardsCleared(settings.plannerData),
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

4. Shard checkbox unreachable 🐞 Bug ✓ Correctness

The AC modal’s “today’s shard” checkbox never appears because it checks shard.type, but
ShardsUtil.getShard() returns { info, timings } with the type nested under info.
Agent Prompt
### Issue description
AC checkbox rendering checks `shard.type`, but `ShardsUtil.getShard()` returns `{ info, timings }`. This makes the “today_shard” option unreachable.

### Issue Context
`type` lives under `shard.info.type`.

### Fix Focus Areas
- packages/skyhelper/src/bot/modules/inputCommands/utility/calculator.ts[132-141]
- packages/utils/src/classes/shardsUtil.ts[131-138]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +110 to +121
// eslint-disable-next-line
const showDailies = (activeSeason && type === "sc") || (type === "c" && !activeSeason);
if (showDailies) {
checkboxes.push({
label: "Did you complete your dailies today?",
value: "dailies",
default: PlannerDataService.hasDoneDailies(
settings.plannerData,
activeSeason?.guid ?? "",
type === "c" ? "season" : "dailies",
),
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

5. Wrong dailies checkin 🐞 Bug ✓ Correctness

The dailies checkbox default uses the wrong checkin type: regular candles (no season) query
season.checkin, while seasonal candles query dailies.checkin, so defaults will be incorrect.
Agent Prompt
### Issue description
The calculator dailies checkbox default reads from the wrong planner checkin key, producing incorrect default state.

### Issue Context
`UserPlannerData` defines different storage for season dailies vs non-season dailies.

### Fix Focus Areas
- packages/skyhelper/src/bot/modules/inputCommands/utility/calculator.ts[110-121]
- packages/skyhelper/src/bot/handlers/planner/helpers/data.service.ts[359-363]
- packages/skyhelper/src/bot/handlers/planner/helpers/data.service.ts[453-460]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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

Labels

package:constants Changes made to constants package package:skyhelper Changes made to skyhelper package package:utils Changes made to utils package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant