diff --git a/src/components/dialogs/UserDialog/UserDialogActivityTab.vue b/src/components/dialogs/UserDialog/UserDialogActivityTab.vue
index e896a4ee..9e397a7e 100644
--- a/src/components/dialogs/UserDialog/UserDialogActivityTab.vue
+++ b/src/components/dialogs/UserDialog/UserDialogActivityTab.vue
@@ -23,7 +23,6 @@
- {{ t('dialog.user.activity.period_180') }}
{{ t('dialog.user.activity.period_90') }}
{{ t('dialog.user.activity.period_30') }}
{{ t('dialog.user.activity.period_7') }}
@@ -241,6 +240,7 @@
});
let activeRequestId = 0;
+ let activeOverlapRequestId = 0;
let lastLoadedUserId = '';
const pendingWorldThumbnailFetches = new Set();
@@ -281,6 +281,7 @@
mainHeatmapView.value = { rawBuckets: [], normalizedBuckets: [] };
overlapHeatmapView.value = { rawBuckets: [], normalizedBuckets: [] };
activeRequestId++;
+ activeOverlapRequestId++;
lastLoadedUserId = '';
}
@@ -291,6 +292,7 @@
}
const requestId = ++activeRequestId;
+ ++activeOverlapRequestId;
if (!silent) {
isLoading.value = true;
}
@@ -386,20 +388,56 @@
await refreshData();
}
+ async function refreshOverlapOnly() {
+ const userId = userDialog.value.id;
+ if (!userId || isSelf.value || !hasAnyData.value) {
+ return;
+ }
+
+ const requestId = ++activeOverlapRequestId;
+ isOverlapLoading.value = true;
+
+ try {
+ const rangeDays = parseInt(selectedPeriod.value, 10) || 30;
+ const overlapView = await activityStore.loadOverlapView({
+ currentUserId: currentUser.value.id,
+ targetUserId: userId,
+ rangeDays,
+ dayLabels: dayLabels.value,
+ forceRefresh: false,
+ excludeHours: {
+ enabled: excludeHoursEnabled.value,
+ startHour: parseInt(excludeStartHour.value, 10),
+ endHour: parseInt(excludeEndHour.value, 10)
+ }
+ });
+ if (requestId !== activeOverlapRequestId || userDialog.value.id !== userId) {
+ return;
+ }
+ overlapHeatmapView.value = {
+ rawBuckets: overlapView.rawBuckets,
+ normalizedBuckets: overlapView.normalizedBuckets
+ };
+ hasOverlapData.value = overlapView.hasOverlapData;
+ overlapPercent.value = overlapView.overlapPercent;
+ bestOverlapTime.value = overlapView.bestOverlapTime;
+ } finally {
+ if (requestId === activeOverlapRequestId) {
+ isOverlapLoading.value = false;
+ }
+ }
+ }
+
async function onExcludeToggle(value) {
excludeHoursEnabled.value = value;
await configRepository.setBool('VRCX_overlapExcludeEnabled', value);
- if (!isSelf.value && hasAnyData.value) {
- await refreshData({ silent: true });
- }
+ await refreshOverlapOnly();
}
async function onExcludeRangeChange() {
await configRepository.setString('VRCX_overlapExcludeStart', excludeStartHour.value);
await configRepository.setString('VRCX_overlapExcludeEnd', excludeEndHour.value);
- if (!isSelf.value && hasAnyData.value) {
- await refreshData({ silent: true });
- }
+ await refreshOverlapOnly();
}
async function fetchMissingTopWorldThumbnails(worlds) {
@@ -510,19 +548,34 @@
ensureActivityChart();
if (!activityChart) return;
- activityChart.setOption(buildHeatmapOption({
- data: toHeatmapSeriesData(mainHeatmapView.value.normalizedBuckets, weekStartsOn.value),
- rawBuckets: mainHeatmapView.value.rawBuckets,
- dayLabels: displayDayLabels.value,
- hourLabels,
- weekStartsOn: weekStartsOn.value,
- isDarkMode: isDarkMode.value,
- emptyColor: isDarkMode.value ? 'hsl(220, 15%, 12%)' : 'hsl(210, 30%, 95%)',
- scaleColors: isDarkMode.value
- ? ['hsl(160, 40%, 24%)', 'hsl(150, 48%, 32%)', 'hsl(142, 55%, 38%)', 'hsl(142, 65%, 46%)', 'hsl(142, 80%, 55%)']
- : ['hsl(160, 40%, 82%)', 'hsl(155, 45%, 68%)', 'hsl(142, 55%, 55%)', 'hsl(142, 65%, 40%)', 'hsl(142, 76%, 30%)'],
- unitLabel: t('dialog.user.activity.minutes_online')
- }), { notMerge: true });
+ activityChart.setOption(
+ buildHeatmapOption({
+ data: toHeatmapSeriesData(mainHeatmapView.value.normalizedBuckets, weekStartsOn.value),
+ rawBuckets: mainHeatmapView.value.rawBuckets,
+ dayLabels: displayDayLabels.value,
+ hourLabels,
+ weekStartsOn: weekStartsOn.value,
+ isDarkMode: isDarkMode.value,
+ emptyColor: isDarkMode.value ? 'hsl(220, 15%, 12%)' : 'hsl(210, 30%, 95%)',
+ scaleColors: isDarkMode.value
+ ? [
+ 'hsl(160, 40%, 24%)',
+ 'hsl(150, 48%, 32%)',
+ 'hsl(142, 55%, 38%)',
+ 'hsl(142, 65%, 46%)',
+ 'hsl(142, 80%, 55%)'
+ ]
+ : [
+ 'hsl(160, 40%, 82%)',
+ 'hsl(155, 45%, 68%)',
+ 'hsl(142, 55%, 55%)',
+ 'hsl(142, 65%, 40%)',
+ 'hsl(142, 76%, 30%)'
+ ],
+ unitLabel: t('dialog.user.activity.minutes_online')
+ }),
+ { replaceMerge: ['series'] }
+ );
}
function renderOverlapChart() {
@@ -533,19 +586,34 @@
ensureOverlapChart();
if (!overlapChart) return;
- overlapChart.setOption(buildHeatmapOption({
- data: toHeatmapSeriesData(overlapHeatmapView.value.normalizedBuckets, weekStartsOn.value),
- rawBuckets: overlapHeatmapView.value.rawBuckets,
- dayLabels: displayDayLabels.value,
- hourLabels,
- weekStartsOn: weekStartsOn.value,
- isDarkMode: isDarkMode.value,
- emptyColor: isDarkMode.value ? 'hsl(220, 15%, 12%)' : 'hsl(210, 30%, 95%)',
- scaleColors: isDarkMode.value
- ? ['hsl(260, 30%, 26%)', 'hsl(260, 42%, 36%)', 'hsl(260, 50%, 45%)', 'hsl(260, 60%, 54%)', 'hsl(260, 70%, 62%)']
- : ['hsl(260, 35%, 85%)', 'hsl(260, 42%, 70%)', 'hsl(260, 48%, 58%)', 'hsl(260, 55%, 48%)', 'hsl(260, 60%, 38%)'],
- unitLabel: t('dialog.user.activity.overlap.minutes_overlap')
- }), { notMerge: true });
+ overlapChart.setOption(
+ buildHeatmapOption({
+ data: toHeatmapSeriesData(overlapHeatmapView.value.normalizedBuckets, weekStartsOn.value),
+ rawBuckets: overlapHeatmapView.value.rawBuckets,
+ dayLabels: displayDayLabels.value,
+ hourLabels,
+ weekStartsOn: weekStartsOn.value,
+ isDarkMode: isDarkMode.value,
+ emptyColor: isDarkMode.value ? 'hsl(220, 15%, 12%)' : 'hsl(210, 30%, 95%)',
+ scaleColors: isDarkMode.value
+ ? [
+ 'hsl(260, 30%, 26%)',
+ 'hsl(260, 42%, 36%)',
+ 'hsl(260, 50%, 45%)',
+ 'hsl(260, 60%, 54%)',
+ 'hsl(260, 70%, 62%)'
+ ]
+ : [
+ 'hsl(260, 35%, 85%)',
+ 'hsl(260, 42%, 70%)',
+ 'hsl(260, 48%, 58%)',
+ 'hsl(260, 55%, 48%)',
+ 'hsl(260, 60%, 38%)'
+ ],
+ unitLabel: t('dialog.user.activity.overlap.minutes_overlap')
+ }),
+ { replaceMerge: ['series'] }
+ );
}
function rebuildCharts() {
@@ -557,7 +625,7 @@
}
function onChartRightClick() {
- toast(t('dialog.user.activity.easter_egg'), { position: 'bottom-center', icon: h(Tractor) });
+ toast(t('dialog.user.activity.chart_hint'), { position: 'bottom-center', icon: h(Tractor) });
clearTimeout(easterEggTimer);
easterEggTimer = setTimeout(() => {
easterEggTimer = null;
@@ -566,7 +634,7 @@
function onOverlapChartRightClick() {
if (easterEggTimer) {
- toast(t('dialog.user.activity.easter_egg_reply'), { position: 'bottom-center', icon: h(Sprout) });
+ toast(t('dialog.user.activity.chart_hint_reply'), { position: 'bottom-center', icon: h(Sprout) });
}
}
@@ -577,40 +645,60 @@
void loadForVisibleTab();
}
- watch(() => userDialog.value.id, () => {
- resetActivityState();
- rebuildCharts();
- if (userDialog.value.visible && userDialog.value.activeTab === 'Activity') {
- void nextTick(() => loadForVisibleTab());
+ watch(
+ () => userDialog.value.id,
+ () => {
+ resetActivityState();
+ rebuildCharts();
+ if (userDialog.value.visible && userDialog.value.activeTab === 'Activity') {
+ void nextTick(() => loadForVisibleTab());
+ }
}
- });
+ );
watch([locale, isDarkMode, weekStartsOn], rebuildCharts);
- watch(() => selectedPeriod.value, () => {
- if (userDialog.value.visible && userDialog.value.activeTab === 'Activity') {
- void onPeriodChange();
+ watch(
+ () => selectedPeriod.value,
+ () => {
+ if (userDialog.value.visible && userDialog.value.activeTab === 'Activity') {
+ void onPeriodChange();
+ }
}
- });
- watch(() => mainHeatmapView.value, () => {
- nextTick(() => renderActivityChart());
- }, { deep: true });
- watch(() => overlapHeatmapView.value, () => {
- nextTick(() => renderOverlapChart());
- }, { deep: true });
- watch(() => userDialog.value.visible, (visible) => {
- if (!visible) return;
- nextTick(() => {
- activityChart?.resize();
- overlapChart?.resize();
- });
- if (userDialog.value.activeTab === 'Activity') {
- void loadForVisibleTab();
+ );
+ watch(
+ () => mainHeatmapView.value,
+ () => {
+ nextTick(() => renderActivityChart());
+ },
+ { deep: true }
+ );
+ watch(
+ () => overlapHeatmapView.value,
+ () => {
+ nextTick(() => renderOverlapChart());
+ },
+ { deep: true }
+ );
+ watch(
+ () => userDialog.value.visible,
+ (visible) => {
+ if (!visible) return;
+ nextTick(() => {
+ activityChart?.resize();
+ overlapChart?.resize();
+ });
+ if (userDialog.value.activeTab === 'Activity') {
+ void loadForVisibleTab();
+ }
}
- });
- watch(() => userDialog.value.activeTab, (activeTab) => {
- if (activeTab === 'Activity' && userDialog.value.visible) {
- void loadForVisibleTab();
+ );
+ watch(
+ () => userDialog.value.activeTab,
+ (activeTab) => {
+ if (activeTab === 'Activity' && userDialog.value.visible) {
+ void loadForVisibleTab();
+ }
}
- });
+ );
onMounted(async () => {
await initializeSettings();
diff --git a/src/localization/en.json b/src/localization/en.json
index 3ca36076..1acfd0ef 100644
--- a/src/localization/en.json
+++ b/src/localization/en.json
@@ -1435,7 +1435,6 @@
"most_active_day": "Most active day:",
"most_active_time": "Peak hours:",
"period": "Period:",
- "period_180": "Last 180 Days",
"period_90": "Last 90 Days",
"period_30": "Last 30 Days",
"period_7": "Last 7 Days",
@@ -1450,8 +1449,8 @@
"sat": "Sat",
"sun": "Sun"
},
- "easter_egg": "Did you farm your green squares today?",
- "easter_egg_reply": "You can't farm this.",
+ "chart_hint": "Did you farm your green squares today?",
+ "chart_hint_reply": "You can't farm this.",
"overlap": {
"header": "Online Overlap",
"peak_overlap": "Peak overlap:",
diff --git a/src/stores/activity.js b/src/stores/activity.js
index b34eb9ec..6541b86f 100644
--- a/src/stores/activity.js
+++ b/src/stores/activity.js
@@ -134,8 +134,21 @@ export const useActivityStore = defineStore('Activity', () => {
return false;
}
- async function loadActivity(userId, { isSelf = false, rangeDays = 30, normalizeConfig, dayLabels, forceRefresh = false }) {
- const snapshot = await ensureSnapshot(userId, { isSelf, rangeDays, forceRefresh });
+ async function loadActivity(
+ userId,
+ {
+ isSelf = false,
+ rangeDays = 30,
+ normalizeConfig,
+ dayLabels,
+ forceRefresh = false
+ }
+ ) {
+ const snapshot = await ensureSnapshot(userId, {
+ isSelf,
+ rangeDays,
+ forceRefresh
+ });
const cacheKey = String(rangeDays);
const currentCursor = snapshot.sync.sourceLastCreatedAt || '';
@@ -175,38 +188,55 @@ export const useActivityStore = defineStore('Activity', () => {
builtAt: new Date().toISOString()
};
snapshot.activityViews.set(cacheKey, view);
- deferWrite(() => database.upsertActivityBucketCacheV2({
- ownerUserId: userId,
- rangeDays,
- viewKind: database.ACTIVITY_VIEW_KIND.ACTIVITY,
- builtFromCursor: currentCursor,
- rawBuckets: view.rawBuckets,
- normalizedBuckets: view.normalizedBuckets,
- summary: {
- peakDay: view.peakDay,
- peakTime: view.peakTime,
- filteredEventCount: view.filteredEventCount
- },
- builtAt: view.builtAt
- }));
+ deferWrite(() =>
+ database.upsertActivityBucketCacheV2({
+ ownerUserId: userId,
+ rangeDays,
+ viewKind: database.ACTIVITY_VIEW_KIND.ACTIVITY,
+ builtFromCursor: currentCursor,
+ rawBuckets: view.rawBuckets,
+ normalizedBuckets: view.normalizedBuckets,
+ summary: {
+ peakDay: view.peakDay,
+ peakTime: view.peakTime,
+ filteredEventCount: view.filteredEventCount
+ },
+ builtAt: view.builtAt
+ })
+ );
return buildActivityResponse(snapshot, view);
}
- async function loadOverlap(currentUserId, targetUserId, {
- rangeDays = 30,
- dayLabels,
- normalizeConfig,
- excludeHours,
- forceRefresh = false
- }) {
+ async function loadOverlap(
+ currentUserId,
+ targetUserId,
+ {
+ rangeDays = 30,
+ dayLabels,
+ normalizeConfig,
+ excludeHours,
+ forceRefresh = false
+ }
+ ) {
const [selfSnapshot, targetSnapshot] = await Promise.all([
- ensureSnapshot(currentUserId, { isSelf: true, rangeDays, forceRefresh }),
- ensureSnapshot(targetUserId, { isSelf: false, rangeDays, forceRefresh })
+ ensureSnapshot(currentUserId, {
+ isSelf: true,
+ rangeDays,
+ forceRefresh
+ }),
+ ensureSnapshot(targetUserId, {
+ isSelf: false,
+ rangeDays,
+ forceRefresh
+ })
]);
const excludeKey = overlapExcludeKey(excludeHours);
const cacheKey = `${targetUserId}:${rangeDays}:${excludeKey}`;
- const cursor = pairCursor(selfSnapshot.sync.sourceLastCreatedAt, targetSnapshot.sync.sourceLastCreatedAt);
+ const cursor = pairCursor(
+ selfSnapshot.sync.sourceLastCreatedAt,
+ targetSnapshot.sync.sourceLastCreatedAt
+ );
let view = targetSnapshot.overlapViews.get(cacheKey);
if (view?.builtFromCursor === cursor) {
@@ -246,26 +276,35 @@ export const useActivityStore = defineStore('Activity', () => {
builtAt: new Date().toISOString()
};
targetSnapshot.overlapViews.set(cacheKey, view);
- deferWrite(() => database.upsertActivityBucketCacheV2({
- ownerUserId: currentUserId,
- targetUserId,
- rangeDays,
- viewKind: database.ACTIVITY_VIEW_KIND.OVERLAP,
- excludeKey,
- builtFromCursor: cursor,
- rawBuckets: view.rawBuckets,
- normalizedBuckets: view.normalizedBuckets,
- summary: {
- overlapPercent: view.overlapPercent,
- bestOverlapTime: view.bestOverlapTime
- },
- builtAt: view.builtAt
- }));
+ deferWrite(() =>
+ database.upsertActivityBucketCacheV2({
+ ownerUserId: currentUserId,
+ targetUserId,
+ rangeDays,
+ viewKind: database.ACTIVITY_VIEW_KIND.OVERLAP,
+ excludeKey,
+ builtFromCursor: cursor,
+ rawBuckets: view.rawBuckets,
+ normalizedBuckets: view.normalizedBuckets,
+ summary: {
+ overlapPercent: view.overlapPercent,
+ bestOverlapTime: view.bestOverlapTime
+ },
+ builtAt: view.builtAt
+ })
+ );
return view;
}
- async function loadTopWorlds(userId, { rangeDays = 30, limit = 5, isSelf = true, forceRefresh = false }) {
- const snapshot = await ensureSnapshot(userId, { isSelf, rangeDays, forceRefresh });
+ async function loadTopWorlds(
+ userId,
+ { rangeDays = 30, limit = 5, isSelf = true, forceRefresh = false }
+ ) {
+ const snapshot = await ensureSnapshot(userId, {
+ isSelf,
+ rangeDays,
+ forceRefresh
+ });
const cacheKey = `${rangeDays}:${limit}`;
const currentCursor = snapshot.sync.sourceLastCreatedAt || '';
@@ -275,7 +314,10 @@ export const useActivityStore = defineStore('Activity', () => {
}
if (!forceRefresh) {
- const persisted = await database.getActivityTopWorldsCacheV2(userId, rangeDays);
+ const persisted = await database.getActivityTopWorldsCacheV2(
+ userId,
+ rangeDays
+ );
if (persisted?.builtFromCursor === currentCursor) {
snapshot.topWorldsViews.set(cacheKey, persisted);
return persisted.worlds;
@@ -292,14 +334,16 @@ export const useActivityStore = defineStore('Activity', () => {
};
snapshot.topWorldsViews.set(cacheKey, entry);
deferWrite(() => database.replaceActivityTopWorldsCacheV2(entry));
- deferWrite(() => database.upsertActivityRangeCacheV2({
- userId,
- rangeDays,
- cacheKind: database.ACTIVITY_RANGE_CACHE_KIND.TOP_WORLDS,
- isComplete: true,
- builtFromCursor: currentCursor,
- builtAt: entry.builtAt
- }));
+ deferWrite(() =>
+ database.upsertActivityRangeCacheV2({
+ userId,
+ rangeDays,
+ cacheKind: database.ACTIVITY_RANGE_CACHE_KIND.TOP_WORLDS,
+ isComplete: true,
+ builtFromCursor: currentCursor,
+ builtAt: entry.builtAt
+ })
+ );
return worlds;
}
@@ -307,7 +351,13 @@ export const useActivityStore = defineStore('Activity', () => {
return loadActivity(userId, { ...options, forceRefresh: true });
}
- async function loadActivityView({ userId, isSelf = false, rangeDays = 30, dayLabels, forceRefresh = false }) {
+ async function loadActivityView({
+ userId,
+ isSelf = false,
+ rangeDays = 30,
+ dayLabels,
+ forceRefresh = false
+ }) {
const response = await loadActivity(userId, {
isSelf,
rangeDays,
@@ -325,7 +375,14 @@ export const useActivityStore = defineStore('Activity', () => {
};
}
- async function loadOverlapView({ currentUserId, targetUserId, rangeDays = 30, dayLabels, excludeHours, forceRefresh = false }) {
+ async function loadOverlapView({
+ currentUserId,
+ targetUserId,
+ rangeDays = 30,
+ dayLabels,
+ excludeHours,
+ forceRefresh = false
+ }) {
const response = await loadOverlap(currentUserId, targetUserId, {
rangeDays,
dayLabels,
@@ -342,7 +399,12 @@ export const useActivityStore = defineStore('Activity', () => {
};
}
- async function loadTopWorldsView({ userId, rangeDays = 30, limit = 5, forceRefresh = false }) {
+ async function loadTopWorldsView({
+ userId,
+ rangeDays = 30,
+ limit = 5,
+ forceRefresh = false
+ }) {
return loadTopWorlds(userId, {
rangeDays,
limit,
@@ -399,7 +461,10 @@ async function hydrateSnapshot(userId, isSelf) {
snapshot.sync = {
...snapshot.sync,
...syncState,
- isSelf: typeof syncState.isSelf === 'boolean' ? syncState.isSelf : snapshot.isSelf
+ isSelf:
+ typeof syncState.isSelf === 'boolean'
+ ? syncState.isSelf
+ : snapshot.isSelf
};
}
if (sessions.length > 0) {
@@ -408,7 +473,10 @@ async function hydrateSnapshot(userId, isSelf) {
return snapshot;
}
-async function ensureSnapshot(userId, { isSelf, rangeDays, forceRefresh = false }) {
+async function ensureSnapshot(
+ userId,
+ { isSelf, rangeDays, forceRefresh = false }
+) {
const jobKey = `${userId}:${isSelf}:${rangeDays}:${forceRefresh ? 'force' : 'normal'}`;
const existingJob = inFlightJobs.get(jobKey);
if (existingJob) {
@@ -440,7 +508,10 @@ async function fullRefresh(snapshot, rangeDays) {
isSelf: snapshot.isSelf,
fromDays: rangeDays
});
- const sourceLastCreatedAt = sourceItems.length > 0 ? sourceItems[sourceItems.length - 1].created_at : '';
+ const sourceLastCreatedAt =
+ sourceItems.length > 0
+ ? sourceItems[sourceItems.length - 1].created_at
+ : '';
const result = await workerCall('computeSessionsSnapshot', {
sourceType: snapshot.isSelf ? 'self_gamelog' : 'friend_presence',
rows: snapshot.isSelf ? sourceItems : undefined,
@@ -462,16 +533,20 @@ async function fullRefresh(snapshot, rangeDays) {
};
clearDerivedViews(snapshot);
- deferWrite(() => database.replaceActivitySessionsV2(snapshot.userId, snapshot.sessions));
+ deferWrite(() =>
+ database.replaceActivitySessionsV2(snapshot.userId, snapshot.sessions)
+ );
deferWrite(() => database.upsertActivitySyncStateV2(snapshot.sync));
- deferWrite(() => database.upsertActivityRangeCacheV2({
- userId: snapshot.userId,
- rangeDays,
- cacheKind: database.ACTIVITY_RANGE_CACHE_KIND.SESSIONS,
- isComplete: true,
- builtFromCursor: snapshot.sync.sourceLastCreatedAt,
- builtAt: snapshot.sync.updatedAt
- }));
+ deferWrite(() =>
+ database.upsertActivityRangeCacheV2({
+ userId: snapshot.userId,
+ rangeDays,
+ cacheKind: database.ACTIVITY_RANGE_CACHE_KIND.SESSIONS,
+ isComplete: true,
+ builtFromCursor: snapshot.sync.sourceLastCreatedAt,
+ builtAt: snapshot.sync.updatedAt
+ })
+ );
}
async function incrementalRefresh(snapshot) {
@@ -496,15 +571,18 @@ async function incrementalRefresh(snapshot) {
sourceType: snapshot.isSelf ? 'self_gamelog' : 'friend_presence',
rows: snapshot.isSelf ? sourceItems : undefined,
events: snapshot.isSelf ? undefined : sourceItems,
- initialStart: snapshot.isSelf ? null : snapshot.sync.pendingSessionStartAt,
+ initialStart: snapshot.isSelf
+ ? null
+ : snapshot.sync.pendingSessionStartAt,
nowMs: Date.now(),
mayHaveOpenTail: snapshot.isSelf,
sourceRevision: sourceLastCreatedAt
});
- const replaceFromStartAt = snapshot.sessions.length > 0
- ? snapshot.sessions[Math.max(snapshot.sessions.length - 1, 0)].start
- : null;
+ const replaceFromStartAt =
+ snapshot.sessions.length > 0
+ ? snapshot.sessions[Math.max(snapshot.sessions.length - 1, 0)].start
+ : null;
const merged = mergeSessions(snapshot.sessions, result.sessions);
snapshot.sessions = merged;
snapshot.sync = {
@@ -515,14 +593,17 @@ async function incrementalRefresh(snapshot) {
};
clearDerivedViews(snapshot);
- const tailSessions = replaceFromStartAt === null
- ? merged
- : merged.filter((session) => session.start >= replaceFromStartAt);
- deferWrite(() => database.appendActivitySessionsV2({
- userId: snapshot.userId,
- sessions: tailSessions,
- replaceFromStartAt
- }));
+ const tailSessions =
+ replaceFromStartAt === null
+ ? merged
+ : merged.filter((session) => session.start >= replaceFromStartAt);
+ deferWrite(() =>
+ database.appendActivitySessionsV2({
+ userId: snapshot.userId,
+ sessions: tailSessions,
+ replaceFromStartAt
+ })
+ );
deferWrite(() => database.upsertActivitySyncStateV2(snapshot.sync));
}
@@ -550,58 +631,120 @@ async function expandRange(snapshot, rangeDays) {
if (result.sessions.length > 0) {
snapshot.sessions = mergeSessions(result.sessions, snapshot.sessions);
- deferWrite(() => database.replaceActivitySessionsV2(snapshot.userId, snapshot.sessions));
+ deferWrite(() =>
+ database.replaceActivitySessionsV2(
+ snapshot.userId,
+ snapshot.sessions
+ )
+ );
}
snapshot.sync.cachedRangeDays = rangeDays;
snapshot.sync.updatedAt = new Date().toISOString();
clearDerivedViews(snapshot);
deferWrite(() => database.upsertActivitySyncStateV2(snapshot.sync));
- deferWrite(() => database.upsertActivityRangeCacheV2({
- userId: snapshot.userId,
- rangeDays,
- cacheKind: database.ACTIVITY_RANGE_CACHE_KIND.SESSIONS,
- isComplete: true,
- builtFromCursor: snapshot.sync.sourceLastCreatedAt,
- builtAt: snapshot.sync.updatedAt
- }));
+ deferWrite(() =>
+ database.upsertActivityRangeCacheV2({
+ userId: snapshot.userId,
+ rangeDays,
+ cacheKind: database.ACTIVITY_RANGE_CACHE_KIND.SESSIONS,
+ isComplete: true,
+ builtFromCursor: snapshot.sync.sourceLastCreatedAt,
+ builtAt: snapshot.sync.updatedAt
+ })
+ );
}
function pickActivityNormalizeConfig(isSelf, rangeDays) {
const role = isSelf ? 'self' : 'friend';
- return {
- self: {
- 7: { floorPercentile: 10, capPercentile: 80, rankWeight: 0.15, targetCoverage: 0.12, targetVolume: 40 },
- 30: { floorPercentile: 15, capPercentile: 85, rankWeight: 0.20, targetCoverage: 0.25, targetVolume: 60 },
- 90: { floorPercentile: 15, capPercentile: 85, rankWeight: 0.20, targetCoverage: 0.30, targetVolume: 50 },
- 180: { floorPercentile: 20, capPercentile: 85, rankWeight: 0.20, targetCoverage: 0.35, targetVolume: 40 }
- },
- friend: {
- 7: { floorPercentile: 10, capPercentile: 80, rankWeight: 0.15, targetCoverage: 0.12, targetVolume: 40 },
- 30: { floorPercentile: 15, capPercentile: 85, rankWeight: 0.20, targetCoverage: 0.25, targetVolume: 60 },
- 90: { floorPercentile: 15, capPercentile: 85, rankWeight: 0.20, targetCoverage: 0.30, targetVolume: 50 },
- 180: { floorPercentile: 20, capPercentile: 85, rankWeight: 0.20, targetCoverage: 0.35, targetVolume: 40 }
+ return (
+ {
+ self: {
+ 7: {
+ floorPercentile: 10,
+ capPercentile: 80,
+ rankWeight: 0.15,
+ targetCoverage: 0.12,
+ targetVolume: 40
+ },
+ 30: {
+ floorPercentile: 15,
+ capPercentile: 85,
+ rankWeight: 0.2,
+ targetCoverage: 0.25,
+ targetVolume: 60
+ },
+ 90: {
+ floorPercentile: 15,
+ capPercentile: 85,
+ rankWeight: 0.2,
+ targetCoverage: 0.3,
+ targetVolume: 50
+ }
+ },
+ friend: {
+ 7: {
+ floorPercentile: 10,
+ capPercentile: 80,
+ rankWeight: 0.15,
+ targetCoverage: 0.12,
+ targetVolume: 40
+ },
+ 30: {
+ floorPercentile: 15,
+ capPercentile: 85,
+ rankWeight: 0.2,
+ targetCoverage: 0.25,
+ targetVolume: 60
+ },
+ 90: {
+ floorPercentile: 15,
+ capPercentile: 85,
+ rankWeight: 0.2,
+ targetCoverage: 0.3,
+ targetVolume: 50
+ }
+ }
+ }[role][rangeDays] || {
+ floorPercentile: 15,
+ capPercentile: 85,
+ rankWeight: 0.2,
+ targetCoverage: 0.25,
+ targetVolume: 60
}
- }[role][rangeDays] || {
- floorPercentile: 15,
- capPercentile: 85,
- rankWeight: 0.20,
- targetCoverage: 0.25,
- targetVolume: 60
- };
+ );
}
function pickOverlapNormalizeConfig(rangeDays) {
- return {
- 7: { floorPercentile: 10, capPercentile: 80, rankWeight: 0.15, targetCoverage: 0.08, targetVolume: 15 },
- 30: { floorPercentile: 15, capPercentile: 85, rankWeight: 0.20, targetCoverage: 0.15, targetVolume: 25 },
- 90: { floorPercentile: 15, capPercentile: 85, rankWeight: 0.20, targetCoverage: 0.18, targetVolume: 20 },
- 180: { floorPercentile: 20, capPercentile: 85, rankWeight: 0.20, targetCoverage: 0.20, targetVolume: 15 }
- }[rangeDays] || {
- floorPercentile: 15,
- capPercentile: 85,
- rankWeight: 0.20,
- targetCoverage: 0.15,
- targetVolume: 25
- };
+ return (
+ {
+ 7: {
+ floorPercentile: 10,
+ capPercentile: 80,
+ rankWeight: 0.15,
+ targetCoverage: 0.08,
+ targetVolume: 15
+ },
+ 30: {
+ floorPercentile: 15,
+ capPercentile: 85,
+ rankWeight: 0.2,
+ targetCoverage: 0.15,
+ targetVolume: 25
+ },
+ 90: {
+ floorPercentile: 15,
+ capPercentile: 85,
+ rankWeight: 0.2,
+ targetCoverage: 0.18,
+ targetVolume: 20
+ }
+ }[rangeDays] || {
+ floorPercentile: 15,
+ capPercentile: 85,
+ rankWeight: 0.2,
+ targetCoverage: 0.15,
+ targetVolume: 25
+ }
+ );
}