From a27fd01950135ca4d4a9940e4391f2ad6eec37f1 Mon Sep 17 00:00:00 2001 From: Andrei Lavrenov Date: Sat, 23 May 2026 21:18:10 +0200 Subject: [PATCH 1/4] docs(jsdoc): add @param descriptions for CascadeLog.props and seasonAnalytics.opts Clears the two jsdoc/require-param-description lint warnings tracked in issue #400. Both sites had the type annotation but no description text, which eslint-plugin-jsdoc requires for completeness. Co-Authored-By: Claude Opus 4.7 --- src/features/timeline/CascadeLog.jsx | 2 +- src/shared/utils/game/seasonAnalytics.mjs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/features/timeline/CascadeLog.jsx b/src/features/timeline/CascadeLog.jsx index 30d487ac..0305c28d 100644 --- a/src/features/timeline/CascadeLog.jsx +++ b/src/features/timeline/CascadeLog.jsx @@ -11,7 +11,7 @@ import { groupCascadesBySeason } from '@/features/timeline/groupCascadesBySeason * Cross-season cascade log. Same section layout as EventLog, grouped by * season instead of by day. Renders nothing when `cascades` is empty. * - * @param {object} props + * @param {object} props - Component props. * @param {Array} props.cascades - Each cascade includes a `season` field. * @param {string} [props.lede] - Optional one-line summary above the groups. * @param {string} [props.title] - Optional heading text (defaults to "Cascade Failures"). diff --git a/src/shared/utils/game/seasonAnalytics.mjs b/src/shared/utils/game/seasonAnalytics.mjs index 8687e3a8..5cc738ca 100644 --- a/src/shared/utils/game/seasonAnalytics.mjs +++ b/src/shared/utils/game/seasonAnalytics.mjs @@ -10,8 +10,8 @@ const MAX_GAP_SEC = 3600; // 1 hour * and consecutive events within `MAX_GAP_SEC` (1 hour). * * @param {Array} events - h1_event records (any type, any status) - * @param {object} [opts] - * @param {number} [opts.minLength=3] - Inclusive minimum cascade length + * @param {object} [opts] - Optional configuration. + * @param {number} [opts.minLength=3] - Inclusive minimum cascade length. * @returns {Array<{ * length: number, * faction: string, From 1e421a474d835cc1e0645d5f2845866db1ca5956 Mon Sep 17 00:00:00 2001 From: Andrei Lavrenov Date: Sat, 23 May 2026 21:18:11 +0200 Subject: [PATCH 2/4] test: rename per-file makeFactionMap helpers to reflect their shapes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three makeFactionMap test helpers across EventCard.test.jsx, DashboardClient.test.jsx, and computeFrontier.test.mjs were flagged by desloppify's signature_variance detector as one symbol with 2 different signatures across 3 files. Closer inspection shows they're genuinely different helpers that just happened to share a name — each takes a different region range (1-11 vs 1-10 vs 0-11) and produces a different field set. Extracting a shared helper would force a fake abstraction over three distinct test shapes. Renamed instead: - EventCard.test.jsx::makeFactionMap → makeSectorMap (per-faction sectors map keyed on regions 1-11, status+percent only) - DashboardClient.test.jsx::makeFactionMap → makeDashboardMap (full DashboardClient mapState shape, regions 0-11, region+status+event+ percent) - computeFrontier.test.mjs::makeFactionMap unchanged (already scoped inside a describe() block — symbol is no longer cross-file once the other two are renamed, so signature_variance dissolves) Closes issue #401. Co-Authored-By: Claude Opus 4.7 --- .../unit/features/dashboard/DashboardClient.test.jsx | 12 ++++++------ .../unit/features/galaxy/EventCard.test.jsx | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/__tests__/unit/features/dashboard/DashboardClient.test.jsx b/src/__tests__/unit/features/dashboard/DashboardClient.test.jsx index 48959c7a..38c84b5a 100644 --- a/src/__tests__/unit/features/dashboard/DashboardClient.test.jsx +++ b/src/__tests__/unit/features/dashboard/DashboardClient.test.jsx @@ -65,7 +65,7 @@ vi.mock('@/features/stats/evaluateProgress.mjs', () => ({ import DashboardClient from '@/features/dashboard/DashboardClient'; -function makeFactionMap(overrides = {}) { +function makeDashboardMap(overrides = {}) { const map = {}; for (let r = 0; r <= 11; r++) { map[r] = { region: `Region ${r}`, status: 'lost', event: 'idle', percent: 0 }; @@ -77,10 +77,10 @@ function makeFactionMap(overrides = {}) { } const baseMapState = { - 0: makeFactionMap(), - 1: makeFactionMap(), - 2: makeFactionMap(), - 3: makeFactionMap(), // Super Earth + 0: makeDashboardMap(), + 1: makeDashboardMap(), + 2: makeDashboardMap(), + 3: makeDashboardMap(), // Super Earth }; function getCardProps(testId) { @@ -306,7 +306,7 @@ describe('DashboardClient — homeworld card suppression', () => { }, mapState: { ...baseMapState, - 0: makeFactionMap({ + 0: makeDashboardMap({ 11: { event: 'active', status: 'active', diff --git a/src/__tests__/unit/features/galaxy/EventCard.test.jsx b/src/__tests__/unit/features/galaxy/EventCard.test.jsx index 46c9823d..a9b46307 100644 --- a/src/__tests__/unit/features/galaxy/EventCard.test.jsx +++ b/src/__tests__/unit/features/galaxy/EventCard.test.jsx @@ -25,7 +25,7 @@ const baseProps = { * Build a mapState[factionIndex] with the given sector statuses. * Default: everything lost. Each entry in `overrides` keyed by region 1–11. */ -function makeFactionMap(overrides = {}) { +function makeSectorMap(overrides = {}) { const map = {}; for (let r = 1; r <= 11; r++) map[r] = { status: 'lost', percent: 0 }; for (const [key, val] of Object.entries(overrides)) { @@ -258,7 +258,7 @@ describe('EventCard (campaign view)', () => { const campaignProps = { ...baseProps, view: 'campaign', - factionMap: makeFactionMap({ + factionMap: makeSectorMap({ 1: { status: 'captured', percent: 100 }, 2: { status: 'captured', percent: 100 }, 3: { status: 'captured', percent: 100 }, @@ -286,7 +286,7 @@ describe('EventCard (campaign view)', () => { { , @@ -355,7 +355,7 @@ describe('EventCard (campaign view)', () => { , From 39d99e1eac803b1f4e68fe0d0cda6ef73aad7870 Mon Sep 17 00:00:00 2001 From: Andrei Lavrenov Date: Sat, 23 May 2026 21:18:11 +0200 Subject: [PATCH 3/4] refactor(catch): annotate two intentional empty catches with diagnostic logs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Promotes two intentional `catch {}` swallows (flagged by desloppify's smells detector in issue #399) from bare comment to a documented swallow with console.debug logging of the captured error. Both sites are genuinely non-critical paths where throwing would do more harm than good: - MinistryProvider.jsx flicker scheduler — cosmetic animation; a transient timing error should not crash the provider that wraps the entire dashboard. - useLiveData.mjs saveCachedState — localStorage may be full, disabled in private mode, or unavailable in SSR; the cache is a PWA offline nicety, never a correctness path. The console.debug calls make the swallows visible to anyone running the app with the browser console open at debug level, removing the "silently lost" failure mode without changing any runtime behavior. Closes issue #399. Co-Authored-By: Claude Opus 4.7 --- src/features/ministry/MinistryProvider.jsx | 7 +++++-- src/shared/hooks/useLiveData.mjs | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/features/ministry/MinistryProvider.jsx b/src/features/ministry/MinistryProvider.jsx index dbf80fb9..efa0a9e0 100644 --- a/src/features/ministry/MinistryProvider.jsx +++ b/src/features/ministry/MinistryProvider.jsx @@ -189,8 +189,11 @@ export default function MinistryProvider({ warTone, children }) { ]; const dur = randomBetween(FLICKER_DUR_MIN_MS, FLICKER_DUR_MAX_MS, rng); entry.onFlicker(charIdx, dur); - } catch { - // swallow; reschedule below + } catch (err) { + // Swallow flicker-scheduling errors and reschedule below. + // The flicker animation is a cosmetic non-critical path; we + // never want a transient timing issue to crash the provider. + console.debug('[MinistryProvider] flicker scheduling failed:', err); } scheduleNext(); } diff --git a/src/shared/hooks/useLiveData.mjs b/src/shared/hooks/useLiveData.mjs index b6707c0f..2f1db070 100644 --- a/src/shared/hooks/useLiveData.mjs +++ b/src/shared/hooks/useLiveData.mjs @@ -37,8 +37,10 @@ function saveCachedState(data, mapState) { CACHE_KEY, JSON.stringify({ data, mapState, ts: Date.now() }), ); - } catch { - // localStorage full or unavailable — ignore + } catch (err) { + // localStorage full / unavailable / disabled in private mode — ignore. + // The cache is an offline-fallback nicety, never a correctness path. + console.debug('[useLiveData] saveCachedState skipped:', err); } } From a5ce58edc03422639ec27acd6a0732feb99016a4 Mon Sep 17 00:00:00 2001 From: Andrei Lavrenov Date: Sat, 23 May 2026 21:18:11 +0200 Subject: [PATCH 4/4] docs(changelog): record mechanical cleanup of #399, #400, #401 Co-Authored-By: Claude Opus 4.7 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13c248c1..32270293 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Changes + +- **Desloppify mechanical cleanup** — closes three filed issues in one PR. (1) Adds JSDoc `@param` descriptions to `CascadeLog.jsx` (`props`) and `seasonAnalytics.mjs` (`opts`), clearing the two `jsdoc/require-param-description` lint warnings (#400). (2) Renames the cross-file `makeFactionMap` test helpers — `EventCard.test.jsx` uses `makeSectorMap` (it builds a per-faction sectors map for one event card) and `DashboardClient.test.jsx` uses `makeDashboardMap` (it builds the full mapState shape including region 0/11) — each helper is a genuinely different shape (regions 1-11 vs 1-10 vs 0-11 with different field sets), so renaming is more honest than extracting a fake shared abstraction; the third occurrence in `computeFrontier.test.mjs` is already scoped inside a `describe()` block (#401). (3) Annotates the two intentional empty-`catch` swallows in `MinistryProvider.jsx` (flicker scheduler) and `useLiveData.mjs` (`saveCachedState`) — both now capture `err` and `console.debug` it so the swallow is visible during diagnosis, with the existing rationale promoted from inline comment to a fuller multi-line explanation of why each path is non-critical (#399). No runtime behavior change. + ## 0.51.3 ### Changes