Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a8a4d57
fix: prevent Arcade bonus score double counting
hoangsvit Oct 30, 2026
67e6a40
fix: prevent Arcade bonus score double counting
hoangsvit Oct 30, 2026
c5a007a
fix: prevent Arcade bonus score double counting
hoangsvit Oct 30, 2026
73752e5
fix: prevent Arcade bonus score double counting
hoangsvit Oct 30, 2026
818cd68
fix: prevent Arcade bonus score double counting
hoangsvit Oct 30, 2026
4d7f770
fix: prevent Arcade bonus score double counting
hoangsvit Oct 30, 2026
424a5a8
fix: prevent Arcade bonus score double counting
hoangsvit Oct 30, 2026
0fbff7c
style: format code with Prettier
deepsource-autofix[bot] Oct 30, 2026
7e9d041
fix: repair backtick delimiters in bonus score regression test
hoangsvit Oct 30, 2026
cb913b1
style: format code with Prettier
deepsource-autofix[bot] Oct 30, 2026
2893b35
refactor: address DeepSource countdown complexity and empty catches
hoangsvit Oct 30, 2026
ceacc13
fix: close refactored countdown service method correctly
hoangsvit Oct 30, 2026
b4d3aa9
test: cover Remote Config countdown lifecycle and single Arcade fallback
hoangsvit Oct 30, 2026
ab61573
style: format code with Prettier
deepsource-autofix[bot] Oct 30, 2026
cce8b0a
test: mock WXT browser i18n for countdown completion scenarios
hoangsvit Oct 30, 2026
b682646
style: format code with Prettier
deepsource-autofix[bot] Oct 30, 2026
6d37632
style: replace countdown string concatenation with template literals
hoangsvit Oct 30, 2026
941fd16
test: return explicit Promises from countdown mocks without unnecessa…
hoangsvit Oct 30, 2026
d9cc478
style: format code with Prettier
deepsource-autofix[bot] Oct 30, 2026
414b4bb
fix: remove redundant undefined argument from countdown test mock
hoangsvit Oct 30, 2026
2367b2a
chore(release): prepare extension v1.3.5
hoangsvit Oct 30, 2026
70a70af
docs(changelog): document v1.3.5 Arcade bonus hotfix
hoangsvit Oct 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ Current behavior:
- Requires explicit user confirmation.
- Keeps Bonus Milestone separate from the normal Facilitator milestone bonus.
- Avoids double counting when the API says the bonus is already included.
- The claim chip is status-only: it triggers a signed API refresh and never adjusts the headline DOM total itself.
- API point snapshots remain unmodified in extension storage; derived totals are only for presentation and toolbar badges.
- Persists confirmation using a profile/period-scoped storage key.
- Supports undo.
- Ignores stale confirmation for periods where the bonus is unavailable.
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.3.5] - 2026-10-30

### :bug: Bug Fixes

- Prevent double counting of the self-reported `+10` Bonus Milestone in the Arcade popup headline.
- Preserve raw Arcade API point totals in active-account and legacy storage instead of persisting locally adjusted totals.
- Respect the API's `facilitator.bonusIncludedInTotal` flag when deriving Facilitator bonus for the popup, account cards, and extension badge.

### :white_check_mark: Tests

- Add regression coverage for the API-confirmed 130-point score remaining unchanged after Bonus Milestone confirmation.
- Cover Facilitator/Arcade countdown Remote Config behavior, active timers, and single-card fallback after Facilitator expiry.

### :wrench: Maintenance

- Refactor countdown UI to reduce repeated code, remove empty exception handlers, and resolve DeepSource JavaScript findings.
- Bump the extension version to 1.3.5 in preparation for the `dev → main` release. The release tag is created only after approval and merge.

## [1.3.4] - 2026-10-06

### :lock: Security
Expand Down
48 changes: 1 addition & 47 deletions components/BonusMilestoneControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,46 +54,7 @@ function renderedManualBonus(state: BonusMilestoneControlState): number {
return state.appliedPoints > 0 ? state.appliedPoints : CLAIM_BONUS_POINTS;
}

/** Parse a rendered point total into a safe numeric value. */
function parseDisplayedTotal(value: string | null | undefined): number {
if (!value) return 0;
const normalized = value.replace(/\s/gu, "").replace(/,/gu, "");
const parsed = Number(normalized);
return Number.isFinite(parsed) ? parsed : 0;
}

/** Add only the self-reported bonus to the main total, without double counting it. */
function syncDisplayedTotal(state: BonusMilestoneControlState): void {
const total = document.getElementById("arcade-points");
if (!total) return;

if (total.dataset.bonusMilestoneProfile !== state.profileUrl) {
delete total.dataset.bonusMilestoneApplied;
delete total.dataset.bonusMilestoneRenderedTotal;
total.dataset.bonusMilestoneProfile = state.profileUrl;
}

const currentTotal = parseDisplayedTotal(total.textContent);
const previousApplied =
Number(total.dataset.bonusMilestoneApplied || "0") || 0;
const previousRendered = Number(
total.dataset.bonusMilestoneRenderedTotal || "NaN",
);
const stillContainsPreviousBonus =
Number.isFinite(previousRendered) && currentTotal === previousRendered;
const baseTotal = stillContainsPreviousBonus
? Math.max(0, currentTotal - previousApplied)
: currentTotal;
const nextApplied = state.bonusIncludedInTotal
? 0
: renderedManualBonus(state);
const nextTotal = baseTotal + nextApplied;

total.textContent = formatPoints(nextTotal);
total.dataset.bonusMilestoneApplied = String(nextApplied);
total.dataset.bonusMilestoneRenderedTotal = String(nextTotal);
}

/** The signed Arcade refresh owns the total; the claim chip never edits score DOM. */
/** Render the self-report disclaimer and the official task link before confirmation. */
function BonusMilestoneInfo({ completed }: BonusMilestoneInfoProps) {
return (
Expand Down Expand Up @@ -147,13 +108,6 @@ function BonusMilestoneControl() {
[reload],
);

useEffect(
function keepMainTotalInSync() {
syncDisplayedTotal(state);
},
[state],
);

const manualBonus = renderedManualBonus(state);
const canConfirm = state.participating && state.enabled && state.points > 0;

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "google-cloud-skills-boost-helper",
"description": "__MSG_extDescription__",
"private": true,
"version": "1.3.4",
"version": "1.3.5",
"author": "dev@eplus.dev",
"license": "MIT",
"homepage_url": "https://eplus.dev",
Expand Down
8 changes: 5 additions & 3 deletions services/optionsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,11 @@ const OptionsService = {
const nickname = account.nickname;
const arcadePoints = account.arcadeData?.arcadePoints?.totalPoints || 0;
// If facilitator program is enabled, compute facilitator bonus and add to total
const facilitatorBonus = account.facilitatorProgram
? calculateFacilitatorBonus(account.arcadeData?.faciCounts)
: 0;
const facilitatorBonus =
account.facilitatorProgram &&
account.arcadeData?.facilitator?.bonusIncludedInTotal !== true
? calculateFacilitatorBonus(account.arcadeData?.faciCounts)
: 0;
const finalPoints = (arcadePoints || 0) + (facilitatorBonus || 0);
const activeLabel = browser.i18n.getMessage("statusActive");
const facilitatorLabel = browser.i18n.getMessage("labelFacilitatorProgram");
Expand Down
Loading
Loading