mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-30 06:12:06 -07:00
fix: address PR 36 CodeRabbit follow-ups
This commit is contained in:
15
Backlog.md
15
Backlog.md
@@ -20,7 +20,7 @@ Priority keys:
|
|||||||
|
|
||||||
| ID | Pri | Status | Area | Title |
|
| ID | Pri | Status | Area | Title |
|
||||||
| ------ | --- | ------ | -------------- | --------------------------------------------------- |
|
| ------ | --- | ------ | -------------- | --------------------------------------------------- |
|
||||||
| SM-013 | P1 | doing | review-followup | Address PR #36 CodeRabbit action items |
|
| SM-013 | P1 | done | review-followup | Address PR #36 CodeRabbit action items |
|
||||||
|
|
||||||
## Ready
|
## Ready
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ Done:
|
|||||||
|
|
||||||
Title: Address PR #36 CodeRabbit action items
|
Title: Address PR #36 CodeRabbit action items
|
||||||
Priority: P1
|
Priority: P1
|
||||||
Status: doing
|
Status: done
|
||||||
Scope:
|
Scope:
|
||||||
|
|
||||||
- `plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh`
|
- `plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh`
|
||||||
@@ -251,7 +251,16 @@ Scope:
|
|||||||
- `src/core/services/immersion-tracker/maintenance.ts`
|
- `src/core/services/immersion-tracker/maintenance.ts`
|
||||||
- `src/main/boot/services.ts`
|
- `src/main/boot/services.ts`
|
||||||
- `src/main/character-dictionary-runtime/zip.test.ts`
|
- `src/main/character-dictionary-runtime/zip.test.ts`
|
||||||
Acceptance:
|
Acceptance:
|
||||||
- fix valid open CodeRabbit findings on PR #36
|
- fix valid open CodeRabbit findings on PR #36
|
||||||
- add focused regression coverage for behavior changes where practical
|
- add focused regression coverage for behavior changes where practical
|
||||||
- verify touched tests plus typecheck stay green
|
- verify touched tests plus typecheck stay green
|
||||||
|
|
||||||
|
Done:
|
||||||
|
|
||||||
|
- hardened `--artifact-dir` validation in the verification script
|
||||||
|
- fixed trend aggregation rounding and monthly ratio bucketing
|
||||||
|
- preserved unwatched anime episodes in episode queries
|
||||||
|
- restored seconds-based aggregate timestamps in shared maintenance
|
||||||
|
- fixed the startup refactor compile break by making the predicates local at the call site
|
||||||
|
- verified with `bun test src/core/services/immersion-tracker/__tests__/query.test.ts src/core/services/immersion-tracker/__tests__/query-split-modules.test.ts` and `bun run typecheck`
|
||||||
|
|||||||
@@ -155,29 +155,32 @@ function upsertDailyRollupsForGroups(
|
|||||||
CAST(julianday(s.started_at_ms / 1000, 'unixepoch', 'localtime') - 2440587.5 AS INTEGER) AS rollup_day,
|
CAST(julianday(s.started_at_ms / 1000, 'unixepoch', 'localtime') - 2440587.5 AS INTEGER) AS rollup_day,
|
||||||
s.video_id AS video_id,
|
s.video_id AS video_id,
|
||||||
COUNT(DISTINCT s.session_id) AS total_sessions,
|
COUNT(DISTINCT s.session_id) AS total_sessions,
|
||||||
COALESCE(SUM(sm.max_active_ms), 0) / 60000.0 AS total_active_min,
|
COALESCE(SUM(COALESCE(sm.max_active_ms, s.active_watched_ms)), 0) / 60000.0 AS total_active_min,
|
||||||
COALESCE(SUM(sm.max_lines), 0) AS total_lines_seen,
|
COALESCE(SUM(COALESCE(sm.max_lines, s.lines_seen)), 0) AS total_lines_seen,
|
||||||
COALESCE(SUM(sm.max_tokens), 0) AS total_tokens_seen,
|
COALESCE(SUM(COALESCE(sm.max_tokens, s.tokens_seen)), 0) AS total_tokens_seen,
|
||||||
COALESCE(SUM(sm.max_cards), 0) AS total_cards,
|
COALESCE(SUM(COALESCE(sm.max_cards, s.cards_mined)), 0) AS total_cards,
|
||||||
CASE
|
CASE
|
||||||
WHEN COALESCE(SUM(sm.max_active_ms), 0) > 0
|
WHEN COALESCE(SUM(COALESCE(sm.max_active_ms, s.active_watched_ms)), 0) > 0
|
||||||
THEN (COALESCE(SUM(sm.max_cards), 0) * 60.0) / (COALESCE(SUM(sm.max_active_ms), 0) / 60000.0)
|
THEN (COALESCE(SUM(COALESCE(sm.max_cards, s.cards_mined)), 0) * 60.0)
|
||||||
|
/ (COALESCE(SUM(COALESCE(sm.max_active_ms, s.active_watched_ms)), 0) / 60000.0)
|
||||||
ELSE NULL
|
ELSE NULL
|
||||||
END AS cards_per_hour,
|
END AS cards_per_hour,
|
||||||
CASE
|
CASE
|
||||||
WHEN COALESCE(SUM(sm.max_active_ms), 0) > 0
|
WHEN COALESCE(SUM(COALESCE(sm.max_active_ms, s.active_watched_ms)), 0) > 0
|
||||||
THEN COALESCE(SUM(sm.max_tokens), 0) / (COALESCE(SUM(sm.max_active_ms), 0) / 60000.0)
|
THEN COALESCE(SUM(COALESCE(sm.max_tokens, s.tokens_seen)), 0)
|
||||||
|
/ (COALESCE(SUM(COALESCE(sm.max_active_ms, s.active_watched_ms)), 0) / 60000.0)
|
||||||
ELSE NULL
|
ELSE NULL
|
||||||
END AS tokens_per_min,
|
END AS tokens_per_min,
|
||||||
CASE
|
CASE
|
||||||
WHEN COALESCE(SUM(sm.max_lookups), 0) > 0
|
WHEN COALESCE(SUM(COALESCE(sm.max_lookups, s.lookup_count)), 0) > 0
|
||||||
THEN CAST(COALESCE(SUM(sm.max_hits), 0) AS REAL) / CAST(SUM(sm.max_lookups) AS REAL)
|
THEN CAST(COALESCE(SUM(COALESCE(sm.max_hits, s.lookup_hits)), 0) AS REAL)
|
||||||
|
/ CAST(COALESCE(SUM(COALESCE(sm.max_lookups, s.lookup_count)), 0) AS REAL)
|
||||||
ELSE NULL
|
ELSE NULL
|
||||||
END AS lookup_hit_rate,
|
END AS lookup_hit_rate,
|
||||||
? AS CREATED_DATE,
|
? AS CREATED_DATE,
|
||||||
? AS LAST_UPDATE_DATE
|
? AS LAST_UPDATE_DATE
|
||||||
FROM imm_sessions s
|
FROM imm_sessions s
|
||||||
JOIN (
|
LEFT JOIN (
|
||||||
SELECT
|
SELECT
|
||||||
t.session_id,
|
t.session_id,
|
||||||
MAX(t.active_watched_ms) AS max_active_ms,
|
MAX(t.active_watched_ms) AS max_active_ms,
|
||||||
@@ -227,14 +230,14 @@ function upsertMonthlyRollupsForGroups(
|
|||||||
CAST(strftime('%Y%m', s.started_at_ms / 1000, 'unixepoch', 'localtime') AS INTEGER) AS rollup_month,
|
CAST(strftime('%Y%m', s.started_at_ms / 1000, 'unixepoch', 'localtime') AS INTEGER) AS rollup_month,
|
||||||
s.video_id AS video_id,
|
s.video_id AS video_id,
|
||||||
COUNT(DISTINCT s.session_id) AS total_sessions,
|
COUNT(DISTINCT s.session_id) AS total_sessions,
|
||||||
COALESCE(SUM(sm.max_active_ms), 0) / 60000.0 AS total_active_min,
|
COALESCE(SUM(COALESCE(sm.max_active_ms, s.active_watched_ms)), 0) / 60000.0 AS total_active_min,
|
||||||
COALESCE(SUM(sm.max_lines), 0) AS total_lines_seen,
|
COALESCE(SUM(COALESCE(sm.max_lines, s.lines_seen)), 0) AS total_lines_seen,
|
||||||
COALESCE(SUM(sm.max_tokens), 0) AS total_tokens_seen,
|
COALESCE(SUM(COALESCE(sm.max_tokens, s.tokens_seen)), 0) AS total_tokens_seen,
|
||||||
COALESCE(SUM(sm.max_cards), 0) AS total_cards,
|
COALESCE(SUM(COALESCE(sm.max_cards, s.cards_mined)), 0) AS total_cards,
|
||||||
? AS CREATED_DATE,
|
? AS CREATED_DATE,
|
||||||
? AS LAST_UPDATE_DATE
|
? AS LAST_UPDATE_DATE
|
||||||
FROM imm_sessions s
|
FROM imm_sessions s
|
||||||
JOIN (
|
LEFT JOIN (
|
||||||
SELECT
|
SELECT
|
||||||
t.session_id,
|
t.session_id,
|
||||||
MAX(t.active_watched_ms) AS max_active_ms,
|
MAX(t.active_watched_ms) AS max_active_ms,
|
||||||
@@ -276,7 +279,7 @@ function getAffectedRollupGroups(
|
|||||||
FROM imm_session_telemetry t
|
FROM imm_session_telemetry t
|
||||||
JOIN imm_sessions s
|
JOIN imm_sessions s
|
||||||
ON s.session_id = t.session_id
|
ON s.session_id = t.session_id
|
||||||
WHERE t.sample_ms > ?
|
WHERE t.sample_ms >= ?
|
||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
.all(lastRollupSampleMs) as unknown as RollupGroupRow[]
|
.all(lastRollupSampleMs) as unknown as RollupGroupRow[]
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ export function getSimilarWords(db: DatabaseSync, wordId: number, limit = 10): S
|
|||||||
headword: string;
|
headword: string;
|
||||||
reading: string;
|
reading: string;
|
||||||
} | null;
|
} | null;
|
||||||
if (!word) return [];
|
if (!word || word.headword.trim() === '') return [];
|
||||||
return db
|
return db
|
||||||
.prepare(
|
.prepare(
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ export function getQueryHints(db: DatabaseSync): {
|
|||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const todayLocal = Math.floor(
|
const todayLocal = Math.floor(
|
||||||
(now.getTime() / 1000 - now.getTimezoneOffset() * 60) / 86_400,
|
new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime() / 86_400_000,
|
||||||
);
|
);
|
||||||
|
|
||||||
const episodesToday =
|
const episodesToday =
|
||||||
|
|||||||
@@ -272,5 +272,11 @@ export function deleteSessionsByIds(db: DatabaseSync, sessionIds: number[]): voi
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function toDbMs(ms: number | bigint): bigint {
|
export function toDbMs(ms: number | bigint): bigint {
|
||||||
return BigInt(Math.trunc(Number(ms)));
|
if (typeof ms === 'bigint') {
|
||||||
|
return ms;
|
||||||
|
}
|
||||||
|
if (!Number.isFinite(ms)) {
|
||||||
|
throw new TypeError(`Invalid database timestamp: ${ms}`);
|
||||||
|
}
|
||||||
|
return BigInt(Math.trunc(ms));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ function buildAggregatedTrendRows(rollups: ImmersionSessionRollupRow[]) {
|
|||||||
words: 0,
|
words: 0,
|
||||||
sessions: 0,
|
sessions: 0,
|
||||||
};
|
};
|
||||||
existing.activeMin += Math.round(rollup.totalActiveMin);
|
existing.activeMin += rollup.totalActiveMin;
|
||||||
existing.cards += rollup.totalCards;
|
existing.cards += rollup.totalCards;
|
||||||
existing.words += rollup.totalTokensSeen;
|
existing.words += rollup.totalTokensSeen;
|
||||||
existing.sessions += rollup.totalSessions;
|
existing.sessions += rollup.totalSessions;
|
||||||
@@ -179,7 +179,7 @@ function buildAggregatedTrendRows(rollups: ImmersionSessionRollupRow[]) {
|
|||||||
.sort(([left], [right]) => left - right)
|
.sort(([left], [right]) => left - right)
|
||||||
.map(([key, value]) => ({
|
.map(([key, value]) => ({
|
||||||
label: makeTrendLabel(key),
|
label: makeTrendLabel(key),
|
||||||
activeMin: value.activeMin,
|
activeMin: Math.round(value.activeMin),
|
||||||
cards: value.cards,
|
cards: value.cards,
|
||||||
words: value.words,
|
words: value.words,
|
||||||
sessions: value.sessions,
|
sessions: value.sessions,
|
||||||
@@ -243,22 +243,32 @@ function buildSessionSeriesByMonth(
|
|||||||
.map(([monthKey, value]) => ({ label: makeTrendLabel(monthKey), value }));
|
.map(([monthKey, value]) => ({ label: makeTrendLabel(monthKey), value }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildLookupsPerHundredWords(sessions: TrendSessionMetricRow[]): TrendChartPoint[] {
|
function buildLookupsPerHundredWords(
|
||||||
const lookupsByDay = new Map<number, number>();
|
sessions: TrendSessionMetricRow[],
|
||||||
const wordsByDay = new Map<number, number>();
|
groupBy: TrendGroupBy,
|
||||||
|
): TrendChartPoint[] {
|
||||||
|
const lookupsByBucket = new Map<number, number>();
|
||||||
|
const wordsByBucket = new Map<number, number>();
|
||||||
|
|
||||||
for (const session of sessions) {
|
for (const session of sessions) {
|
||||||
const epochDay = getLocalEpochDay(session.startedAtMs);
|
const bucketKey =
|
||||||
lookupsByDay.set(epochDay, (lookupsByDay.get(epochDay) ?? 0) + session.yomitanLookupCount);
|
groupBy === 'month' ? getLocalMonthKey(session.startedAtMs) : getLocalEpochDay(session.startedAtMs);
|
||||||
wordsByDay.set(epochDay, (wordsByDay.get(epochDay) ?? 0) + getTrendSessionWordCount(session));
|
lookupsByBucket.set(
|
||||||
|
bucketKey,
|
||||||
|
(lookupsByBucket.get(bucketKey) ?? 0) + session.yomitanLookupCount,
|
||||||
|
);
|
||||||
|
wordsByBucket.set(
|
||||||
|
bucketKey,
|
||||||
|
(wordsByBucket.get(bucketKey) ?? 0) + getTrendSessionWordCount(session),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(lookupsByDay.entries())
|
return Array.from(lookupsByBucket.entries())
|
||||||
.sort(([left], [right]) => left - right)
|
.sort(([left], [right]) => left - right)
|
||||||
.map(([epochDay, lookups]) => {
|
.map(([bucketKey, lookups]) => {
|
||||||
const words = wordsByDay.get(epochDay) ?? 0;
|
const words = wordsByBucket.get(bucketKey) ?? 0;
|
||||||
return {
|
return {
|
||||||
label: dayLabel(epochDay),
|
label: groupBy === 'month' ? makeTrendLabel(bucketKey) : dayLabel(bucketKey),
|
||||||
value: words > 0 ? +((lookups / words) * 100).toFixed(1) : 0,
|
value: words > 0 ? +((lookups / words) * 100).toFixed(1) : 0,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -595,7 +605,7 @@ export function getTrendsDashboard(
|
|||||||
const animePerDay = {
|
const animePerDay = {
|
||||||
episodes: buildEpisodesPerAnimeFromDailyRollups(dailyRollups, titlesByVideoId),
|
episodes: buildEpisodesPerAnimeFromDailyRollups(dailyRollups, titlesByVideoId),
|
||||||
watchTime: buildPerAnimeFromDailyRollups(dailyRollups, titlesByVideoId, (rollup) =>
|
watchTime: buildPerAnimeFromDailyRollups(dailyRollups, titlesByVideoId, (rollup) =>
|
||||||
Math.round(rollup.totalActiveMin),
|
rollup.totalActiveMin,
|
||||||
),
|
),
|
||||||
cards: buildPerAnimeFromDailyRollups(
|
cards: buildPerAnimeFromDailyRollups(
|
||||||
dailyRollups,
|
dailyRollups,
|
||||||
@@ -633,7 +643,7 @@ export function getTrendsDashboard(
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
ratios: {
|
ratios: {
|
||||||
lookupsPerHundred: buildLookupsPerHundredWords(sessions),
|
lookupsPerHundred: buildLookupsPerHundredWords(sessions, groupBy),
|
||||||
},
|
},
|
||||||
animePerDay,
|
animePerDay,
|
||||||
animeCumulative: {
|
animeCumulative: {
|
||||||
|
|||||||
28
src/main.ts
28
src/main.ts
@@ -4480,11 +4480,31 @@ const { runAndApplyStartupState } = composeHeadlessStartupHandlers<
|
|||||||
});
|
});
|
||||||
|
|
||||||
runAndApplyStartupState();
|
runAndApplyStartupState();
|
||||||
if (isAnilistTrackingEnabled(getResolvedConfig())) {
|
const shouldUseMinimalStartup = Boolean(
|
||||||
void refreshAnilistClientSecretStateIfEnabled({ force: true });
|
appState.initialArgs?.texthooker ||
|
||||||
anilistStateRuntime.refreshRetryQueueState();
|
(appState.initialArgs?.stats &&
|
||||||
|
(appState.initialArgs?.statsCleanup ||
|
||||||
|
appState.initialArgs?.statsBackground ||
|
||||||
|
appState.initialArgs?.statsStop)),
|
||||||
|
);
|
||||||
|
const shouldSkipHeavyStartup = Boolean(
|
||||||
|
appState.initialArgs &&
|
||||||
|
(shouldRunSettingsOnlyStartup(appState.initialArgs) ||
|
||||||
|
appState.initialArgs.stats ||
|
||||||
|
appState.initialArgs.dictionary ||
|
||||||
|
appState.initialArgs.setup),
|
||||||
|
);
|
||||||
|
if (!appState.initialArgs || (!shouldUseMinimalStartup && !shouldSkipHeavyStartup)) {
|
||||||
|
if (isAnilistTrackingEnabled(getResolvedConfig())) {
|
||||||
|
void refreshAnilistClientSecretStateIfEnabled({ force: true }).catch((error) => {
|
||||||
|
logger.error('Failed to refresh AniList client secret state during startup', error);
|
||||||
|
});
|
||||||
|
anilistStateRuntime.refreshRetryQueueState();
|
||||||
|
}
|
||||||
|
void initializeDiscordPresenceService().catch((error) => {
|
||||||
|
logger.error('Failed to initialize Discord presence service during startup', error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
void initializeDiscordPresenceService();
|
|
||||||
const { createMainWindow: createMainWindowHandler, createModalWindow: createModalWindowHandler } =
|
const { createMainWindow: createMainWindowHandler, createModalWindow: createModalWindowHandler } =
|
||||||
createOverlayWindowRuntimeHandlers<BrowserWindow>({
|
createOverlayWindowRuntimeHandlers<BrowserWindow>({
|
||||||
createOverlayWindowDeps: {
|
createOverlayWindowDeps: {
|
||||||
|
|||||||
Reference in New Issue
Block a user